15

我理解 和 之间的关系HttpPostedFileBaseHttpPostedFileWrapper就两者的需求而言(即在单元测试/模拟中)。但是为什么,当我在 return 上设置一个断点时HttpPostedFileBase,它会显示为HttpPostedFileWrapper吗?

此外,HttpPostedFileBase不实现 ContentType 属性。那么为什么当我的代码引用HttpPostedFileBase而不是引用时它会返回一个值HttpPostedFileWrapper呢?这是什么诡计?

在此处输入图像描述

编辑#1:

感谢@lawliet29 的精彩回复。我已经按照建议写出了结构。

public sealed class MyHttpPostedFile
{
    public string ContentType { get { return "123"; } }
}

public abstract class MyHttpPostedFileBase
{
}

public class MyHttpPostedFileWrapper : MyHttpPostedFileBase
{
    private MyHttpPostedFile _myHttpPostedFile;    

    public MyHttpPostedFileWrapper(MyHttpPostedFile myHttpPostedFile) { 
        _myHttpPostedFile = myHttpPostedFile;
    }

    public string ContentType { get { return _myHttpPostedFile.ContentType; } }
}

但是,为了使它起作用,我需要像这样传递参数:

GetFiles(new MyHttpPostedFileWrapper(new MyHttpPostedFile());

这似乎是我所质疑的诡计所在。.NET 如何知道传递给它的字节是一个类型的类,MyHttpPostedFile并且它应该接受该对象并将其作为参数传递给我的构造函数?

编辑#2:

我没有意识到 ASP.NET MVC 绑定器不仅仅通过传递这些更高级别的对象来传递字节。这是我想知道的诡计!感谢您的好评。

4

1 回答 1

21

其实很简单。HttpPostedFileBase 是一个抽象类,仅用于派生目的。使用它是为了使密封类 HttpPostedFile 中的某些东西是可模拟的。

然而,在现实生活中,HttpPostedFile 是您处理发布文件的工具,为了保持一致,创建了 HttpPostedFileWrapper。此类通过包装 HttpPostedFile 为 HttpPostedFileBase 提供实现。

所以HttpPostedFileBase是一个统一的抽象,HttpPostedFile是一个代表发布文件的类,HttpPostedFileWrapper是包装HttpPostedFile的HttpPostedFileBase的实现。HttpPostedFileWrapper 的 ContentType 属性的实现从底层 HttpPostedFile 读取内容类型。

编辑:某种解释

ASP.NET MVC 接收到一个文件,并且在它下面的某个地方创建了一个 HttpPostedFile 实例,因为这是自 .NET Framework 1.0 以来的工作方式。HttpPostedFile 的定义如下所示:

public sealed class HttpPostedFile

这基本上意味着它不能被继承,也不能被模拟以进行单元测试。

为了解决这个问题,ASP.NET MVC 开发人员创建了一个可模拟的抽象——HttpPostedFileBase,它的定义如下:

public abstract class HttpPostedFileBase

所以现在,您可以定义您的 MVC 操作,以便它们接受 HttpPostedFileBase 而不是不可模拟的 HttpPostedFile:

[HttpPost]
public ActionResult PostFile(HttpPostedFileBase file)
{
    // some logic here...
}

问题是,在下面的某个地方,表示已发布文件的唯一方法是旧的刚性 HttpPostedFile。因此,为了支持这种抽象,MVC 开发人员创建了一个名为 HttpPostedFileWrapper 的装饰器,大致如下所示:

public class HttpPostedFileWrapper : HttpPostedFileBase
{
    private HttpPostedFile _httpPostedFile;    

    public HttpPostedFileWrapper(HttpPostedFile httpPostedFile) { 
        _httpPostedFile = httpPostedFile;
    }

    public string ContentType { get { return _httpPostedFile.ContentType; } }

    // implementation of other HttpPostedFileBase members
}

所以现在 HttpPostedFileWrapper 是您在使用发布的文件执行真正的 HTTP POST 请求时实际得到的。由于多态性,您可以将派生类的实例 - HttpPostedFileWrapper - 传递给接受基类的方法 - HttpPostedFileBase。

一直以来,您都可以创建自己的模拟实现,例如,看起来像正在发布的视频文件。你会这样做

public class MockPostedVideoFile : HttpPostedFileBase
{
    public string ContentType { get { return "video/mp4"; } }

    // rest of implementation here
}

另一个编辑: HttpPostedFile 的实际实例化全部由 System.Web 为您处理。ASP.NET MVC binder 对发布的表单数据非常智能。它会自动检测某些 post 值实际上是文件的字节,因此为了正确表示它们,它可以使用 System.Web 框架中的旧东西来创建实例 HttpPostedFile。

重点是 - 你不需要担心它。这里有很多幕后发生的事情,我们真的需要感谢 ASP.NET MVC 团队抽象出所有这些低级的事情。

您唯一需要担心的地方是单元测试。在您的测试中,您可以使用模拟实现来调用您的操作,如下所示:

myController.PostFile(new MockPostedVideoFile())
于 2014-07-23T13:02:06.877 回答