Hello World
Spiga

为什么是HttpContextBase而不是IHttpContext

2009-08-21 15:15 by 老赵, 6220 visits

我们知道,由于HttpContext很难进行Mock,因此为了提高可测试性,微软随ASP.NET MVC发布了一个“抽象包”,专门用于对HttpContext及其相关组件进行抽象。不过在Preview 1版本中,这些抽象都是一个个接口,如IHttpContext,IHttpRequest等等。而在下一个版本中,立即就成为了一个个抽象类,如HttpContextBase,HttpRequestBase。从命名上来说,我喜欢接口的命名方式,清晰而好用。而在自己的项目中,我也倾向于使用接口而不是抽象类。但是对于ASP.NET Abstractions中的这些抽象类型,我倒觉得“的确应该如此”。除了在公开API中应该谨慎地使用接口这一原因(而对于HttpContext这种拥有数十个成员的类型更不应该使用接口),有些朋友也提到HttpContext是一个实际的概念,而接口表示的是“can do”,因此应该使用抽象类。不过,现在我打算从“使用”角度来谈一下,为什么这里的确应该用抽象类而不是接口。

关于这点,我们应该直接来看HttpContextBase这个抽象类的写法:

public abstract class HttpContextBase : IServiceProvider
{
    protected HttpContextBase() { }

    public virtual void AddError(Exception errorInfo)
    {
        throw new NotImplementedException();
    }

    public virtual void ClearError()
    {
        throw new NotImplementedException();
    }

    ...
}

我们可以看到,虽然HttpContextBase是抽象类,但是其中没有任何一个成员是抽象的,只不过每个成员都会直接抛出NotImplementedException。为什么这样?

这是由这个类的作用决定的。ASP.NET Abstractions中的类都是为了提高“可测试性”,而是用手段就是提供一个“抽象”以便创建Mock对象。一个Mock对象的作用,简单地说便是模拟真实对象的行为,然后交给被测试功能使用,以此判断被测试功能是否正确。例如,我们使用Moq框架可以轻松地创建HttpContext的Mock对象:

var mockContext = new Mock<HttpContextBase>();
mockContext.Setup(c => c.Request.Form).Returns(new NameValueCollection());
DoSomething(mockContext.Object);

由于DoSomething基于HttpContext的抽象HttpContextBase编写,因此我们可以准备一个Mock对象,并使它的Request.Form属性返回我们需要的内容,这样DoSomething方法内部就可以访问到我们所期望的值了。但实际上,Moq等Mock框架的工作方式,都是动态生成一些类型,继承抽象类(或实现接口),并根据我们的“设置”来实现一些成员。因此,它们基本上都要对被Mock的对象有一定要求,例如不允许是一个密闭(sealed)类,而被操作的成员也必须是abstract或virtual的,因为只有这样,动态类型才能替换掉这些成员的实现1

显然,HttpContextBase满足这个要求。事实上,Mock框架只是帮我们省去了编写下面这些代码的负担(当然写法可以不同):

public class MockHttpContext : HttpContextBase
{
    public override HttpRequestBase Request
    {
        get
        {
            return this.MockRequest;
        }
    }

    public HttpRequestBase MockRequest { get; set; }
}

public class MockHttpRequest : HttpRequestBase
{
    public override NameValueCollection Form
    {
        get
        {
            return this.MockForm;
        }
    }

    public NameValueCollection MockForm { get; set; }
}

试想,如果HttpContextBase是IHttpContext接口怎么办?没错,除了我们需要的成员之外,我们还必须准备一大堆我们不需要的成员的“空架子”——同样道理,如果拥有抽象的成员也会遇到这个情况。因此,虽然HttpContextBase是抽象类,但是没有任何一个抽象成员。这意味着当我们需要手动实现Mock对象的时候,只需要覆盖我们的目标成员就可以了。

Pragmatic Programmer》告诉我们:“不要基于假设编程”,因此在设计HttpContext抽象的时候,也不应该假设“Mock框架在任何情况下都是最好的选择”。因此,我认为HttpContextBase是非常合适的。

 

注1:使用CLR Profiler类似机制的Mock框架TypeMock不会收到这个约束,它可以任意操作密闭类或方法——只可惜它是收费框架。不过,如果Moq或Rhino Mocks等“普通”Mock框架无法满足您需求的话,这基本上可以算是您的设计问题:缺少可测试性。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

