How to change registered (Simple Injector) DbContext's connection string after user's authorization?

余生长醉 提交于 2020-08-09 08:14:43

问题


I'm building ASP.NET Core Web Api with Simple Injector and have the following problem: The data for each user is stored in his individual base (but with the same schema), which is known only after he authorizes himself. So... I can give DbContext it's proper connection string only after user's authorization. What is the best way to accomplish this?

For now I am storing the connection string in HttpContext custom claim and am refering to it with a use of a static helper class in DbContext's OnConfiguring method.

The helper:

public class WebHelper
{
    private static IHttpContextAccessor _httpContextAccessor;

    public static void Configure(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    private static HttpContext HttpContext
    {
        get
        {
            return _httpContextAccessor.HttpContext;
        }
    }

    public static string ConnectionString
    {
        get
        {
            return _httpContextAccessor?.HttpContext?.User.FindFirst(CustomClaimTypes.ConnectionString)?.Value;
        }
    }
}

Which I have registered like this:

private SimpleInjector.Container _container = new SimpleInjector.Container();

public void ConfigureServices(IServiceCollection services)
{
    _container.Register(() =>
    {
        return new BaseDbContext(SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(),
        "Data Source=127.0.0.1").Options);
    }, Lifestyle.Scoped);

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    WebHelper.Configure(app.ApplicationServices.GetRequiredService<IHttpContextAccessor>());
}

And after all I am calling the connection string like this:

public class BaseDbContext : DbContext
{
    public BasetDbContext(DbContextOptions options)
            : base(options) { }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (WebHelper.ConnectionString != null)
        {
            optionsBuilder.UseSqlServer(WebHelper.ConnectionString);
        }
    }
}

And I am not exactly satisfied with this approach, so I want to ask if anyone has a better idea...


回答1:


My general advice is to keep runtime data separated from object graph composition. This means that everything that object graphs should only consist of components, and everything that consists of (mutable) runtime data, should not be injected into the object graph using constructor injection, but instead should flow through the method calls of the components of the existing object graph.

In my experience, this separation leads to a simplified model that is much easier to graphs, and an object graph that is easier to verify for correctness.

In your question, there are two obvious pieces of runtime data: the DbContext and the connection string. In a typical application, there is only one connection string value, and it's a constant. Constants can be safely injected into object graphs. This is different when a connection string changes based on runtime conditions, since it results in the connection string itself become runtime data.

Instead of injecting the runtime data directly, provide access to the runtime data by means of method calls. An obvious solution is to provide the application with an abstraction that returns the runtime data when a method is called. For instance:

public interface IDbContextProvider
{
    MyDbContext Context { get; }
}

This allows you to retrieve the proper MyDbContext for the current request. An implementation might look as follows:

public class DelegateDbContextProvider : IDbContextProvider
{
    private readonly Func<MyDbContext> provider;
    public DelegateDbContextProvider(Func<MyDbContext> provider)
        => this.provider = provider;
    public MyDbContext Context => this.provider();
}

This allows you to register it as follows:

var contextProducer = Lifestyle.Scoped.CreateProducer<MyDbContext>(
    () => new MyDbContext(WebHelper.ConnectionString), container);

container.RegisterInstance<IDbContextProvider>(
    new DelegateDbContextProvider(contextProducer.GetInstance));

With this code, instead of injecting a MyDbContext into consumers, you inject an IDbContextProvider. Since IDbContextProvider is registered as Singleton, its consumers might be able to become Singletons as well. Still, when they call IDbContextProvider.Context, the correct MyDbContext is returned for the current request/scope.



来源:https://stackoverflow.com/questions/50890514/how-to-change-registered-simple-injector-dbcontexts-connection-string-after-u

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!