HttpContextWrapper 真的那么有用吗?
我一直在清理控制器代码以使每个操作都可测试。一般来说,这并不太困难——当我们有机会使用固定对象时,比如 FormsAuthentication,我们通常会适当地引入某种形式的包装器,然后就可以愉快地进行了。
由于与本次对话不太相关的原因,在处理 HttpContext 的使用时,我们决定使用新创建的 HttpContextWrapper 类,而不是发明一些自行开发的类。我们确实引入的一件事是交换 HttpContextWrapper 的能力(例如,用于单元测试)。这完全受到 Oren Eini 使用 DateTimes 处理单元测试的方式的启发 (参见文章,我们也使用这种模式)
public static class FooHttpContext
{
public static Func<HttpContextWrapper> Current = ()
=> new HttpContextWrapper(HttpContext.Current);
public static void Reset()
{
Current = () => new HttpContextWrapper(HttpContext.Current);
}
}
没有什么特别奇特的。它在我们的控制器代码中运行得很好。当我们去编写单元测试时,问题就来了。我们使用 Moq 作为我们的模拟框架,但可惜的是,
var context = new Mock<HttpContextWrapper>()
由于 HttpContextWrapper 没有无参数构造器,所以中断了。它需要什么作为 ctor 参数?一个 HttpContext 对象。所以我发现自己陷入了第 22 条陷阱。
我正在使用规定的方式来解耦 HttpContext,但我无法模拟其中的值,因为原始 HttpContext 对象是密封的,因此难以测试。我可以映射 HttpContextBase,它们都派生自——但这并不能真正让我得到我想要的东西。我是否只是错过了有关 HttpContextWrapper 的要点?
编辑以澄清意图
我们找到了解决问题的方法 - 但我想我们要解决的最终问题是 HttpContextWrapper 给桌面带来了什么价值?我不怀疑某个地方有人完全惊叹不已!但我就是想不起来。我在这里看到的大多数帖子都从可测试性的角度讨论它——但我自己的经验让我相信它在这方面并没有带来太多好处。除非我们做错了。 (完全有可能)。
I've been going through the process of cleaning up our controller code to make each action as testable. Generally speaking, this hasn't been too difficult--where we have opportunity to use a fixed object, like say FormsAuthentication, we generally introduce some form of wrapper as appropriate and be on our merry way.
For reasons not particularly germaine to this conversation, when it came to dealing with usage of HttpContext, we decided to use the newly created HttpContextWrapper class rather than inventing something homegrown. One thing we did introduce was the ability to swap in a HttpContextWrapper (like say, for unit testing). This was wholly inspired by the way Oren Eini handles unit testing with DateTimes (see article, a pattern we also use)
public static class FooHttpContext
{
public static Func<HttpContextWrapper> Current = ()
=> new HttpContextWrapper(HttpContext.Current);
public static void Reset()
{
Current = () => new HttpContextWrapper(HttpContext.Current);
}
}
Nothing particularly fancy. And it works just fine in our controller code. The kicker came when we go to write unit tests. We're using Moq as our mocking framework, but alas
var context = new Mock<HttpContextWrapper>()
breaks since HttpContextWrapper doesn't have a parameterless ctor. And what does it take as a ctor parameter? A HttpContext object. So I find myself in a catch 22.
I'm using the prescribed way to decouple HttpContext--but I can't mock a value in because the original HttpContext object was sealed and therefore difficult to test. I can map HttpContextBase, which both derive from--but that doesn't really get me what I'm after. Am I just missing the point somewhere with regard to HttpContextWrapper?
Edit to clarify intent
We found ways to solve the problem--but I guess the ultimate question we're walking away with is what value HttpContextWrapper brings to the table? I don't doubt somewhere someone totally had an a-ha! moment with it, but it just doesn't come to me. Most postings I see here discuss it in terms of testability--but my own experience has led me to believe that it didn't bring much in that context. Unless we're doing it wrong. (Wholly possible).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这篇博文很好地解释了这一点:
http://splinter.com.au/httpcontext -vs-httpcontextbase-vs-httpcontext
要点是“老式”HttpContext 不实现 HttpContextBase,并且不是虚拟的,因此不能被 Mocked。 HttpContextBase 在 3.5 中作为可模拟的替代方案引入。但仍然存在一个问题,即老式 HttpContext 没有实现 HttpContextBase。
因此 HttpContextWrapper 是一个方便的包装类(或“kludge”),它实现了 HttpContextBase,并且可以在使用 IOC 注入“真实”HttpContext 时使用,通常使用如下工厂方法:新的 HttpContextWrapper(HttpContext.Current)
This blog post explains it pretty well:
http://splinter.com.au/httpcontext-vs-httpcontextbase-vs-httpcontext
The point is that 'vintage' HttpContext does not implement HttpContextBase, and isn't virtual, and therefore cannot be Mocked. HttpContextBase was introduced in 3.5 as a mockable alternative. But there's still the problem that vintage HttpContext doesn't implement HttpContextBase.
So HttpContextWrapper is a handy wrapper class (or 'kludge') that does implement HttpContextBase, and can be used when injecting a 'real' HttpContext using IOC, usually with a factory method like this:
() => new HttpContextWrapper(HttpContext.Current)
您应该使用抽象的 HttpContextBase ,它比 HttpContextWrapper 更容易模拟。
在你的单元测试中:
You should be using the abstract
HttpContextBase
which is much easier to mock instead ofHttpContextWrapper
.And in your unit test:
除了测试之外,还有一个现实世界的例子。
除了嘲笑之外,我偶然发现了一个特殊的问题,包装类确实帮助我解决了这个问题。我们在 Azure 中有一个应用程序,并且我们只能控制该应用程序。它位于反向代理后面,该代理更改传入请求的主机标头并在自定义标头中发送原始主机。应用程序依赖主机标头来构建动态链接、验证重定向等。因此我们需要一种方法来替换 HttpContext.HttpRequests.Url 属性中设置的主机。由于我们仅使用应用程序中各处的包装器将 HttpContext 公开为 HttpContextBaase,因此我们能够创建一个继承 HttpContextWrapper 并覆盖 Request 的类,然后返回从 RequestWrapper 继承并覆盖 Url 属性的对象。因此,最终应用程序将 ASP.NET 用于上下文的 url 中的主机替换为反向代理设置的自定义标头中的自定义主机。除了手动搜索使用 HttpContext.Request.Url 的代码并应用修复之外,应用程序内没有其他方法可以做到这一点。
One real world example other than testing.
Besides mocking I stumbled upon a peculiar issue that the wrapper class really helped me solve. We have an application in Azure and we have control only on the application. It sits behind a reverse proxy which changes the host header of the incoming requests and sends the original host in a custom header. The application relies on the host header in order to build dynamic links, validate redirects etc. so we needed a way to substitute the host that was set in the HttpContext.HttpRequests.Url property. Since we exposed the HttpContext only as HttpContextBaase using the wrapper everywhere in the app we were able to create a class that inherits HttpContextWrapper and overrides the Request and then returns object that inherits from the RequestWrapper and overrides the Url property. So in the end the application replaced the host in the url that ASP.NET used for the context with the custom host from the custom header the reverse proxy has set. There was no other way from within the app to do that other than manually searching through the code where HttpContext.Request.Url is used and applying fix.