zoukankan      html  css  js  c++  java
  • 全局获取HttpContext

    全局获取HttpContext

    在我们平常开发中会有这样的需求,我们的Service业务层需要获取请求上下文中的用户信息,一般我们从控制器参数传递过来。如果你觉得这样就可以了,请您关闭文章。

    场景

    但是我们也会遇到控制器传递困难的场景,我自己最近使用单库实现多租户的PAAS平台,发现EF Core上下文获取我Token或者Headers中获取租户Id进行全局过滤就很麻烦(多租户解决方案后期我补充)。

    涉及知识

    我们先要知道一个思想如果想要整个.NET程序中共享一个变量,我们可以将想要共享的变量放在某个类的静态属性上来实现。
    但是我们的请求上下文每个人的信息不一样,就需要将这个变量的共享范围缩小到单个线程内。例如在web应用中,服务器为每个同时访问的请求分配一个独立的线程,我们要在这些独立的线程中维护自己的当前访问用户的信息时,就需要线程本地存储了。

    • IHttpContextAccessor 设置实现规范
    • HttpContextAccessor 基于当前执行上下文提供的实现。
    • AsyncLocal 实现多线程中静态变量独立化 (这里画一个圈圈)

    这个时候我们再看源码思路就清晰了,我们通过注入HttpContextAccessor,然后内部将请求上下文保存在_httpContextCurrent静态变量中,这个就可以全局访问啦(当然访问范围是在该主线程内部)。

        // HttpContextAccessor源码
        public class HttpContextAccessor : IHttpContextAccessor
        {
            // 通过AsyncLocal保存当前上下文信息
            private static readonly AsyncLocal<HttpContextHolder> _httpContextCurrent =new AsyncLocal<HttpContextHolder>();
    
            public HttpContext? HttpContext
            {
                get
                {
                    return  _httpContextCurrent.Value?.Context;
                }
                set
                {
                    var holder = _httpContextCurrent.Value;
                    if (holder != null)
                    {
                        // 清除AsyncLocals中捕获的当前HttpContext
                        holder.Context = null;
                    }
                    if (value != null)
                    {
                        // 使用一个对象间接在AsyncLocal中保存HttpContext,
                        // 所以当它被清除时,它可以在所有的ExecutionContexts中被清除。
                        _httpContextCurrent.Value = new HttpContextHolder { Context = 
    value };
                    }
                }
            }
    
            private class HttpContextHolder
            {
                public HttpContext? Context;
            }
    

    整活

    首先我们需要在Startup的ConfigureServices方法中注册IHttpContextAccessor的实例

    public void ConfigureServices(IServiceCollection services)
    {
          services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
          ....
    }
    

    这个时候你Service层注入该类的时候就可以获取到请求上下文信息了,但是这个就不符合我们诗一般程序员的气质。
    因为直接将请求上下文抛出来不友好,我们本来只需要租户ID但是你给我一坨,挺不好把握的。

    整大活

    我们可以进行包装,我使用PrincipalAccessor进行请求上下文拆解

    然后在Startup的ConfigureServices方法中,我们一样把这个类也加入注册中

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddSingleton<IPrincipalAccessor, PrincipalAccessor>();
        ....
    }
    

    最后自己项目的一些优化

    自己不断的在优化自己的项目结构,或者设计思路,我发现我为什么有这么多注入,我构造函数都要爆了。

    然后自己想了想,我其实可以将访问上下文的类放入BaseService中静态变量存储,系统提供了IServiceCollection来注册服务和提供了IServiceProvider这个让我们解析各种注册过的服务.
    我们定义一个存储类

    public class ServiceProviderInstance
     {
          public static IServiceProvider Instance { get; set; }
     } 
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
     {
            ...
           ServiceProviderInstance.Instance = app.ApplicationServices;
     }
    

    宝贝相信我剩下的我们交给时间,我们只需要这样(BaseService定义属性、获取注入就可以了),然后就那样(就直接可以使用啦)

        public class BaseService<T, Repository> : IBaseService<T>
              where T : BaseEntityCore, new()
              //规定这个Repository类型一定是继承仓储的接口,下面就可以使用接口的方法
              where Repository : IBaseRepository<T>
        {
            /// <summary>
            /// 身份信息
            /// </summary>
            protected IClaimsAccessor Claims { get; set; }
    
            /// <summary>
            /// 获取仓储实体
            /// </summary>
            private readonly Repository CurrentRepository;
    
            public BaseService(Repository currentRepository)
            {
                CurrentRepository = currentRepository;
                Claims = ServiceProviderInstance.Instance.GetRequiredService<IClaimsAccessor>();
            }
            .....
    }
    
    我曾七次鄙视自己的灵魂:
    第一次,当它本可进取时,却故作谦卑;
    第二次,当它空虚时,用爱欲来填充;
    第三次,在困难和容易之间,它选择了容易;
    第四次,它犯了错,却借由别人也会犯错来宽慰自己;
    第五次,它自由软弱,却把它认为是生命的坚韧;
    第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;
    第七次,它侧身于生活的污泥中虽不甘心,却又畏首畏尾。
  • 相关阅读:
    C#读取Excel日期时间
    软件需求3个层次――业务需求、用户需求和功能需求
    软件开发中的基线
    软件开发过程(CMMI/RUP/XP/MSF)是与非
    第1章项目初始.pdf
    计算机鼓轮
    概念模型,逻辑模型,物理模型
    第0章项目管理概述.pdf
    C#中提供的精准测试程序运行时间的类Stopwatch
    Installing and configuring OpenSSH with pam_ldap for RedHat Enterprise Linux3
  • 原文地址:https://www.cnblogs.com/chenxi001/p/14998845.html
Copyright © 2011-2022 走看看