【问题标题】:How to set custom headers when using IHttpActionResult?使用 IHttpActionResult 时如何设置自定义标头?
【发布时间】:2014-02-21 19:12:54
【问题描述】:

在 ASP.NET Web API 2 中,IHttpActionResult 在简化控制器代码方面提供了很多价值,我不愿意停止使用它,但我遇到了问题。

我需要在传出响应上设置一个 ETag,但我找不到任何可以让我访问响应标头的属性。目前我正在使用来自ApiControllerOk<T>(T content) 辅助方法,它返回一个OkNegotiatedContentResult<T> 对象。这似乎没有暴露任何可以让我修改标题的东西。

我是否遗漏了什么,或者在使用股票 IHttpActionResult 类型时真的没有办法做到这一点?我考虑过一个消息处理程序,但随后我必须弄清楚如何将 ETag 从操作中传递出去(不同操作的 ETag 生成方式不同,因此不必为所有操作创建一个通用处理程序)。

我想避免使用原始 HttpResponseMessage,但目前这看起来很困难。

【问题讨论】:

    标签: c# asp.net asp.net-web-api


    【解决方案1】:

    对于您的方案,您需要创建一个自定义IHttpActionResult。以下是我从OkNegotiatedContentResult<T> 派生的示例,因为它运行Content-Negotiation 并设置Ok 状态代码。

    public class CustomOkResult<T> : OkNegotiatedContentResult<T>
    {
        public CustomOkResult(T content, ApiController controller)
            : base(content, controller) { }
    
        public CustomOkResult(T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters) 
            : base(content, contentNegotiator, request, formatters) { }
    
        public string ETagValue { get; set; }
    
        public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            HttpResponseMessage response = await base.ExecuteAsync(cancellationToken);
    
            response.Headers.ETag = new EntityTagHeaderValue(this.ETagValue);
    
            return response;
        }        
    }
    

    控制器

    public class ValuesController : ApiController
    {
        public IHttpActionResult Get()
        {
            return new CustomOkResult<string>(content: "Hello World!", controller: this)
                {
                        ETagValue = "You ETag value"
                };
        }
    }
    

    请注意,您也可以从 NegotiatedContentResult&lt;T&gt; 派生,在这种情况下,您需要自己提供 StatusCode。希望这会有所帮助。

    你可以找到OkNegotiatedContentResult&lt;T&gt;NegotiatedContentResult&lt;T&gt;的源代码,其实很简单。

    【讨论】:

    • 我正在考虑另辟蹊径,因为我不想扩展所有内置响应。我目前的想法是拥有一个HttpResponseHeaders 字典,我将它合并到消息处理程序中来自 IHttpActionResult 的字典中。除了响应标头没有构造函数之外,这似乎可行。
    • 您可以查看我对其他评论的回复。你的方法不正确。
    • 在这个例子中我一直得到“406 Not Acceptable”,直到我用引号括起来:即用string.Format("\"{0}\"", this.ETagValue)替换this.ETagValue。实际上,我在尝试添加内容类型参数时偶然发现了这个解决方案(profile="http:/..." - 必须引用 href。)
    • 正确答案是使用ResponseMessageResult在这里找到:stackoverflow.com/a/41459475/1644019
    【解决方案2】:

    您可以创建HttpResponseMessage,根据需要添加标题,然后从中创建ResponseMessageResult

    HttpResponseMessage response =new HttpResponseMessage(HttpStatusCode.OK);
    response.Headers.Add("MyHeader", "MyHeaderValue");
    return ResponseMessage(response);
    

    【讨论】:

    • 发帖人已明确要求使用 IHttpActionResult 的解决方案
    • @AdrianHope-Bailie 这是一个 X/Y 问题,任何人 试图按照发帖人的要求做都是错误的。此解决方案是框架编写者希望您对响应进行细粒度控制的预期解决方案。
    【解决方案3】:

    这是我没有 ActionFilterAttributes 的简单实现,类似于 AlexACD 的响应。我的解决方案使用了实现 IHttpActionResult 接口的 ResponseMessageResult。

    HttpResponseMessage responseMessage = new HttpResponseMessage(HttpStatusCode.OK);
    responseMessage.Headers.Add("Headername", "Value");
    ResponseMessageResult response = new ResponseMessageResult(responseMessage);
    return response;
    

    【讨论】:

    • ResponseMessage 返回ResponseMessageResult
    【解决方案4】:
    public static class HttpExtentions
    {
        public static IHttpActionResult AddHeader(this IHttpActionResult action,
            string headerName, IEnumerable<string> headerValues)
        {
            return new HeaderActionResult(action, headerName, headerValues);
        }
    
        public static IHttpActionResult AddHeader(this IHttpActionResult action,
            string headerName, string header)
        {
            return AddHeader(action, headerName, new[] {header});
        }
    
        private class HeaderActionResult : IHttpActionResult
        {
            private readonly IHttpActionResult action;
    
            private readonly Tuple<string, IEnumerable<string>> header;
    
            public HeaderActionResult(IHttpActionResult action, string headerName,
                IEnumerable<string> headerValues)
            {
                this.action = action;
    
                header = Tuple.Create(headerName, headerValues);
            }
    
            public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
            {
                var response = await action.ExecuteAsync(cancellationToken);
    
                response.Headers.Add(header.Item1, header.Item2);
    
                return response;
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      很老的问题。当时可能其他答案是有意义的,但今天您可以简单地添加这一行,而无需更改或扩展IHttpActionResult。它完美地在您的响应中添加了标题。确保还格式化为RFC 1123 标准,如下所示。否则,尽管 Last-Modified 出现在标题中,但客户端无法使用 HttpClient 读取它。

          System.Web.HttpContext.Current.Response.Headers.
      Add(Microsoft.Net.Http.Headers.HeaderNames.LastModified, DBdateModified.Value.ToString("r"));
      

      【讨论】:

        【解决方案6】:

        这可以通过一个 ActionFilterAttribute 来实现,它会在控制器功能之后但在响应之前检查响应,然后您可以在控制器方法上设置属性以添加此信息,下面是我的实现:

        public class EnableETag : ActionFilterAttribute
        {
        
            /// <summary>
            /// NOTE: a real production situation, especially when it involves a web garden
            ///       or a web farm deployment, the tags must be retrieved from the database or some other place common to all servers.
            /// </summary>
            private static ConcurrentDictionary<string, EntityTagHeaderValue> etags = new ConcurrentDictionary<string, EntityTagHeaderValue>();
        
            public override void OnActionExecuting(HttpActionContext context)
            {
                var request = context.Request;
                if (request.Method == HttpMethod.Get)
                {
                    var key = GetKey(request);
                    ICollection<EntityTagHeaderValue> etagsFromClient = request.Headers.IfNoneMatch;
                    if (etagsFromClient.Count > 0)
                    {
                        EntityTagHeaderValue etag = null;
                        if (etags.TryGetValue(key, out etag) && etagsFromClient.Any(t => t.Tag == etag.Tag))
                        {
                            context.Response = new HttpResponseMessage(HttpStatusCode.NotModified);
                            SetCacheControl(context.Response);
                        }
                    }
                }
            }
            public override void OnActionExecuted(HttpActionExecutedContext context)
            {
                var request = context.Request;
                var key = GetKey(request);
                EntityTagHeaderValue etag;
                if (!etags.TryGetValue(key, out etag) || request.Method == HttpMethod.Put ||
                request.Method == HttpMethod.Post)
                {
                    etag = new EntityTagHeaderValue("\"" + Guid.NewGuid().ToString() + "\"");
                    etags.AddOrUpdate(key, etag, (k, val) => etag);
                }
                context.Response.Headers.ETag = etag;
                SetCacheControl(context.Response);
            }
            private string GetKey(HttpRequestMessage request)
            {
                return request.RequestUri.ToString();
            }
        
            /// <summary>
            /// Defines the time period to hold item in cache (currently 10 seconds)
            /// </summary>
            /// <param name="response"></param>
            private void SetCacheControl(HttpResponseMessage response)
            {
                response.Headers.CacheControl = new CacheControlHeaderValue()
                {
                    MaxAge = TimeSpan.FromSeconds(10),
                    MustRevalidate = true,
                    Private = true
                };
            }
        }
        

        }

        【讨论】:

          【解决方案7】:

          这是我在我的通用 Web API 2 库代码中使用的一个解决方案,它可以轻松支持设置任何标头 - 或在 ExecuteAsync 中提供的 HttpResponseMessage 上的任何其他属性 - 而不会绑定到任何特定的派生 @ 987654323@实现:

          public class FlexibleNegotiatedContentResult<T> : NegotiatedContentResult<T>
          {
              private readonly Action<HttpResponseMessage> _responseMessageDelegate;
          
              public FlexibleNegotiatedContentResult(HttpStatusCode statusCode, T content, IContentNegotiator contentNegotiator, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
                  : base(statusCode, content, contentNegotiator, request, formatters)
              {
              }
          
              public FlexibleNegotiatedContentResult(HttpStatusCode statusCode, T content, ApiController controller, Action<HttpResponseMessage> responseMessageDelegate = null)
                  : base(statusCode, content, controller)
              {
                  _responseMessageDelegate = responseMessageDelegate;
              }
          
              public override async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
              {
                  HttpResponseMessage responseMessage = await base.ExecuteAsync(cancellationToken);
          
                  if (_responseMessageDelegate != null)
                  {
                      _responseMessageDelegate(responseMessage);
                  }
          
                  return responseMessage;
              }
          }
          

          以及一个示例用法:

          new FlexibleNegotiatedContentResult<string>(HttpStatusCode.Created, "Entity created!", controller, response => response.Headers.Location = new Uri("https://myapp.com/api/entity/1"));
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-08-28
            • 1970-01-01
            • 1970-01-01
            • 2021-10-02
            • 2012-01-18
            • 2012-07-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多