You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# HttpClient随机抛出System.NullReferenceException问题求助

排查HttpClient随机抛出NullReferenceException的问题

IDE中的错误视图

错误详情:

System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=System.Net.Http
  StackTrace:
   at System.Net.Http.HttpConnection.WriteAsciiString(String s)
   at System.Net.Http.HttpConnection.WriteHeaderCollection(HttpHeaders headers, String cookiesFromContainer)
   at System.Net.Http.HttpConnection.WriteHeaders(HttpRequestMessage request, HttpMethod normalizedMethod)
   at System.Net.Http.HttpConnection.<SendAsync>d__57.MoveNext()
   at System.Net.Http.HttpConnection.<SendAsync>d__57.MoveNext()
   at System.Net.Http.HttpConnectionPool.<SendWithVersionDetectionAndRetryAsync>d__89.MoveNext()
   at System.Net.Http.DiagnosticsHandler.<SendAsyncCore>d__10.MoveNext()
   at System.Net.Http.Metrics.MetricsHandler.<SendAsyncWithMetrics>d__5.MoveNext()
   at System.Net.Http.RedirectHandler.<SendAsync>d__4.MoveNext()
   at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.<<SendCoreAsync>g__Core|5_0>d.MoveNext()
   at Microsoft.Extensions.ServiceDiscovery.Http.ResolvingHttpDelegatingHandler.<SendAsync>d__4.MoveNext()
   at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.<<SendCoreAsync>g__Core|5_0>d.MoveNext()
   at System.Net.Http.HttpClient.<<SendAsync>g__Core|83_0>d.MoveNext()
   at MyApiService.Clients.Party3rdClient.<GetAuthentication>d__7.MoveNext() in Party3rdClient.cs:

这个问题随机出现,且在其他HttpClient调用中也会发生。我通过依赖注入使用HttpClient,不确定是否注入方式有误(本人刚接触C#)。以下是相关代码:

Program.cs

builder.Services.Configure<Party3rdSettings>(builder.Configuration.GetSection("Party3rdSettings"));
builder.Services.AddTransient<IParty3rdClient, Party3rdClient>();
builder.Services.AddHttpClient<Party3rdClient>(client =>
{
    client.Timeout = TimeSpan.FromMinutes(5);
});

IParty3rdClient.cs

namespace MyApiService.Clients.Interfaces
{
    public interface IParty3rdClient
    {
        public Task<string> GetAuthentication(AuthFields authFields);
    }
}

Part3rdClient.cs

namespace MyApiService.Clients
{
    public class Part3rdClient: IPart3rdClient
    {
        private readonly Party3rdSettings _party3rdSettings;
        private readonly HttpClient _client;
        private static string Authentication = string.Empty;

        private static readonly JsonSerializerOptions jsonOptions = new ()
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
        };

        public Part3rdClient(IOptions<Party3rdSettings > party3rdSettings, HttpClient client)
        {
            _party3rdSettings= party3rdSettings.Value;
            _client = client;
        }

        public async Task<string> GetAuthentication(AuthFields authFields)
        {
            try
            {
                using var request = new HttpRequestMessage(HttpMethod.Post, party3rdSettings.AuthConnectionString); 
                var authenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{party3rdSettings.UserName}:{party3rdSettings.Password}"));
                _client.DefaultRequestHeaders.Clear();
                var base64EncodedAuthenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes(authenticationString));
                _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authenticationString);
                _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                string body = JsonSerializer.Serialize(authFields, jsonOptions);
                request.Content = new StringContent(body, Encoding.UTF8, "application/json");
                HttpResponseMessage response = await _client.SendAsync(request);
                if (response != null && response.IsSuccessStatusCode)
                {
                    Debug.WriteLine("Authenticating 3rd Party. Successful!");
                    return await response.Content.ReadAsStringAsync();
                }
                else if (response != null)
                {
                    Debug.WriteLine("Error Authenticating 3rd Party. HttpStatusCode: " + response.StatusCode + ", message: " + response.RequestMessage);
                    return "Unauthorized";
                }
            }
            catch (HttpRequestException e)
            {
                Debug.WriteLine("3rd Party GetAuthentication() Exception :{0} ", e.Message);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("3rd Party GetAuthentication() Exception :{0} ", ex.Message);
            }
            return string.Empty;
        }
    }
}

