Search code examples
c#asp.net-mvcdependency-injectionninjecthttpcontext

MVC5 Ninject binding and HttpContext


I am trying to set up a new project and I've added a new class MembershipService that requires the HttpContext to be passed in it's constructor.

In a previous project I used the code

    private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<IMembershipService>()
            .To<MembershipService>()
            .InRequestScope()
            .WithConstructorArgument("context", HttpContext.Current);
       ....
    }

However in the new project I'm using Ninject Modules, and after some searching on StackOverflow and Google, I've come up with the code below: public class ServiceHandlerModule : NinjectModule {

    public override void Load()
    {

        Bind<IMembershipService>()
            .To<MembershipService>()
            .WithConstructorArgument("context", ninjectContext=> HttpContext.Current);


        this.Kernel.Bind(x =>
        {
            x.FromAssemblyContaining(typeof(NinjectWebCommon))
                .SelectAllClasses()
                .Where(t => t != typeof(MembershipService))
                .BindDefaultInterface();
        });
        this.Kernel.Bind(x =>
        {
            x.FromAssemblyContaining<BrandServiceHandler>()
                .SelectAllClasses()
                .Where(t => t != typeof(MembershipService))
                .BindDefaultInterface();
        });

    }
}

However, I get the error below:

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: Ninject.ActivationException: Error activating string No matching bindings are available, and the type is not self-bindable. Activation path:

5) Injection of dependency string into parameter filename of constructor of type HttpRequest

4) Injection of dependency HttpRequest into parameter request of constructor of type HttpContext

3) Injection of dependency HttpContext into parameter httpContext of constructor of type MembershipService

2) Injection of dependency IMembershipService into parameter membershipService of constructor of type HomeController

1) Request for HomeController

Can someone point out where I'm going wrong?

Thanks, John


Solution

  • Steven was right about the HttpContext being a runtime value. Its values are not even populated at the time the application is composed.

    This makes sense if you think about it because the application should be initialized outside of any individual user context.

    However, Steven's solution only moved the problem to a different service. After all, the class that implements IUserContext will still need to take HttpContext as a dependency.

    The solution is to use an Abstract Factory to allow the HttpContext instance to be accessed at runtime instead of when the factory is wired up.

    Important: HttpContext is not an abstraction, so it cannot be swapped or mocked. To ensure we are dealing with an abstraction, Microsoft has provided the HttpContextBase abstract class and the default concrete type HttpContextWrapper. HttpContextBase has exactly the same interface as HttpContext. You should always use HttpContextBase as the abstract reference type within your services, not HttpContext.

    With those 2 things in mind, you can create a factory for your HttpContext, as follows:

    public interface IHttpContextFactory
    {
        HttpContextBase Create();
    }
    
    public class HttpContextFactory
        : IHttpContextFactory
    {
        public HttpContextBase Create()
        {
            return new HttpContextWrapper(HttpContext.Current);
        }
    }
    

    Your MembershipService can then be modified to accept an IHttpContextFactory in its constructor:

    public class MembershipService : IMembershipService
    {
        private readonly IHttpContextFactory httpContextFactory;
    
        // This is called at application startup, but note that it 
        // does nothing except get our service(s) ready for runtime.
        // It does not actually use the service.
        public MembershipService(IHttpContextFactory httpContextFactory)
        {
            if (httpContextFactory == null)
                throw new ArgumentNullException("httpContextFactory");
            this.httpContextFactory = httpContextFactory;
        }
    
        // Make sure this is not called from any service constructor
        // that is called at application startup.
        public void DoSomething()
        {
            HttpContextBase httpContext = this.httpContextFactory.Create();
    
            // Do something with HttpContext (at runtime)
        }
    }
    

    And you need only inject the HttpContextFactory at composition time.

    kernel.Bind<IHttpContextFactory>()
        .To<HttpContextFactory>();
    
    kernel.Bind<IMembershipService>()
        .To<MembershipService>();
    

    This alone might not solve the entire issue, though. You need to ensure that the rest of your application does not try to use HttpContext before it is ready. In terms of DI, it means you can't use HttpContext in any constructor of types that are composed in application start or any service members that one of those constructors calls. To solve that, you may need to create additional abstract factories to ensure those services don't call members of IMembershipService until HttpContext is ready.

    See this answer for more information about how to accomplish that.

    Steven's solution also entailed creating a Facade around HttpContext. While this does not really help solve the problem at hand, I agree that this might be a good idea if your MembershipService (and perhaps other services) only uses a small number of members of HttpContext. Generally, this pattern is to make a complex object simpler to work with (such as flattening it down to a few members that may be nested deep within its hierarchy). But you really need to weigh the extra maintenance of adding another type against the complexity of using HttpContext within your application (or the value of swapping out a section of it) to make that decision.