首页 新闻 搜索 专区 学院

.net core addscoped 在middleware中赋值,但是在controller里面拿不到

0
悬赏园豆:5 [已解决问题] 解决于 2021-01-11 10:55

1.在configservice中注入:services.AddScoped<ICurrentTenant, CurrentTenant>();
2.在中间件中赋值:_currentTenant = SetCurrentTenant(tenant);,此时的id,name,connectionString都是有值的
3.在controller中注入CurrentTenant,调试时看到只是new的一个空对象。

目前我的理解是:scope作用域,在一次http请求中,创建一个currentTenant实例,不管是在业务逻辑层还是在数据访问层都使用同一个currentTenant。那么currentTenant在中间件中赋值了,在controller中应该能够拿得到啊。

后来经过写日志,发现它们的hashcode不一样。
2021-01-11 09:53:47.9354|INFO|LS.Industry.BaseInfo.Api.Core.MultiTenantMiddleware|_currentTenant:59817589
2021-01-11 09:53:47.9354|INFO|LS.Industry.BaseInfo.Api.Core.MultiTenantMiddleware|_tenantRepository:48209832
2021-01-11 09:53:48.0385|INFO|LS.Industry.BaseInfo.Api.Controllers.HomeController|_currentTenant:64254500
2021-01-11 09:53:48.0385|INFO|LS.Industry.BaseInfo.Api.Controllers.HomeController|_tenantRepository:7167227

@dudu站长,各路大神求解惑。

屌丝大叔的笔记的主页 屌丝大叔的笔记 | 初学一级 | 园豆:5
提问于:2021-01-10 21:51

建议提供 middleware 中的相关代码

dudu 8个月前

方式一:
var _currentTenant = httpContext.RequestServices.GetRequiredService<ICurrentTenant>();
_currentTenant.ChangeValue(tenant);

方式二:
public async Task Invoke(HttpContext httpContext, ITenantRepository _tenantRepository, ICurrentTenant _currentTenant){
_tenantRepository.ChangeDatabase(string.Empty);
}

如Jonny-Xhl 所说,方式二比较优雅。

屌丝大叔的笔记 8个月前

@屌丝大叔的笔记: Invoke和反模式获取都是在Child Provider创建的,并非Root创建的。

Jonny-Xhl 8个月前
< >
分享
最佳答案
0

提供源码看ICurrentTenant是采用的什么方式进行注入的。
ctor ? InvokeAsync?

收获园豆:5
Jonny-Xhl | 小虾三级 |园豆:664 | 2021-01-10 22:28

startup:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();

        //全局
        services.AddSingleton<ITenantResolver, QueryStringTenantResolver>();
        services.AddScoped<ICurrentTenant, CurrentTenant>();
        services.AddScoped<ITenantRepository, TenantRepository>();

        //dapper会用到
        services.Configure<ConnectionString>(Configuration.GetSection("ConnectionStrings"));
    }

MultiTenantMiddleware:
private readonly RequestDelegate _nextDelegate;
private readonly ITenantResolver _tenantResolver;
private readonly ITenantRepository _tenantRepository;
private ICurrentTenant _currentTenant;//不能使用readonly,因为要改变它的值

    public MultiTenantMiddleware(RequestDelegate nextDelegate
        , ITenantResolver tenantResolver
        , ITenantRepository tenantRepository
        , ICurrentTenant currentTenant)
    {
        _nextDelegate = nextDelegate;
        _tenantResolver = tenantResolver;
        _tenantRepository = tenantRepository;
        _currentTenant = currentTenant;
    }

controller:
private readonly ITenantRepository _tenantRepository;
private readonly ICurrentTenant _currentTenant;

    public HomeController(ITenantRepository tenantRepository
        , ICurrentTenant currentTenant)
    {
        _tenantRepository = tenantRepository;
        _currentTenant = currentTenant;
    }

谢谢您,这是我的部分代码。

屌丝大叔的笔记 | 园豆:5 (初学一级) | 2021-01-10 23:25

@屌丝大叔的笔记: 使用构造(ctor)注入的始终是单例的,并且Scope是不能注册在root provider的,可以参见我写的demo

Jonny-Xhl | 园豆:664 (小虾三级) | 2021-01-11 09:26

@屌丝大叔的笔记:
我进行了一个测试,就是你这个原因,因为项目中肯定不验证Scope的,所以导致中间件中ctor注册scope服务是无效的,scope应该在Invoke中使用。

  • 测试同样存在Null

附上我的测试代码:

  1. ICurrenTenant
namespace cnblog_133057
{
    public interface ICurrentTenant
    {
        string TenantId { get; }
        string ConnectionString { get; }

        void SetTenant(string id, string conn);
    }

    public class CurrentTenant : ICurrentTenant
    {
        public string TenantId { get; private set; }

        public string ConnectionString { get; private set; }

        public void SetTenant(string id, string conn)
        {
            TenantId = id;
            ConnectionString = conn;
        }
    }
}
  1. TenantMiddleware
namespace cnblog_133057
{
    public class TenantMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ICurrentTenant _currentTenant;

        public TenantMiddleware(RequestDelegate next, ICurrentTenant currentTenant)
        {
            _next = next;
            _currentTenant = currentTenant;
        }

        public async Task InvokeAsync(HttpContext httpContext)
        {
            var __tenantId = httpContext.Request.Headers["__tenantId"];
            var conn = "xxxxxx";
            _currentTenant.SetTenant(__tenantId, conn);
            await _next(httpContext);
        }
    }
}
  1. 添加注册
app.UseMiddleware(typeof(TenantMiddleware));
  1. 控制器测试
namespace cnblog_133057.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private readonly ICurrentTenant _tenant;
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger, ICurrentTenant tenant)
        {
            _logger = logger;
            _tenant = tenant;
        }

        [HttpGet]
        public IEnumerable<WeatherForecast> Get()
        {
            var rng = new Random();
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)],
                Tenant = $"{_tenant.TenantId};{_tenant.ConnectionString}"
            })
            .ToArray();
        }
    }
}

上面在模板项目中运行是会报错的,下面我为了测试强制不进行报错,添加如下代码

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseDefaultServiceProvider(configure =>
                    {
                        configure.ValidateOnBuild = false;
                        configure.ValidateScopes = false;
                    });
                    webBuilder.UseStartup<Startup>();
                });

配置不验证ValidateScopes 和在编译的时候不进行验证ValidateOnBuild

使用Invoke测试


变更一下Invoke

public async Task InvokeAsync(HttpContext httpContext, ICurrentTenant _currentTenant)
{
      var __tenantId = httpContext.Request.Headers["__tenantId"];
       var conn = "xxxxxx";
       _currentTenant.SetTenant(__tenantId, conn);
       await _next(httpContext);
}
Jonny-Xhl | 园豆:664 (小虾三级) | 2021-01-11 10:01

@Jonny-Xhl: 牛逼啊,大哥。我看了你给的链接,换成以下方式就行了:
var _currentTenant = httpContext.RequestServices.GetRequiredService<ICurrentTenant>();
_currentTenant.ChangeValue(tenant);

屌丝大叔的笔记 | 园豆:5 (初学一级) | 2021-01-11 10:42

@屌丝大叔的笔记: 中间件构造中注入慎用!!!,很容易导致内存泄漏

Jonny-Xhl | 园豆:664 (小虾三级) | 2021-01-11 11:45

@Jonny-Xhl: 知道了,谢谢。

屌丝大叔的笔记 | 园豆:5 (初学一级) | 2021-01-11 14:07
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册