调用代码

// 后续可考虑缓存认证令牌以避免重复调用
if (string.IsNullOrEmpty(Authentication))
    Authentication = await GetAuthentication(new AuthFields("Something", "More"));
if (!string.IsNullOrEmpty(Authentication) && Authentication != "Unauthorized")
{

我是否在HttpClient配置或依赖注入中遗漏了什么?这是否是IDE的bug?该问题并非持续出现,恳请帮忙排查解决。


问题根源与解决办法

  1. 服务注册重复引发冲突
    你手动注册了AddTransient<IParty3rdClient, Party3rdClient>,又通过AddHttpClient<Party3rdClient>注册了一次,这会导致框架生成两个不同的服务实例,引发不可预期的异常。

    修正:移除手动的AddTransient,改用AddHttpClient直接关联接口:

    builder.Services.Configure<Party3rdSettings>(builder.Configuration.GetSection("Party3rdSettings"));
    builder.Services.AddHttpClient<IParty3rdClient, Party3rdClient>(client =>
    {
        client.Timeout = TimeSpan.FromMinutes(5);
    });
    
  2. 全局修改DefaultRequestHeaders引发并发问题
    你在方法中修改HttpClient的全局默认头(Clear()、设置Authorization等),当多个请求并发执行时,会导致头信息被覆盖或出现空引用,这是随机异常的核心原因。

    修正:直接在HttpRequestMessage上设置请求头,不修改全局默认值:

    using var request = new HttpRequestMessage(HttpMethod.Post, _party3rdSettings.AuthConnectionString); 
    var authenticationString = Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_party3rdSettings.UserName}:{_party3rdSettings.Password}"));
    // 仅为当前请求设置头
    request.Headers.Authorization = new AuthenticationHeaderValue("Basic", authenticationString);
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    string body = JsonSerializer.Serialize(authFields, jsonOptions);
    request.Content = new StringContent(body, Encoding.UTF8, "application/json");
    HttpResponseMessage response = await _client.SendAsync(request);
    
  3. 静态变量的线程安全问题
    静态变量Authentication没有线程保护,多线程并发读写会导致竞态条件,可能引发异常或读取到无效值。

    修正:使用lock块保证线程安全,或改用框架自带的IMemoryCache存储令牌(自带线程安全机制):

    private static readonly object _authLock = new object();
    // 读取/写入时加锁
    lock(_authLock)
    {
        if (string.IsNullOrEmpty(Authentication))
            Authentication = await GetAuthentication(new AuthFields("Something", "More"));
    }
    
  4. 配置项的空值验证
    若Party3rdSettings中的配置项为空,会导致后续操作抛出异常。建议在构造函数中提前验证:

    public Part3rdClient(IOptions<Party3rdSettings> party3rdSettings, HttpClient client)
    {
        _party3rdSettings = party3rdSettings.Value ?? throw new ArgumentNullException(nameof(party3rdSettings));
        if(string.IsNullOrEmpty(_party3rdSettings.AuthConnectionString))
            throw new InvalidOperationException("未配置AuthConnectionString");
        if(string.IsNullOrEmpty(_party3rdSettings.UserName))
            throw new InvalidOperationException("未配置UserName");
        if(string.IsNullOrEmpty(_party3rdSettings.Password))
            throw new InvalidOperationException("未配置Password");
        _client = client ?? throw new ArgumentNullException(nameof(client));
    }
    

总结

这个问题不是IDE的bug,主要是并发修改全局请求头和服务注册重复导致的随机异常。按照上述步骤修正后,即可解决问题。

内容的提问来源于stack exchange,提问作者Age of Empires

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 06:05:00