none
Multitenant NET Core 3.1 - Web Api RRS feed

  • Pregunta

  • Trabajo con Net Core 3.1 estoy implementando una Web api

    Envio el nombre del inquilino por los claims facilmente lo puedo obtener, el problema es que sería posible que cuando aya un request obtener los claims directamente en el DBContext?

    Algo como esto:

    public class BusinessContext : DbContext
        {
            private List<Conexion> _detalleItemTemp;
            private string _nameTenan = string.Empty;
            private IConfiguration _configuration;
            private readonly IHttpContextAccessor _httpContextAccessor;
    
            //private ITenantProvider _tenantProvider;
    
            public BusinessContext()
            {
    
            }
    
            //private void InicializarTenant()
            //{
            //    //_tenantProvider = tenantProvider;
    
            //    _nameTenan = _tenantProvider.GetName().Result;
            //    _detalleItemTemp = _tenantProvider.MostrarConexiones().Result.ToList();
    
            //}
    
            public BusinessContext(DbContextOptions<BusinessContext> options,
                IHttpContextAccessor httpContextAccessor, IConfiguration configuration)
                : base(options)
            {
                //_tenantProvider = tenantProvider;
                //_nameTenan = _tenantProvider.GetName().Result;
                //_detalleItemTemp = _tenantProvider.MostrarConexiones().Result.ToList();
    
                //var connectionString = configuration["ConnectionStrings:DefaultConnection"];
                _configuration = configuration;
                _httpContextAccessor = httpContextAccessor;
    
    
                if (httpContextAccessor.HttpContext != null)
                {
                    _nameTenan = _httpContextAccessor.HttpContext.User.Claims
                    .Where(c => c.Type == ClaimsIdentity.DefaultNameClaimType).FirstOrDefault().Value;
                }
            }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                //optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Business;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False");
                //optionsBuilder.UseSqlServer(@"Server=tcp:catalog-dpt-minimainor.database.windows.net,1433;Database=Business;User ID=developer;Password=E962279!gh4df56j;Trusted_Connection=False;Encrypt=True;MultipleActiveResultSets=True;");
    
                //var ConnectionString = _detalleItemTemp.Where(x => x.Tenant == _nameTenan)
                //        .FirstOrDefault().DatabaseConnectionString;
                //optionsBuilder.UseSqlServer(ConnectionString);
    
                //Asignar cadena de conexión
                var builder = new ConfigurationBuilder();
                builder.AddJsonFile("appsettings.json", optional: false);
                _configuration = builder.Build();
                string connectionString = _configuration.GetConnectionString(_nameTenan).ToString();
                optionsBuilder.UseSqlServer(connectionString);
            }

    El problema es que no entra en el constructor del DbContext para poder obtener el claims

    Pero si se puede obtener el claims desde el controler en la variable _httpContextAccessor

    Authorize]
        [Route("api/[controller]")]
        [ApiController]
        public class UsuarioController : ControllerBase
        {
            private readonly ISdUsuario _sdUsuario;
            private readonly IMapper _mapper;
            private readonly IHttpContextAccessor _httpContextAccessor;
            //private readonly ITenantProvider _tenantProvider;
            public UsuarioController(ISdUsuario sdUsuario, IMapper mapper, IHttpContextAccessor httpContextAccessor)
            {
                _sdUsuario = sdUsuario;
                _mapper = mapper;
                _httpContextAccessor = httpContextAccessor;
                //_tenantProvider = tenantProvider;
    
                //var name = _tenantProvider.GetName();
            }

    ¿Hay alguna manera de como debo enviar el contenido de _httpContextAccessor desde el controler a la capa donde se encuentra el DBContext, ó se puede interceptar directamente el request en el DBContext?

     Tengo configurado el Startup

    public void ConfigureServices(IServiceCollection services)
            {
    
                services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
                services.AddControllers().AddNewtonsoftJson(options =>
                {
                    options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
                });
    
    
    
                //var connectionString = Configuration.GetConnectionString("CatalogoInquilino");
                //services.AddDbContext<CatalogoContext>(options =>
                //{
                //    options.UseSqlServer(connectionString);
                //});
    
                var connection = Configuration["ConnectionStrings"];
                services.AddEntityFrameworkSqlServer();
                services.AddDbContext<CatalogoContext>(options => options.UseSqlServer(connection));
                services.AddDbContext<BusinessContext>(options => options.UseSqlServer(connection));
                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    Saludos!


    Pedro Ávila
    "El hombre sabio querrá estar siempre con quien sea mejor que él."
    Lima - Perú


    domingo, 25 de octubre de 2020 19:45

Respuestas

  • Hola, yo algo parecido tuve que solventar.

    Para ello implementé una clase de esta forma que resumo

    public static class DBContextExtensions { private static IHttpContextAccessor _httpContextAccessor; private const string customClaimName = "<CLAIM>"; public static void Configure(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public static HttpContext CurrentHttpContext => _httpContextAccessor.HttpContext; // A partir de aquí métodos de extensión de public static IEnumerable<string> GetEntityKey<T>(this DbContext context, T entity) where T : class { return context.Model.FindEntityType(entity.GetType()).FindPrimaryKey().Properties.Select(x => x.Name); } public static async Task<int> SaveChangesWithTrackerAsync<T>(this DbContext context) where T : class { CurrentHttpContext.Request.Headers.TryGetValue(customClaimName, out StringValues claim); // En claim tengo el valor que venga desde la cabecera context.ChangeTracker.DetectChanges(); var allEntries = context.ChangeTracker.Entries().ToList(); var added = allEntries.Where(e => e.State == EntityState.Added); var deleted = allEntries.Where(e => e.State == EntityState.Deleted); ....... }

    public static HttpContext GetHttpContext(this DbContext context) => CurrentHttpContext; }


    Como ves existe un método Configure() el cual uso en Startup en el método Configure()

            /// <summary>
            /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            /// </summary>
            /// <param name="app"></param>
            /// <param name="env"></param>
            /// <param name="loggerFactory"></param>
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                // CONFIGURAR 
                DBContextExtensions.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
    
                app.UseMvc();
            }


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos




    domingo, 25 de octubre de 2020 22:15
    Moderador
  • En tu método 

     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                //optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Business;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False");
                //optionsBuilder.UseSqlServer(@"Server=tcp:catalog-dpt-minimainor.database.windows.net,1433;Database=Business;User ID=developer;Password=E962279!gh4df56j;Trusted_Connection=False;Encrypt=True;MultipleActiveResultSets=True;");
    
                //var ConnectionString = _detalleItemTemp.Where(x => x.Tenant == _nameTenan)
                //        .FirstOrDefault().DatabaseConnectionString;
                //optionsBuilder.UseSqlServer(ConnectionString);
    
                //Asignar cadena de conexión
                var builder = new ConfigurationBuilder();
                builder.AddJsonFile("appsettings.json", optional: false);
                _configuration = builder.Build();
                string connectionString = _configuration.GetConnectionString(_nameTenan).ToString();
                optionsBuilder.UseSqlServer(connectionString);
            }

    Podrías recuperar el HttpContextAccesor usando

    DBContextExtensions.CurrentHttpContext

    O inclusive implementar un método de extensión que agrego en el post anterior si te parece


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos


    domingo, 25 de octubre de 2020 23:31
    Moderador

Todas las respuestas

  • Hola, yo algo parecido tuve que solventar.

    Para ello implementé una clase de esta forma que resumo

    public static class DBContextExtensions { private static IHttpContextAccessor _httpContextAccessor; private const string customClaimName = "<CLAIM>"; public static void Configure(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public static HttpContext CurrentHttpContext => _httpContextAccessor.HttpContext; // A partir de aquí métodos de extensión de public static IEnumerable<string> GetEntityKey<T>(this DbContext context, T entity) where T : class { return context.Model.FindEntityType(entity.GetType()).FindPrimaryKey().Properties.Select(x => x.Name); } public static async Task<int> SaveChangesWithTrackerAsync<T>(this DbContext context) where T : class { CurrentHttpContext.Request.Headers.TryGetValue(customClaimName, out StringValues claim); // En claim tengo el valor que venga desde la cabecera context.ChangeTracker.DetectChanges(); var allEntries = context.ChangeTracker.Entries().ToList(); var added = allEntries.Where(e => e.State == EntityState.Added); var deleted = allEntries.Where(e => e.State == EntityState.Deleted); ....... }

    public static HttpContext GetHttpContext(this DbContext context) => CurrentHttpContext; }


    Como ves existe un método Configure() el cual uso en Startup en el método Configure()

            /// <summary>
            /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            /// </summary>
            /// <param name="app"></param>
            /// <param name="env"></param>
            /// <param name="loggerFactory"></param>
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                // CONFIGURAR 
                DBContextExtensions.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
    
                app.UseMvc();
            }


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos




    domingo, 25 de octubre de 2020 22:15
    Moderador
  • En tu método 

     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                //optionsBuilder.UseSqlServer(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=Business;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False");
                //optionsBuilder.UseSqlServer(@"Server=tcp:catalog-dpt-minimainor.database.windows.net,1433;Database=Business;User ID=developer;Password=E962279!gh4df56j;Trusted_Connection=False;Encrypt=True;MultipleActiveResultSets=True;");
    
                //var ConnectionString = _detalleItemTemp.Where(x => x.Tenant == _nameTenan)
                //        .FirstOrDefault().DatabaseConnectionString;
                //optionsBuilder.UseSqlServer(ConnectionString);
    
                //Asignar cadena de conexión
                var builder = new ConfigurationBuilder();
                builder.AddJsonFile("appsettings.json", optional: false);
                _configuration = builder.Build();
                string connectionString = _configuration.GetConnectionString(_nameTenan).ToString();
                optionsBuilder.UseSqlServer(connectionString);
            }

    Podrías recuperar el HttpContextAccesor usando

    DBContextExtensions.CurrentHttpContext

    O inclusive implementar un método de extensión que agrego en el post anterior si te parece


    Si se solucionó tu consulta no olvides marcar la respuesta. Si te ayudó, vótala como útil. Saludos


    domingo, 25 de octubre de 2020 23:31
    Moderador
  • Hola,

     

    gracias por confirmar que encontraste una respuesta a tu pregunta.

     

    Saludos cordiales

     

    Gabriel Castro

    lunes, 26 de octubre de 2020 13:09
    Moderador