Asp.net mvc 如何以可测试的方式在操作中获取和设置http头

Asp.net mvc 如何以可测试的方式在操作中获取和设置http头,asp.net-mvc,Asp.net Mvc,我有一个操作返回FileContentResult或NotModifiedResult,这是一个自定义结果类型,返回HTTP 304以指示请求的资源未被修改,如下所示: [ReplaceMissingPicture(Picture = "~/Content/Images/nothumbnail.png", MimeType = "image/png")] public ActionResult Thumbnail(int id) { var item = Service.GetItem(

我有一个操作返回FileContentResult或NotModifiedResult,这是一个自定义结果类型,返回HTTP 304以指示请求的资源未被修改,如下所示:

[ReplaceMissingPicture(Picture = "~/Content/Images/nothumbnail.png", MimeType = "image/png")]
public ActionResult Thumbnail(int id)
{
    var item = Service.GetItem(id);

    var requestTag = Request.Headers["If-None-Match"] ?? string.Empty;
    var tag = Convert.ToBase64String(item.Version.ToArray());

    if (tag == requestTag)
    {
        return new NotModifiedResult();
    }

    if (item.Thumbnail != null)
    {
        var thumbnail = item.Thumbnail.ToArray();
        var mime = item.PictureMime;

        Response.AppendHeader("ETag", tag);

        return File(thumbnail, mime);
    }
    else
    {
        return null;
    }
}
此操作需要访问响应对象,该对象在测试期间当然不存在,因此使此操作不稳定。我可以在它周围添加条件语句,以便它在测试期间运行,但无法测试是否正确设置了头

这个问题的解决方案是什么


仅供参考,如果此操作返回null,ReplaceMissingPicture筛选器将返回一个特定的资源,以便出于同样的原因将MapPath()调用保留在控制器之外。

第一步是创建一个接口,简化您需要的服务:-

  public interface IHeaders
  {
       public string GetRequestHeader(string headerName);
       public void AppendResponseHeader(string headerName, string headerValue);
  }
现在创建一个默认实现:-

 public Headers : IHeaders
 {
       public string GetRequestHeader(string headerName)
       {
            return HttpContext.Current.Request[headerName];
       }
       public void AppendResponseHeader(string headerName, string headerValue)
       {
            HttpContext.Current.Response.AppendHeader(headerName, headerValue);
       } 
 }
现在向控制器添加一个新字段:-

     private IHeaders myHeadersService;
     public MyController(IHeaders headersService) 
     {
         myHeadersService = headersService;
     }
将新构造函数添加到控制器:-

     private IHeaders myHeadersService;
     public MyController(IHeaders headersService) 
     {
         myHeadersService = headersService;
     }
修改或添加默认构造函数:-

    public MyController()
    {
         myHeadersService = new Headers();
    }
现在,在动作代码中使用myHeadersService,而不是响应和请求对象


在您的测试中,创建自己的
IHeaders
接口实现,以模拟/测试操作代码,并在构造控制器时传递该实现。

创建
FileResult
的子类怎么样?比如说
ETagFileResult
——它的
ExecuteResult()中的子类
方法设置
ETag
头,然后默认为基类实现?您可以使用模拟上下文测试该类(就像您使用
NotModifiedResult
时所做的那样),以确保它做的事情是正确的。并消除了控制器测试中的所有复杂问题

如果做不到这一点,可以在测试中在控制器上设置模拟上下文(在实例化类之后,在调用操作方法之前)。例如,见。但这似乎需要更多的工作


(顺便说一句,这里的标签值似乎引用了两次:一次是在设置了
tag
时,另一次是在实际设置标题时……)

这听起来是一个不错的解决方案!除了使用ViewData或任何其他现成的数据收集,这还能给我们带来什么呢?ViewData不是一个好地方,部分原因是它不是类型安全的。另外,我不相信FileContentResult有ViewData。这也是我最初的想法,但我有一个结果“包装器”,它是一个更通用的类,如TagResult。另外,关于引用:是的,这在我的应用程序中已经修复。我将更新代码示例。是的,包装器一点也不坏:你可以在文件结果和常规HTML视图中使用它。那么测试方法中的controllerInstance.HttpContext.Response.Headers[“ETag”]呢?如果你在单元测试期间想要一个HttpContext,你特别需要模拟它。这里的要点是使您的操作方法不依赖于HttpContext。