【问题标题】:How to extract custom header value in Web API message handler?如何在 Web API 消息处理程序中提取自定义标头值?
【发布时间】:2013-02-04 17:20:39
【问题描述】:

我目前在我的 Web API 服务中有一个消息处理程序,它会覆盖“SendAsync”,如下所示:

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
  //implementation
}

在此代码中,我需要检查一个名为 MyCustomID 的自定义添加请求标头值。问题是当我执行以下操作时:

if (request.Headers.Contains("MyCustomID"))  //OK
    var id = request.Headers["MyCustomID"];  //build error - not OK

...我收到以下错误消息:

无法将 [] 索引应用于类型表达式 'System.Net.Http.Headers.HttpRequestHeaders'

如何通过传递给此重写方法的HttpRequestMessage (MSDN Documentation) 实例访问单个 自定义请求标头?

【问题讨论】:

  • 如果你使用request.Headers.Get("MyCustomID");会发生什么?
  • 没有Get' on the HttpRequestHeaders` 类型。产生消息:“无法解析符号'Get'”。

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


【解决方案1】:

试试这样的:

IEnumerable<string> headerValues = request.Headers.GetValues("MyCustomID");
var id = headerValues.FirstOrDefault();

还有一个 TryGetValues 方法可以用于标头,如果您不能始终保证可以访问标头。

【讨论】:

  • GetValues 的 null 检查不提供任何值,因为它永远不会返回 null。如果标头不存在,您将收到 InvalidOperationException。如果请求中可能不存在标头,则需要使用 TryGetHeaders 并检查错误响应或尝试/捕获 GetValues 调用(不推荐)。
  • @Drew request.Headers.Single(h => h.Key == "授权");更少的代码做同样的事情!
  • 为什么不只是var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
  • @SaeedNamati 因为标头值不是一对一的。您可以在同一请求中拥有 Some-Header: oneSome-Header: two。有些语言会默默地丢弃“一”,但这是不正确的。它在 RFC 中,但我现在懒得找。
  • Saeed 的观点是正确的,可用性很重要,这里最常见的用例是检索请求标头的一个值。您仍然可以使用 GetValues 操作来检索请求标头的多个值(人们会使用),但 99% 的时间他们只想检索特定请求标头的一个值,而这应该是一个班轮。
【解决方案2】:

如果密钥不存在,则在throws exception 下面的行。

IEnumerable<string> headerValues = request.Headers.GetValues("MyCustomID");

使用 TryGetValues 的安全解决方案:

包括 System.Linq;

IEnumerable<string> headerValues;
var userId = string.Empty;

     if (request.Headers.TryGetValues("MyCustomID", out headerValues))
     {
         userId = headerValues.FirstOrDefault();
     }           

