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;
// 构造函数注入IHttpContextAccessor
public 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和Header
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["X-Test-Header"] = "test-value";
// 模拟IHttpContextAccessor
var 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服务IRequestContextProvider
public async Task InvokeAsync(HttpContext context, IRequestContextProvider requestContextProvider)
{
// 这里可以安全使用IRequestContextProvider
var userId = requestContextProvider.GetUserId(); // 示例:调用Scoped服务的方法
// 你的授权逻辑...
if (string.IsNullOrEmpty(userId))
{
context.Response.StatusCode = 401; // 未授权
return;
}
// 调用下一个中间件
await _next(context);
}
}
7.不同生命周期注入情况
![image]()