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