20 条回复

  1. 老赵
    admin
    链接

    老赵 2009-08-21 15:15:00

    忽然想到,随手记录,耗时40分钟。

  2. 9haowan.com[未注册用户]
    *.*.*.*
    链接

    9haowan.com[未注册用户] 2009-08-21 15:22:00

    沙发

  3. 路过2009[未注册用户]
    *.*.*.*
    链接

    路过2009[未注册用户] 2009-08-21 15:23:00

    老赵是不是MVP要RENEW了,最近这么高产

  4. kokyu
    *.*.*.*
    链接

    kokyu 2009-08-21 15:24:00

    sofa!!!

  5. Kevin Dai
    *.*.*.*
    链接

    Kevin Dai 2009-08-21 15:25:00

    var mockContext = new Mock<HttpRequestBase>();
    mockContext.Setup(c => c.Request.Form).Returns(new NameValueCollection());
    DoSomething(mockContext.Object);
    


    第一条语句是不是应该为
    var mockContext = new Mock<HttpContextBase>();
    

  6. 老赵
    admin
    链接

    老赵 2009-08-21 15:28:00

    @Kevin Dai
    谢谢提醒,写太快了,呵呵。

  7. 老赵
    admin
    链接

    老赵 2009-08-21 15:28:00

    路过2009:老赵是不是MVP要RENEW了,最近这么高产


    刚renew两个月不到,谢谢……

  8. 别爱上哥,哥只是个传说!
    *.*.*.*
    链接

    别爱上哥,哥只是个传说! 2009-08-21 15:45:00

    头像还没换!哇晒!

  9. 温景良(Jason)
    *.*.*.*
    链接

    温景良(Jason) 2009-08-21 15:55:00

    不要基于假设编程,这话好

  10. dongdeliao[未注册用户]
    *.*.*.*
    链接

    dongdeliao[未注册用户] 2009-08-21 16:42:00

    真能写 会军大哥最近怎么没有动静呢?

  11. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-08-21 17:29:00

    TL他这几个月在开心王注册了。。。难道沉迷。。。

  12. 铁打的西西。
    *.*.*.*
    链接

    铁打的西西。 2009-08-21 18:01:00

    怎样才能看到类库中方法的实现啊

  13. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-08-21 18:32:00

    其实微软的风格也是在不断的完善,在以前的.NET Framework中就很难看到这样的设计,抽象基类总也要实现几个方法。总的来说我喜欢微软和.NET的这种实用至上的风格,Java的风格总感觉很教条。

  14. CCAV[未注册用户]
    *.*.*.*
    链接

    CCAV[未注册用户] 2009-08-22 01:20:00

    向老赵致敬!

  15. 王德水
    *.*.*.*
    链接

    王德水 2009-08-22 14:35:00

    跟着致敬,高产好呀,注意身体,趁没结婚,多写点,哈哈

  16. CoderZh
    *.*.*.*
    链接

    CoderZh 2009-08-23 10:12:00

    为了Mock方便而使用抽象类在一定程度上说的过去,但我觉得理由也不是特别充分

  17. 老赵
    admin
    链接

    老赵 2009-08-23 13:20:00

    @CoderZh
    具体说说?

  18. Xuefly
    *.*.*.*
    链接

    Xuefly 2009-08-23 21:50:00

    微软把接口改为了抽象类,有没有想要重构ASP.NET的意思,会不会将来WebForm和ASP.NET MVC都用HttpContextBase?

  19. 老赵
    admin
    链接

    老赵 2009-08-23 22:59:00

    @Xuefly
    我认为一定会越来越倾向于使用HttpContextBase的,但是已有的内容是不会修改了,因为要兼容。

  20. Ivony...
    *.*.*.*
    链接

    Ivony... 2009-08-24 13:31:00

    Xuefly:微软把接口改为了抽象类,有没有想要重构ASP.NET的意思,会不会将来WebForm和ASP.NET MVC都用HttpContextBase?



    这不太现实。。。。
    ASP.NET到2.0后已经是一个成熟的体系,体系成熟后再做大手术的可能性是不大的,但我们将来可能可以看到更多的这种在它外围的修补。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我