ASP.NET MVC:HTTPContext和依赖注入

18

目前我有一个ActionFilter,它从HttpContext中获取当前用户的名称并将其传递到使用服务方法的操作中。例如:

Service.DoSomething(userName);

我现在有一个理由在控制器构造函数级别上进行操作,而不是在操作级别上进行操作。目前我正在使用StructureMap创建控制器并注入服务。我正在查看类似于以下内容:

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;
        }
    }
}

话虽如此,我对IoC并不熟悉,因为这是我第一次在项目中使用它。

我的问题是... 如何告诉Structure Map在HttpContextUserProvider的构造函数中传递HttpContext?这似乎很奇怪...我不确定如何考虑HttpContext。

4个回答

10

看起来您应该使用HttpContextBase而不是HttpContextUserProvider。这是HttpContext的开箱即用的抽象,并允许您创建模拟对象,编写单元测试并注入您的依赖项。

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));

8
拥有一个接口抽象HttpContext.Current。只暴露你需要的方法。例如,GetUserName()在实现中会调用HttpContext.Current.User.Identity.Name。尽可能使它变得简单。
将该抽象注入到其他提供程序类中。这将允许您通过模拟http上下文抽象来测试提供程序。作为一个附带好处,除了模拟之外,您还可以使用那个HttpContext抽象执行其他妙招。例如重复使用它,添加泛型类型参数等。

你是什么意思,"在袋子中添加通用类型参数"?听起来很有趣。 - Charlie Flowers
4
提供强类型封装来覆盖Session。 - Matt Hinze
3
你有任何样例代码/链接吗?这看起来很有趣,但我对依赖注入比较陌生,所以我不能很好地理解它。如果可以提供任何帮助或建议,将不胜感激... - Haroon
请您能否举个例子? - Petrus Theron

4

我不确定你为什么要这样做。直接在HttpContextUserProvider中使用HttpContext.Current似乎是正确的做法。因为你永远不会替换掉另一个HttpContext...


2
哎呀...我觉得我对HttpContext不是太懂。我猜我习惯了通过构造函数传递参数,而HttpContext似乎是全局存在的? - anonymous
好的,我返回了HttpContext.Current.User.Identity.Name;,它起作用了。 我想我有点迟钝,因为Current是HttpContext上的静态属性,负责知道如何找到当前的HttpContext。唯一的缺点是你不能(至少我认为你不能)通过构造函数注入明确地依赖于HttpContext,因为你不能传递静态属性或类型? 你可以创建一个类似IHttpContextProvider的包装器,它只返回HttpContext.Current,然后你就会知道某些内容依赖于HttpContext?还是这样很愚蠢? - anonymous
1
在不引用Web的服务层中,您如何注入HttpContext呢?也许我们可以说这是一个使用案例场景,其中记录/数据集取决于当前用户(asp.net mvc2)... - Haroon

2
也许我漏掉了一些东西,但上面的答案对我不起作用(已被删除——尽管它仍然是一个有用的答案——它显示了如何告诉SM传递构造函数参数)。相反,如果我这样做:
ObjectFactory.Initialize(x =>
{
    x.BuildInstancesOf<HttpContext>()
         .TheDefault.Is.ConstructedBy(() => HttpContext.Current);
    x.ForRequestedType<IUserProvider>()
         .TheDefault.Is.OfConcreteType<HttpContextUserProvider>();
});

我做到了。在找到这篇文章之后,我让它正常工作:如果你需要使用StructureMap,但不能使用new()构建它...
编辑:
感谢Brad的回答,我觉得我更好地掌握了HttpContext. 他的答案肯定有效,只是我不确定是否喜欢在类内部调用HttpContext.Current(似乎隐藏了依赖关系,但我对这方面并不是很专业)。
据我所知,上述代码应该可以用于注入HttpContext. Matt Hinze提出了一个额外的问题,即如果我从HttpContext中需要的仅仅是User.Identity.Name,那么我的设计应该明确这一点(只有一个接口围绕HttpContext,只公开我需要的东西)。我认为这是个好主意。
问题是,在午餐时我意识到我的服务实际上只需要依赖于一个字符串:userName。依赖于IUserProvider可能没有太多的增加价值。所以我知道我不想依赖于HttpContext,我确实知道我需要的仅仅是一个字符串(userName)——我需要看看自己是否可以学到足够的StructureMap技巧,让它帮我建立这个连接。(sirrocoo的回答给了一个提示,但他删掉了它:*( )。

通俗地说,当构造函数依赖于HttpContext时,IoC将传递HttpContext.Current的实例。而当构造函数依赖于IUserProvider时,IoC将实例化一个新的HttpContextUserProvider实例并将其传递到构造函数中。 - Todd Smith
StructureMap接口读起来很好。我的困惑在于我认为有两个部分:1)我只使用过ForRequestedType - 我怎么知道何时才需要使用BuildInstancesOf(我可能只需要谷歌一下--这个问题很简单)。2)我可以认为StructureMap知道如何获取HttpContext.Current吗?我想HttpContext让我有点困惑,因为它似乎在各个地方神奇地飘荡着。我想不出能够获取它的一个来源,而其他所有东西都有更清晰的来源。 - anonymous
我的解决方案确实没有起作用,甚至出现了一个严重的错误,内部异常提示了有关JIT限制的内容...哇 - sirrocco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接