>我正在尝试设置一个新项目,并且添加了一个新的类MemberService,它要求在其构造函数中传递HttpContext。
在以前的项目中,我使用了代码
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IMembershipService>()
.To<MembershipService>()
.InRequestScope()
.WithConstructorArgument("context", HttpContext.Current);
....
}
然而,在新项目中,我使用的是Ninject模块,在StackOverflow和Google上进行一些搜索后,我想出了以下代码: 公共类 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();
});
}
}
但是,我收到以下错误:
说明:执行 当前 Web 请求。请查看堆栈跟踪以获取更多信息 有关错误及其在代码中起源位置的信息。
异常详细信息:Ninject.激活异常:激活时出错 字符串 没有匹配的绑定可用,并且类型不是 可自绑定。激活路径:
5) 将依赖字符串注入到参数文件名中 HttpRequest 类型的构造函数
4) 将依赖 HttpRequest 注入到参数请求中 HttpContext 类型的构造函数
3) 将依赖 HttpContext 注入到参数 httpContext 中 成员服务类型的构造函数
2) 将依赖关系注入到参数中 成员类型为HomeController的构造函数的服务
1) 请求主控制器
有人可以指出我哪里出错了吗?
谢谢John
Steven关于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);
}
}
然后,可以修改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<IHttpContextFactory>()
.To<HttpContextFactory>();
kernel.Bind<IMembershipService>()
.To<MembershipService>();
但是,仅凭这一点可能无法解决整个问题。您需要确保应用程序的其余部分在准备就绪之前不会尝试使用HttpContext
。就 DI 而言,这意味着不能在应用程序启动中组合的任何类型的构造函数或其中一个构造函数调用的任何服务成员中使用HttpContext
。为了解决这个问题,您可能需要创建其他抽象工厂,以确保这些服务在准备就绪之前不会调用IMembershipService
成员HttpContext
。
有关如何实现此目的的更多信息,请参阅此答案。
史蒂文的解决方案还需要在HttpContext
周围创建一个立面。虽然这并不能真正帮助解决手头的问题,但我同意,如果您的MembershipService
(也许还有其他服务)仅使用少量HttpContext
成员,这可能是一个好主意。通常,此模式是为了使复杂对象更易于使用(例如将其平展为可能嵌套在其层次结构深处的几个成员)。但是,您确实需要权衡添加另一种类型的额外维护与在应用程序中使用HttpContext
的复杂性(或交换其中一部分的价值)来做出决定。
我添加了一个新的类成员服务,它需要 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<HttpContext>().ToMethod(c => HttpContext.Current);