MVC5 Ninject绑定和HttpContext

Joh*_*hur 2 c# asp.net-mvc dependency-injection ninject httpcontext

我正在尝试建立一个新项目,并且我添加了一个新类MembershipService,它需要在它的构造函数中传递HttpContext.

在之前的项目中,我使用了代码

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

然而,在新项目中,我正在使用Ninject Modules,在对StackOverflow和Google进行一些搜索之后,我提出了以下代码: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();
        });

    }
}
Run Code Online (Sandbox Code Playgroud)

但是,我收到以下错误:

描述:执行当前Web请求期间发生未处理的异常.请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息.

异常详细信息:Ninject.ActivationException:激活字符串时出错没有匹配的绑定可用,并且该类型不可自我绑定.激活路径:

5)将依赖字符串注入到HttpRequest类型的构造函数的参数filename中

4)将依赖关系HttpRequest注入到HttpContext类型的构造函数的参数请求中

3)将依赖关系HttpContext注入到MembershipService类型的构造函数的参数httpContext中

2)将依赖关系IMembershipService注入到HomeController类型的构造函数的参数membershipService中

1)请求HomeController

谁能指出我哪里出错了?

谢谢,约翰

Nig*_*888 15

史蒂文对于HttpContext成为运行时价值是正确的.在编写应用程序时,它的值甚至不会填充.

如果您考虑它,这是有道理的,因为应该在任何单个用户上下文之外初始化应用程序.

但是,Steven的解决方案只将问题转移到了另一项服务上.毕竟,实现的类IUserContext仍然需要HttpContext作为依赖项.

解决方案是使用抽象工厂来允许HttpContext在运行时访问实例,而不是在工厂接线时访问.

重要说明: HttpContext不是抽象,因此无法交换或模拟.为了确保我们处理抽象,Microsoft提供了HttpContextBase抽象类和默认的具体类型HttpContextWrapper.HttpContextBase与HttpContext具有完全相同的接口.您应始终使用HttpContextBase作为服务中的抽象引用类型,而不是HttpContext.

考虑到这两件事,您可以为您创建一个工厂HttpContext,如下所示:

public interface IHttpContextFactory
{
    HttpContextBase Create();
}

public class HttpContextFactory
    : IHttpContextFactory
{
    public HttpContextBase Create()
    {
        return new HttpContextWrapper(HttpContext.Current);
    }
}
Run Code Online (Sandbox Code Playgroud)

MembershipService然后,您可以修改为IHttpContextFactory在其构造函数中接受:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

而且你只需要注入HttpContextFactory组合时间.

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

kernel.Bind<IMembershipService>()
    .To<MembershipService>();
Run Code Online (Sandbox Code Playgroud)

但是,仅此一点可能无法解决整个问题.您需要确保应用程序的其余部分HttpContext在准备好之前不尝试使用.就DI而言,这意味着您不能HttpContext在任何由应用程序启动组成的类型的构造函数或其中一个构造函数调用的服务成员中使用.要解决这个问题,您可能需要创建其他抽象工厂,以确保这些服务在准备好IMembershipService之前不会调用成员HttpContext.

有关如何完成此操作的详细信息,请参阅此答案.

史蒂芬的解决方案秘书处还就建立一个门面周围HttpContext.虽然这并没有真正帮助解决手头的问题,但我同意如果您MembershipService(以及可能的其他服务)仅使用少数成员,这可能是一个好主意HttpContext.通常,此模式是使复杂对象更易于使用(例如将其展平为可能嵌套在其层次结构内的少数成员).但是你真的需要权衡添加另一种类型的额外维护与HttpContext应用程序中使用的复杂性(或交换其中一部分的价值)来做出决定.


Ste*_*ven 5

我添加了一个新类MembershipService,它需要在它的构造函数中传递HttpContext.

这是你出错的地方.HttpContext是一个运行时值,但您的对象图应该只包含编译时或配置时依赖性.其他任何内容,运行时值,应该通过方法调用传递,或者应该作为注入的服务的属性公开.

不遵循本指南,将使撰写和测试对象图变得更加困难.测试组合根是一个很好的例子,因为HttpContext.Current在测试框架内运行时不可用.

因此,请防止此操作MembershipService依赖于构造函数HttpContext.相反,注入一个公开HttpContext属性的服务,因为这允许您在对象图是构造函数之后请求此上下文.

但也许更好的是隐藏HttpContext一个特定于应用程序的抽象背后.HttpContext不是抽象; 它是一个庞大而丑陋的API,使您的代码更难以测试,更难以理解.相反,创建非常狭窄/专注的界面,例如这样的界面:

public interface IUserContext
{
    User CurrentUser { get; }
}
Run Code Online (Sandbox Code Playgroud)

现在你MembershipService可以依赖于通过属性IUserContext公开User对象的东西.现在,您可以在调用属性时创建一个AspNetUserContextHttpContext.Current内部使用的实现CurrentUser.这样可以生成更清晰,更易维护的代码.

这是一个可能的实现:

public class AspNetUserContext : IUserContext
{
    public User CurrentUser
    {
        // Do not inject HttpContext in the ctor, but use it
        // here in this property
        get { return new User(HttpContext.Current.User); }
    }
}
Run Code Online (Sandbox Code Playgroud)