Sto creando ASP.NET Core Web Api con Simple Injector e ho il seguente problema: I dati per ogni utente sono memorizzati nella sua base individuale (ma con lo stesso schema), che è noto solo dopo essersi autodefinito. Quindi ... Posso dare a DbContext
è la stringa di connessione corretta solo dopo l'autorizzazione dell'utente. Qual è il modo migliore per farlo?
Per ora sto Memorizzazione della stringa di connessione nel HttpContext
attestazione personalizzata e sto riferisco ad essa con l'uso di una classe di supporto statica in DbContext
metodo OnConfiguring s'.
L'aiutante:
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;
}
}
}
Che ho registrato in questo modo:
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>());
}
E dopo tutto sto chiamando la stringa di connessione in questo modo:
public class BaseDbContext : DbContext
{
public BasetDbContext(DbContextOptions options)
: base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (WebHelper.ConnectionString != null)
{
optionsBuilder.UseSqlServer(WebHelper.ConnectionString);
}
}
}
E non sono esattamente soddisfatto di questo approccio, quindi voglio chiedere se qualcuno ha un'idea migliore ...
Il mio consiglio generale è di mantenere separati i dati di runtime dalla composizione del grafico dell'oggetto . Ciò significa che tutto ciò che i grafici degli oggetti dovrebbero consistere solo di componenti e tutto ciò che è costituito da dati di runtime (mutabili), non dovrebbe essere iniettato nel grafo degli oggetti usando il constructor injection, ma dovrebbe invece scorrere attraverso le chiamate di metodo dei componenti dell'esistente grafico dell'oggetto.
Nella mia esperienza, questa separazione porta a un modello semplificato che è molto più facile da afferrare, e un oggetto grafico che è più facile da verificare per la correttezza.
Nella tua domanda, ci sono due parti ovvie dei dati di runtime: il DbContext
e la stringa di connessione. In un'applicazione tipica, esiste un solo valore di stringa di connessione ed è una costante. Le costanti possono essere iniettate in modo sicuro nei grafici degli oggetti. Questo è diverso quando una stringa di connessione cambia in base alle condizioni di runtime, poiché risulta che la stringa di connessione stessa diventa dati di runtime.
Invece di iniettare direttamente i dati di runtime, fornire l'accesso ai dati di runtime tramite chiamate di metodo. Una soluzione ovvia è fornire all'applicazione un'astrazione che restituisca i dati di runtime quando viene chiamato un metodo. Per esempio:
public interface IDbContextProvider
{
MyDbContext Context { get; }
}
Ciò consente di recuperare il MyDbContext
corretto per la richiesta corrente. Un'implementazione potrebbe apparire come segue:
public class DelegateDbContextProvider : IDbContextProvider
{
private readonly Func<MyDbContext> provider;
public DelegateDbContextProvider(Func<MyDbContext> provider)
=> this.provider = provider;
public MyDbContext Context => this.provider();
}
Questo ti permette di registrarlo come segue:
var contextProducer = Lifestyle.Scoped.CreateProducer<MyDbContext>(
() => new MyDbContext(WebHelper.ConnectionString), container);
container.RegisterInstance<IDbContextProvider>(
new DelegateDbContextProvider(contextProducer.GetInstance));
Con questo codice, invece di iniettare un MyDbContext
nei consumatori, si inietta un IDbContextProvider
. Poiché IDbContextProvider
è registrato come Singleton
, i suoi consumatori potrebbero essere in grado di diventare anche Singletons
. Tuttavia, quando chiamano IDbContextProvider.Context
, viene restituito il MyDbContext
corretto per la richiesta / ambito corrente.