如何在 Web API 消息处理程序中提取自定义标头值?

Posted

技术标签:

【中文标题】如何在 Web API 消息处理程序中提取自定义标头值?【英文标题】:How to extract custom header value in Web API message handler? 【发布时间】: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'”。 【参考方案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 那么您应该使用另一个答案,例如***.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;
        ;

【讨论】:

以上是关于如何在 Web API 消息处理程序中提取自定义标头值?的主要内容,如果未能解决你的问题,请参考以下文章

ASP.NET Web API 消息处理程序

web api - 消息处理程序属性路由

如何在 Web API Post 方法中返回自定义消息

如何在 dotnet core Web Api 中返回自定义错误消息

Web Api 跨域基础认证

讨论:动态链接库如何给主程序发送自定义消息?