.NET 4.6.1 WebAPI2 HttpClient发GET请求报NullReferenceException
空引用异常触发原因
代码存在多个触发NullReferenceException的隐患,核心问题如下:
- Action方法使用
async void签名:ASP.NET Web API管道无法感知async void方法内部的await异步操作,会在方法第一次执行await时就判定请求处理完成,提前释放HttpContext等请求关联对象,后续代码操作已释放的响应/请求对象时直接触发空引用。 - 无类型反序列化结果可能为null:
JsonConvert.DeserializeObject(eventResponse)不传目标类型时,如果Eventbrite返回空内容、非预期格式内容,反序列化结果会是null,后续直接调用events.WriteToReponse()属于对null对象执行方法调用,必然触发异常。 - 静态HttpClient全局头操作存在线程安全问题:静态HttpClient的
DefaultRequestHeaders是全局共享资源,并发请求下执行Clear、Add操作会出现头值丢失、对象状态异常,极端场景下会返回null引用。 - 异步方法同步阻塞隐患:
res.Content.ReadAsStringAsync().Result用同步阻塞方式调用异步方法,在ASP.NET .NET Framework环境下极易触发死锁,若请求上下文被提前释放,res.Content也可能为null触发空引用。 - 异常分支响应未正确输出:catch块中仅创建了
JsonResponse对象,没有将其写入响应流,且如果上下文已提前释放,创建响应对象时访问内部HttpContext也会触发空引用。 - 构造函数初始化请求/响应对象逻辑错误:就算放开构造函数中
_response、_request的赋值注释,构造函数执行时Controller实例还未绑定当前请求上下文,拿到的对象要么是null要么是其他请求的残留实例,后续操作也会触发异常。
修复方案
按以下规则调整代码即可解决问题:
- 将Action方法签名从
async void改为async Task<IHttpActionResult>,让Web API管道可以正确等待异步执行完成,避免上下文提前释放。 - 不要修改静态HttpClient的全局
DefaultRequestHeaders,每次请求通过HttpRequestMessage独立构造请求头,解决并发场景下的线程安全问题。 - 所有异步调用全程使用await,禁止用
.Result、.Wait()做同步阻塞,避免死锁。 - 反序列化后先做判空校验再执行后续操作,所有返回结果通过Web API自带的
Ok()、BadRequest()等方法返回,不要依赖自定义方法直接操作响应流写入。 - 移除控制器中注释掉的
_response、_request字段,这类请求相关对象不要在构造函数中初始化。 - 修正
WriteToReponse的拼写错误(正确拼写为WriteToResponse),如果是自定义扩展方法需确认方法内部做了null值校验,避免对null实例调用时触发异常。
修复后的参考代码:
public class CANWebsiteV3Controller : ApiController { // 静态HttpClient全局复用,禁止每次请求新建实例 private static readonly HttpClient client = new HttpClient(); [Route("api2.0/CANWebsiteV3/GetEvents")] [HttpGet] public async Task<IHttpActionResult> GetEvents() { const string API_TOKEN = "XXXXXXXXXXXX"; const string ORGANIZATION_ID = "YYYYYYYYYY"; string endpoint = $"https://www.eventbriteapi.com/v3/organizations/{ORGANIZATION_ID}/events/?time_filter=current_future"; try { // 每次请求独立构造请求消息,不修改全局默认请求头 using (var request = new HttpRequestMessage(HttpMethod.Get, endpoint)) { request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {API_TOKEN}"); request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var res = await client.SendAsync(request); if (res.IsSuccessStatusCode) { string eventResponse = await res.Content.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(eventResponse)) { return BadRequest("Eventbrite returned empty response"); } var events = JsonConvert.DeserializeObject(eventResponse); if (events == null) { return BadRequest("Failed to parse event data"); } // 直接返回序列化结果,不要直接操作响应流写入 return Ok(events); } string errorContent = await res.Content.ReadAsStringAsync(); return BadRequest($"Could not fetch events: Eventbrite returned {res.StatusCode}, {errorContent}"); } } catch(Exception ex) { return BadRequest($"Could not fetch events: {ex.Message}"); } } }
注意:如果项目需要用自定义
JsonResponse类返回统一格式,只需要把上述代码中Ok(events)、BadRequest()的返回值替换为统一响应格式即可,不要直接操作Response流写入。
内容的提问来源于stack exchange,提问作者Hrithvik Gadhiya
相关产品推荐
相关产品推荐

