MVC5 Ninject绑定和HttpContext
我正在尝试建立一个新项目,并且我添加了一个新类MembershipService ,它需要在它的构造函数中传递HttpContext 。
在之前的项目中,我使用了代码
private static void RegisterServices(IKernel kernel) { kernel.Bind() .To() .InRequestScope() .WithConstructorArgument("context", HttpContext.Current); .... }
然而,在新项目中,我正在使用Ninject Modules,在对StackOverflow和Google进行一些搜索之后,我提出了以下代码:public class ServiceHandlerModule:NinjectModule {
public override void Load() { Bind() .To() .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() .SelectAllClasses() .Where(t => t != typeof(MembershipService)) .BindDefaultInterface(); }); } }
但是,我收到以下错误:
描述:执行当前Web请求期间发生未处理的exception。 请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息。
exception详细信息:Ninject.ActivationException:激活字符串时出错没有匹配的绑定可用,并且该类型不可自绑定。 激活路径:
5)将依赖字符串注入到HttpRequest类型的构造函数的参数文件名中
4)将依赖关系HttpRequest注入到HttpContext类型的构造函数的参数请求中
3)将依赖关系HttpContext注入到MembershipService类型的构造函数的参数httpContext中
2)将依赖关系IMembershipService注入到HomeController类型的构造函数的参数membershipService中
1)请求HomeController
有人可以指出我哪里出错了吗?
谢谢,约翰
Steven认为HttpContext
是一个运行时值。 在编写应用程序时,它的值甚至不会填充。
如果您考虑它,这是有道理的,因为应该在任何单个用户上下文之外初始化应用程序。
但是,史蒂文的解决方案只是将问题转移到了另一项服务上。 毕竟,实现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); } }
然后可以修改您的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) } }
而且你只需要在组合时注入HttpContextFactory
。
kernel.Bind() .To(); kernel.Bind() .To();
但是,仅这一点可能无法解决整个问题。 您需要确保应用程序的其余部分在准备好之前不尝试使用HttpContext
。 就DI而言,这意味着您不能在应用程序启动中组成的任何类型的构造函数或其中一个构造函数调用的任何服务成员中使用HttpContext
。 要解决这个问题,您可能需要创建其他抽象工厂,以确保在HttpContext
准备好之前,这些服务不会调用IMembershipService
成员。
有关如何完成此操作的详细信息,请参阅此答案 。
Steven的解决方案还需要围绕HttpContext
创建Facade 。 虽然这并没有真正帮助解决手头的问题,但我同意,如果您的MembershipService
(可能还有其他服务)只使用少量的HttpContext
成员,这可能是一个好主意。 通常,此模式是使复杂对象更易于使用(例如将其展平为可能嵌套在其层次结构内的少数成员)。 但是你真的需要权衡添加另一种类型的额外维护与在应用程序中使用HttpContext
的复杂性(或交换其中一部分的值)来做出决定。
我添加了一个新类MembershipService,它需要在它的构造函数中传递HttpContext。
这是你出错的地方。 HttpContext是一个运行时值,但您的对象图应该只包含编译时或配置时依赖性。 任何其他的,运行时值,应该通过方法调用传递,或者应该作为注入的服务的属性公开。
不遵循本指南,将使撰写和测试对象图变得更加困难。 测试组合根是一个很好的例子,因为在测试框架内运行时HttpContext.Current
不可用。
因此,请阻止此MembershipService
对HttpContext
采用构造函数依赖项。 相反,注入一个将HttpContext
公开为属性的服务,因为这允许您在对象图是构造函数之后请求此上下文。
但也许更好的方法是将HttpContext
隐藏在特定于应用程序的抽象背后。 HttpContext
不是一个抽象; 它是一个庞大而丑陋的API,使您的代码更难以测试,更难以理解。 相反,创建非常狭窄/专注的接口,例如这样的接口:
public interface IUserContext { User CurrentUser { get; } }
现在,您的MembershipService
可以依赖于通过属性公开User
对象的IUserContext
。 现在,您可以在调用CurrentUser
属性时创建一个在内部使用HttpContext.Current
的AspNetUserContext
实现。 这样可以生成更清晰,更易维护的代码。
这是一个可能的实现:
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); } } }
我同意史蒂文,但你也可以:
kernel.Bind().ToMethod(c => HttpContext.Current);