【问题标题】:How to get and set http headers in an Action, the testable way如何在 Action 中获取和设置 http 标头,可测试的方式
【发布时间】:2010-11-29 13:43:02
【问题描述】:

我有一个返回 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;
    }
}

此操作需要访问 Response 对象,该对象在测试期间当然不存在,因此使此操作无法测试。我可以在它周围添加条件语句,以便它在测试期间运行,但是我无法测试是否正确设置了标头。

有什么办法可以解决这个问题?

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

【问题讨论】:

  • 测试方法中的controllerInstance.HttpContext.Response.Headers["ETag"]怎么样?
  • 如果您在单元测试期间想要一个 HttpContext,您必须特别模拟它。这里的重点是使您的操作方法不依赖于 HttpContext。

标签: asp.net-mvc


【解决方案1】:

如何创建FileResult 的子类--比如说ETagFileResult-- 在其ExecuteResult() 方法中设置ETag 标头,然后默认为基类实现?您可以使用模拟上下文测试该类(就像您使用 NotModifiedResult 一样)以确保它在做正确的事情。并从控制器的测试中消除整个复杂性。

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

(另外,顺便说一句,您似乎在此处引用了两次标记值:一次是在设置 tag 时,另一次是在您实际设置标题时......)

【讨论】:

  • 这也是我最初的想法,但是有一个结果“包装器”,它是一个更通用的类,比如 TagResult
  • 另外,关于引用:是的,这已在我的应用程序中修复。我将更新代码示例。泰。
  • 是的,包装器一点也不坏:您可以将它用于文件结果和常规 HTML 视图。
【解决方案2】:

第一步是创建一个界面来简化您需要的服务:-

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

修改或添加默认构造函数:-

    public MyController()
    {
         myHeadersService = new Headers();
    }

现在在您的 Action 代码中使用 myHeadersService 而不是 Response 和 Request 对象。

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

【讨论】:

  • 这听起来是个不错的解决方案!
  • 这比使用 ViewData 或任何其他现成的数据收集能给我们带来什么?
  • ViewData 不是一个好地方,部分原因是它不是类型安全的。另外,我根本不相信 FileContentResult 有 ViewData。
猜你喜欢
  • 2016-03-11
  • 2014-12-20
  • 2013-05-01
  • 2016-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多