1.安装包:IHttpContextAccessor
1.对于ASP.NET Core 项目(如 Web API、MVC):项目默认引用 Microsoft.AspNetCore.App 元包(.NET 6 中自动包含),已涵盖 IHttpContextAccessor 所需的全部依赖,无需手动安装 NuGet 包。 2.对于非 Web 项目(如类库项目作为业务层):若业务层是独立的类库(.NET 6 Class Library),只需添加对 Microsoft.AspNetCore.Http 的引用即可。在 Visual Studio 中,可通过以下方式添加: 2.1.右键项目 → 添加 → 项目引用 → 搜索并勾选 Microsoft.AspNetCore.Http
2.2.若找不到,可通过 NuGet 安装 Microsoft.AspNetCore.Http.Abstractions 包,这是包含 IHttpContextAccessor 接口定义的基础包,体积很小。
2.Demo 类
2.1.RequestContext
// 请求上下文模型,用于传递需要的Header参数 public class RequestContext {public string UserAgent { get; set; }public string Authorization { get; set; }public string Language { get; set; }// 可以根据需要添加其他Header参数 }
2.2.IRequestContextProvider
// 请求上下文提供器接口 public interface IRequestContextProvider {RequestContext GetRequestContext(); }
2.3.RequestContextProvider
// 请求上下文提供器实现 public class RequestContextProvider : IRequestContextProvider {private readonly IHttpContextAccessor _httpContextAccessor;public RequestContextProvider(IHttpContextAccessor httpContextAccessor){_httpContextAccessor = httpContextAccessor;}public RequestContext GetRequestContext(){var httpContext = _httpContextAccessor.HttpContext;if (httpContext == null){return new RequestContext(); // 非HTTP环境返回空上下文 }return new RequestContext{UserAgent = httpContext.Request.Headers.TryGetValue("User-Agent", out var userAgent) ? userAgent : string.Empty,Authorization = httpContext.Request.Headers.TryGetValue("Authorization", out var auth) ? auth : string.Empty,Language = httpContext.Request.Headers.TryGetValue("Accept-Language", out var lang) ? lang : "en"};} }
2.4.BusinessService
// 业务层服务 public class BusinessService : IBusinessService {private readonly IRequestContextProvider _requestContextProvider;private readonly IDataRepository _dataRepository;public BusinessService(IRequestContextProvider requestContextProvider, IDataRepository dataRepository){_requestContextProvider = requestContextProvider;_dataRepository = dataRepository;}public async Task<DataResult> GetDataAsync(int id){// 从请求上下文获取Header参数var requestContext = _requestContextProvider.GetRequestContext();// 根据Header参数执行不同的业务逻辑if (requestContext.Language == "zh-CN"){// 中文处理逻辑 }else{// 默认处理逻辑 }// 使用Authorization头进行权限验证等if (!string.IsNullOrEmpty(requestContext.Authorization)){// 验证逻辑 }return await _dataRepository.GetDataByIdAsync(id);} }
3.注册服务
var builder = WebApplication.CreateBuilder(args);// 添加HttpContext访问器 builder.Services.AddHttpContextAccessor();// 注册请求上下文提供器 builder.Services.AddScoped<IRequestContextProvider, RequestContextProvider>();// 注册业务服务和数据访问服务 builder.Services.AddScoped<IBusinessService, BusinessService>(); builder.Services.AddScoped<IDataRepository, DataRepository>();var app = builder.Build();// 其他中间件配置... app.Run();
4.依赖注入
using Microsoft.AspNetCore.Http; // 需引入此命名空间 using System;namespace BusinessLayer.Services {public class BusinessService : IBusinessService{private readonly IHttpContextAccessor _httpContextAccessor;// 构造函数注入IHttpContextAccessorpublic BusinessService(IHttpContextAccessor httpContextAccessor){_httpContextAccessor = httpContextAccessor;}public string GetHeaderValue(string headerKey){// 注意:HttpContext可能为null(如非HTTP环境下调用,需判空)var httpContext = _httpContextAccessor.HttpContext;if (httpContext == null){throw new InvalidOperationException("当前上下文无HTTP请求");}// 获取Header中的参数if (httpContext.Request.Headers.TryGetValue(headerKey, out var headerValue)){return headerValue.ToString();}return null;}}public interface IBusinessService{string GetHeaderValue(string headerKey);} }
5.单元测试
[TestClass] public class BusinessServiceTests {[TestMethod]public void GetHeaderValue_Test(){// 模拟HttpContext和Headervar httpContext = new DefaultHttpContext();httpContext.Request.Headers["X-Test-Header"] = "test-value";// 模拟IHttpContextAccessorvar mockAccessor = new Mock<IHttpContextAccessor>();mockAccessor.Setup(a => a.HttpContext).Returns(httpContext);// 注入模拟对象到业务服务var businessService = new BusinessService(mockAccessor.Object);// var businessService = new BusinessService(accessor);
// 验证结果
var result = businessService.GetHeaderValue("X-Test-Header");
Assert.AreEqual("test-value", result);
}
}
PS:
1.架构考虑:避免直接依赖,推荐封装抽象
业务层直接依赖IHttpContextAccessor会导致与 HTTP 上下文强耦合,不利于单元测试和架构灵活性。更好的做法是: 1.创建一个抽象接口(如IRequestContext),定义业务所需的 Header 参数获取方法; 2.在实现类中使用IHttpContextAccessor获取实际数据; 3.业务层依赖IRequestContext而非IHttpContextAccessor
2.处理HttpContext
为 null 的情况
IHttpContextAccessor.HttpContext在非 HTTP 请求场景下(如后台任务、定时作业、单元测试)会为 null,必须在代码中添加判空逻辑,避免空引用异常
3.线程安全:IHttpContextAccessor
本身是线程安全的,但HttpContext
对象不是,不要在异步操作中跨线程访问它
4.测试便利性:在单元测试中,可以使用DefaultHttpContext
来模拟 HTTP 上下文
5.Error: 服务注册顺序正常,但是依赖注入的值为Null
1.服务注册范围不匹配 1.1.若ICacheService注册为Singleton,但它依赖的服务(如IMemoryCache)是Scoped或Transient,会导致冲突。 1.2.解决:IMemoryCache本身是Singleton,因此ICacheService可以安全注册为Singleton。 2.构造函数参数错误 2.1.若MemoryCacheService的构造函数参数不是IMemoryCache,或存在多个构造函数,DI 容器可能无法正确解析。 2.2.解决:确保只有一个构造函数,且仅依赖IMemoryCache。 3.在非 DI 容器创建的实例中使用 3.1.若手动new BusinessService()而不是通过 DI 容器获取实例,注入会失效。 3.2.解决:必须通过 DI 容器获取业务服务(如在控制器中通过构造函数注入IBusinessService)。 4.循环依赖 4.1.若ICacheService和业务服务相互依赖,会导致 DI 容器无法解析,最终注入null。 4.2.解决:重构代码消除循环依赖,或使用Lazy<T>延迟加载
6.Error: System.InvalidOperationException:“Cannot resolve scoped service 'CustCommon.Interfaces.IRequestContextProvider' from root provider.”
原因:
中间件的生命周期是Singleton,但 IRequestContextProvider 的生命周期是 Scoped,.NET 依赖注入容器不允许从单例服务(Singleton/中间件)中直接依赖作用域服务(Scoped);
因为作用域服务的生命周期与请求绑定,而中间件是应用启动时创建的单例,会导致作用域服务无法正确释放或获取 解决: 1.构造函数注入:修改 IRequestContextProvider 生命周期为Singleton 2.方法注入:
中间件实例在应用启动时创建(单例),但InvokeAsync方法会在每次请求时执行
在InvokeAsync方法参数中声明的服务(如IRequestContextProvider),会由.NET 在每次请求时从当前作用域中解析,与请求的作用域(Scoped)生命周期一致,避免了单例依赖 Scoped 的冲突,因此可以在这里安全注入 Scoped 服务public class CustomAuthorizationMiddleware {private readonly RequestDelegate _next;// 构造函数只保留RequestDelegate(单例安全)public CustomAuthorizationMiddleware(RequestDelegate next){_next = next;}// 在InvokeAsync方法中注入Scoped服务IRequestContextProviderpublic async Task InvokeAsync(HttpContext context, IRequestContextProvider requestContextProvider){// 这里可以安全使用IRequestContextProvidervar userId = requestContextProvider.GetUserId(); // 示例:调用Scoped服务的方法// 你的授权逻辑...if (string.IsNullOrEmpty(userId)){context.Response.StatusCode = 401; // 未授权return;}// 调用下一个中间件await _next(context);} }