【讨论】:

    【解决方案3】:

    为了扩展 Youssef 的回答,我基于 Drew 对不存在标头的担忧编写了此方法,因为我在单元测试期间遇到了这种情况。

    private T GetFirstHeaderValueOrDefault<T>(string headerKey, 
       Func<HttpRequestMessage, string> defaultValue, 
       Func<string,T> valueTransform)
        {
            IEnumerable<string> headerValues;
            HttpRequestMessage message = Request ?? new HttpRequestMessage();
            if (!message.Headers.TryGetValues(headerKey, out headerValues))
                return valueTransform(defaultValue(message));
            string firstHeaderValue = headerValues.FirstOrDefault() ?? defaultValue(message);
            return valueTransform(firstHeaderValue);
        }
    

    这是一个示例用法:

    GetFirstHeaderValueOrDefault("X-MyGuid", h => Guid.NewGuid().ToString(), Guid.Parse);
    

    还可以查看@doguhan-uluca 的答案以获得更通用的解决方案。

    【讨论】:

    • FuncAction 是内置于 .NET 3.5 及更高版本的通用委托签名结构。我很乐意讨论有关该方法的具体问题,但我建议先了解这些问题。
    • @neontapir(和其他人)第二个参数用于在未找到密钥时提供默认值。第三个参数用于将返回值“转换”为所需类型,该类型还指定要返回的类型。根据示例,如果未找到“X-MyGuid”,则参数 2 lambda 基本上将默认 guid 作为字符串提供(因为它会从 Header 中检索到),并且 Guid.Parse 第三个参数将转换找到的或默认的字符串值到一个 GUID。
    • @neontapir 这个函数的请求来自哪里? (如果它为空,新的 HttpRequestMessage() 将如何具有任何标头?如果 Request 为空,仅返回默认值是否有意义?
    • 已经两年了,但如果我记得,一个新的HttpRequestMessage 被初始化为一个空的 Headers 集合,它不为空。如果请求为空,此函数最终会返回默认值。
    • @mendel,neontapir 我已经尝试使用上面的 sn-p,我相信方法主体第 2 行的“请求”应该是包含该方法的类中的私有字段,或者是作为参数(HttpRequestMessage 类型)传递给方法
    【解决方案4】:

    创建一个新方法 - '返回一个单独的 HTTP Header 值',并在每次需要从 HttpRequestMessage 访问多个键值时使用键值调用此方法。

    public static string GetHeader(this HttpRequestMessage request, string key)
            {
                IEnumerable<string> keys = null;
                if (!request.Headers.TryGetValues(key, out keys))
                    return null;
    
                return keys.First();
            }
    

    【讨论】:

    • 如果 MyCustomID 不是请求的一部分怎么办......它返回 null 异常。
    • @PrasadKanaparthi,TryGetValues 是安全的
    【解决方案5】:

    为了进一步扩展 @neontapir 的解决方案,这里有一个更通用的解决方案,它可以同样适用于 HttpRequestMessage 或 HttpResponseMessage,并且不需要手动编码的表达式或函数。

    using System.Net.Http;
    using System.Collections.Generic;
    using System.Linq;
    
    public static class HttpResponseMessageExtensions
    {
        public static T GetFirstHeaderValueOrDefault<T>(
            this HttpResponseMessage response,
            string headerKey)
        {
            var toReturn = default(T);
    
            IEnumerable<string> headerValues;
    
            if (response.Content.Headers.TryGetValues(headerKey, out headerValues))
            {
                var valueString = headerValues.FirstOrDefault();
                if (valueString != null)
                {
                    return (T)Convert.ChangeType(valueString, typeof(T));
                }
            }
    
            return toReturn;
        }
    }
    

    示例用法:

    var myValue = response.GetFirstHeaderValueOrDefault<int>("MyValue");
    

    【讨论】:

    • 看起来不错,但是GetFirstHeaderValueOrDefault 有两个参数,所以它在调用示例用法时抱怨缺少参数var myValue = response.GetFirstHeaderValueOrDefault&lt;int&gt;("MyValue"); 我错过了什么吗?
    • 添加了新的静态类,替换了请求的响应。从 API 控制器调用,因为 var myValue = myNameSpace.HttpRequestMessageExtension.GetFirstHeaderValueOrDefault&lt;int&gt;("productID"); 得到 没有给定的参数对应于 'HttpRequestMessageExtension.GetFirstHeaderValueOrDefault(HttpRequestMessage, string)' 的所需形式参数 'headerKey'
    • @Jeb50 您是否在尝试使用此扩展程序的文件上声明 using HttpResponseMessageExtensions
    【解决方案6】:

    对于 ASP.NET,您可以使用 this simple library/package 直接从控制器方法中的参数获取标头。它提供了一个 [FromHeader] 属性,就像您在 ASP.NET Core 中一样:)。例如:

        ...
        using RazHeaderAttribute.Attributes;
    
        [Route("api/{controller}")]
        public class RandomController : ApiController 
        {
            ...
            // GET api/random
            [HttpGet]
            public IEnumerable<string> Get([FromHeader("pages")] int page, [FromHeader] string rows)
            {
                // Print in the debug window to be sure our bound stuff are passed :)
                Debug.WriteLine($"Rows {rows}, Page {page}");
                ...
            }
        }
    

    【讨论】:

      【解决方案7】:

      一行解决方案(假设表头存在)

      var id = request.Headers.GetValues("MyCustomID").FirstOrDefault();
      

      【讨论】:

      • 如果 MyCustomID 不是请求的一部分怎么办......它返回 null 异常。
      • @PrasadKanaparthi 那么您应该使用另一个答案,例如stackoverflow.com/a/25640256/4275342。你看到没有任何空检查,那么requestnull 是什么?这也是可能的。或者如果MyCustomID 是一个空字符串或不等于foo 怎么办?这取决于上下文,所以这个答案只是描述了方式,以及您需要自己添加的所有验证和业务逻辑
      • 您不同意代码正在运行并且可以返回标头值吗?
      • 它工作正常..如果“MyCustomID”是请求请求的一部分。是的,所有验证都需要注意
      • @PrasadKanaparthi,如果标头不存在,您将收到 InvalidOperationException,而不是 null
      【解决方案8】:

      对于 ASP.Net Core,如果想直接在控制器方法中使用参数,有一个简单的解决方案:使用 [FromHeader] 注释。

              public JsonResult SendAsync([FromHeader] string myParam)
              {
              if(myParam == null)  //Param not set in request header
              {
                 return null;
              }
              return doSomething();
          }   
      

      附加信息:在我的情况下,“myParam”必须是一个字符串,int 始终为 0。

      【讨论】:

        【解决方案9】:
        request.Headers.FirstOrDefault( x => x.Key == "MyCustomID" ).Value.FirstOrDefault()
        

        现代变体:)

        【讨论】:

        • 如果 MyCustomID 不是请求的一部分怎么办......它返回 null 异常。
        【解决方案10】:

        这听起来很明显,但请确保您正在读取标头的控制器是请求通过的第一个控制器。

        我有两个相互通信的 WebAPI 项目。第一个是代理,第二个包含逻辑。愚蠢的我,我尝试在第二个控制器中读取自定义标题,而不是第一个。

        【讨论】:

          【解决方案11】:
          var token = string.Empty;
          if (Request.Headers.TryGetValue("MyKey",  out headerValues))
          {
              token = headerValues.FirstOrDefault();
          }
          

          【讨论】:

            【解决方案12】:

            另一种方法

             string customHeader = string.Empty;
                    if (Request.Headers.TryGetValue("x-date", out var xdateValue))
                    {
                        customHeader = xdateValue;
                    };
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-08-03
              • 1970-01-01
              • 2016-12-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-03-14
              相关资源
              最近更新 更多