NET6 业务层获取head中的参数

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

 

 

 


 

posted @ 2025-08-26 16:33  Robot-Blog  阅读(9)  评论(0)    收藏  举报