问题
Currently I have an ActionFilter that gets the current users name from HttpContext and passes it into the action which uses it on a service method. eg:
Service.DoSomething(userName);
I now have a reason to do this not at the action level but the controller constructor level. Currently I'm using structure map to create controllers and inject the service. I'm looking at something like:
public interface IUserProvider
{
string UserName { get; }
}
public class HttpContextUserProvider : IUserProvider
{
private HttpContext context;
public HttpContextUserProvider(HttpContext context)
{
this.context = context;
}
public string UserName
{
get
{
return context.User.Identity.Name;
}
}
}
That said, my IoC foo is really weak as this is the first project I've used it on.
So my question is... how can I tell structure map to pass in HttpContext in the constructor for HttpContextUserProvider? This just seems weird... I'm not sure how to think of HttpContext.
回答1:
Have an interface abstract HttpContext.Current. Expose only the methods you need. GetUserName()
would call HttpContext.Current.User.Identity.Name in the implementation, for example. Make that as thin as possible.
Take that abstraction and inject it into your other provider class. This will allow you to test the provider by mocking the http context abstraction. As a side benefit, you can do other nifty things with that HttpContext abstraction besides mock it. Reuse it, for one thing. Add generic type params to bags, etc.
回答2:
It sounds like you should be using HttpContextBase
instead of HttpContextUserProvider
. This is a out-of-box abstraction of HttpContext
and allows you to create a mock, write UnitTests and inject your dependencies.
public class SomethingWithDependenciesOnContext
{
public SomethingWithDependenciesOnContext(HttpContextBase context) {
...
}
public string UserName
{
get {return context.User.Identity.Name;}
}
}
ObjectFactory.Initialize(x =>
x.For<HttpContextBase>()
.HybridHttpOrThreadLocalScoped()
.Use(() => new HttpContextWrapper(HttpContext.Current));
回答3:
I'm not sure why you're bothering. It seems like just using HttpContext.Current directly in HttpContextUserProvider is the right thing to do. You're never going to be substituting in a different HttpContext...
回答4:
Maybe I left something out, but the above answer doesn't work for me (has since been deleted -- it was still a useful answer though -- it showed how to tell SM to pass constructor arguments). Instead if I do:
ObjectFactory.Initialize(x =>
{
x.BuildInstancesOf<HttpContext>()
.TheDefault.Is.ConstructedBy(() => HttpContext.Current);
x.ForRequestedType<IUserProvider>()
.TheDefault.Is.OfConcreteType<HttpContextUserProvider>();
});
I get it to work. I did this after finding: http://codebetter.com/blogs/jeremy.miller/archive/2008/03/20/if-you-need-something-in-structuremap-but-you-can-t-build-it-with-new.aspx
edit:
Thanks to Brad's answer I think I have a better handle on HttpContext. His answer definitely works, I just am not sure I like having the call to HttpContext.Current inside a class (it seems like it hides the dependency, but I'm far from an expert on this stuff).
The above code should work for injecting HttpContext as far as I can tell. Matt Hinze brings up the added that point that if all I need from HttpContext is the User.Identity.Name, my design should be explicit about that (having an Interface around HttpContext only exposing what I need). I think this is a good idea.
The thing is over lunch I kinda realized my service really only needs to depend on a string: userName. Having it depend on IUserProvider might not have much added value. So I know I don't want it to depend on HttpContext, and I do know all I need is a string (userName) -- I need to see if I can learn enough StructureMap foo to have make this connection for me. (sirrocoo's answer gives a hint on where to start but he deleted it :*( ).
来源:https://stackoverflow.com/questions/877712/asp-net-mvc-httpcontext-and-dependency-injection