Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/329.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 当每个请求都需要新的授权头时,在web应用程序中重用HttpClient_C#_Asp.net Mvc_Http Headers_Dotnet Httpclient - Fatal编程技术网

C# 当每个请求都需要新的授权头时,在web应用程序中重用HttpClient

C# 当每个请求都需要新的授权头时,在web应用程序中重用HttpClient,c#,asp.net-mvc,http-headers,dotnet-httpclient,C#,Asp.net Mvc,Http Headers,Dotnet Httpclient,我已经读了很多关于如何尽可能多地重用HttpClient实例的文章,甚至可能是在整个应用程序生命周期中。为了完整起见,以下是我的陈述所基于的几个资源: 关于这一点,我有几个问题: 如何在ASP.NET MVC中创建一个应用程序范围的HttpClient实例,以便在所有请求之间共享?让我们假设图片中并没有IoC容器,所以我不能在这里用容器名称将它绑定到Singleton范围中,然后就此结束。我将如何“手动执行” 此外,我正在与之交互的web服务在每个请求上都需要一个新的授权令牌,因此,即

我已经读了很多关于如何尽可能多地重用HttpClient实例的文章,甚至可能是在整个应用程序生命周期中。为了完整起见,以下是我的陈述所基于的几个资源:

关于这一点,我有几个问题:

  • 如何在ASP.NET MVC中创建一个应用程序范围的HttpClient实例,以便在所有请求之间共享?让我们假设图片中并没有IoC容器,所以我不能在这里用容器名称将它绑定到Singleton范围中,然后就此结束。我将如何“手动执行”
  • 此外,我正在与之交互的web服务在每个请求上都需要一个新的授权令牌,因此,即使我想出了一种方法来执行上面的#1,我如何在每个请求上提供一个新的授权头,以便它不会与潜在的多个并发请求(来自不同的用户等等)发生冲突?我的理解是HttpClient本身是线程安全的,当涉及到GetAsync和其他方法时,但是设置DefaultAuthorizationHeaders对我来说似乎不是线程安全的,是吗
  • 如何保持它的单元可测试性
  • 到目前为止,我的正在进行的代码是这样的(为了简洁起见,这里采用了某种简化的形式):


    旁注:我在这里使用依赖注入,但不一定是一个IoC容器(出于与本讨论无关的原因)。

    在在线阅读了一些其他内容后,我提出了以下建议:

    public class MyHttpClientWrapper
    {
        private static readonly HttpClient _httpClient;
        private readonly TokenManager _tokenManager;
    
        static MyHttpClientWrapper()
        {
            // Initialize the static http client:
            _httpClient = new HttpClient();
            _httpClient.BaseAddress = new Uri("https://someapp/api/");
            _httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
        }
    
        public HttpServiceClient(TokenManager tokenManager)
        {
            _tokenManager = tokenManager;        
        }
    
        public string GetDataByQuery(string query)
        {
            _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                "amx", _tokenManager.GetNewAuthorizationCode());
    
            var response = _httpClient.GetAsync(query).Result;
            return response.Content.ReadAsStringAsync().Result;
        }
    
    }
    
    唯一的问题是,它不是单元可测试的。我没有办法用一个假的http客户端替换它。我可以将httpClient封装在一个属性中,使其成为非只读的。这样,可以通过属性设置器从单元测试覆盖httpclient。我不确定我是否喜欢这个解决方案

    另一个想法是通过属性对static_httpClient进行延迟初始化,但我不确定这是否更好


    对这两种想法有什么想法吗?还有其他想法吗?

    我决定稍微改变一下,这样我就可以进行单元测试了。我在这里使用属性注入来允许在单元测试中重写Http客户机。但在生产代码中,它只需在第一次访问客户机属性时进行自初始化(惰性)

    public class MyHttpClientWrapper
    {
        private static readonly object ThreadLock = new object();
        private static HttpClient _httpClient;
        private readonly TokenManager _tokenManager;
    
        public Client
        {
            get
            {
                if (_httpClient != null) return _httpClient;
    
                // Initialize http client for the first time, and lock for thread-safety
                lock (ThreadLock)
                {
                    // Double check
                    if (_httpClient != null) return _httpClient;
    
                    _httpClient = new HttpClient();
                    InitClient(_httpClient);
                    return _httpClient;
                }
    
            }
            set
            {
                // primarily used for unit-testing
                _httpClient = value;
                InitClient(_httpClient);
            }
        }
    
        private void InitClient(HttpClient httpClient)
        {
            httpClient.BaseAddress = new Uri("https://someapp/api/");
            httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
        }
    
    
        public HttpServiceClient(TokenManager tokenManager)
        {
            _tokenManager = tokenManager;        
        }
    
        public string GetDataByQuery(string query)
        {
            Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                "amx", _tokenManager.GetNewAuthorizationCode());
    
            var response = _httpClient.GetAsync(query).Result;
            return response.Content.ReadAsStringAsync().Result;
        }
    
    }
    

    假设您的应用程序是异步的,如果多个线程进入GetDataByQuery,并且它们都更改了DefaultRequestHeaders.Authorization,会发生什么情况?我相信您可以创建一个HttpRequestMessage对象,在其上设置授权头,最后调用_httpClient.SendAsync(requestMessage)是的,这一点很好。事实上,我在某一点上也意识到了同样的事情,并开始做你在那里描述的事情。
    public class MyHttpClientWrapper
    {
        private static readonly object ThreadLock = new object();
        private static HttpClient _httpClient;
        private readonly TokenManager _tokenManager;
    
        public Client
        {
            get
            {
                if (_httpClient != null) return _httpClient;
    
                // Initialize http client for the first time, and lock for thread-safety
                lock (ThreadLock)
                {
                    // Double check
                    if (_httpClient != null) return _httpClient;
    
                    _httpClient = new HttpClient();
                    InitClient(_httpClient);
                    return _httpClient;
                }
    
            }
            set
            {
                // primarily used for unit-testing
                _httpClient = value;
                InitClient(_httpClient);
            }
        }
    
        private void InitClient(HttpClient httpClient)
        {
            httpClient.BaseAddress = new Uri("https://someapp/api/");
            httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));
        }
    
    
        public HttpServiceClient(TokenManager tokenManager)
        {
            _tokenManager = tokenManager;        
        }
    
        public string GetDataByQuery(string query)
        {
            Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
                "amx", _tokenManager.GetNewAuthorizationCode());
    
            var response = _httpClient.GetAsync(query).Result;
            return response.Content.ReadAsStringAsync().Result;
        }
    
    }