Reputation: 2954
I need to create DbContext for each lifetime scope and the database name is not fixed, for each lifetime scope I want to have the DB from a different local store, to get the local store's value I need a key and I want to get that key from HTTPContext
or IHttpContextAccessor
. So, how can I get the value of the HTTP header in startup.cs?
N.B: I don't have any plan to send DB name through the header, this code is just an example, I'll update my code using key-value and some store service for the database name. The given code example always execute for each request if the request reference to the database.
services.AddDbContext<CheesedDbContext>((options =>
{
options.UseSqlServer($"Server=.;Database={DB};Trusted_Connection=True;MultipleActiveResultSets=true;");
});
Upvotes: 1
Views: 5947
Reputation: 3498
this is the first time I see someone who stores the database name in the request headers!. You should consider refactoring your way of storing that, as you don't want to expose any part of your connectionString to the public.
as an alternative, you can create an entry database that stores the database names, and users, each user would mapped to one database and give it a unique security stamp. Use that security stamp to get the stored database name. and reinitiate the context with a new connectionString.
for the IHttpContextAccessor
part. You can register IHttpContextAccessor
in the startup like this :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
//register IHttpContextAccessor
services.AddHttpContextAccessor();
}
Now you can use it in your DbContext
:
public class CheesedDbContext : DbContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public CheesedDbContext(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var dbName = _context.HttpContext.Request.Headers["Db"];
if(string.IsNullOrWhiteSpace(dbName))
{
throw new ArgumentNullException(nameof(dbName));
}
optionsBuilder.UseSqlServer($"Server=.;Database={dbName};Trusted_Connection=True;MultipleActiveResultSets=true;");
}
}
You can also use ActionFilter
to work with the IHttpContextAccessor
on each request. Like this :
public class SomeFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
//Get header
var requestHeaders = context.HttpContext.Request.Headers["somekey"];
// do some sync actions ..etc.
base.OnActionExecuted(context);
}
public async override Task OnActionExecutionAsync(ActionExecutingContext context , ActionExecutionDelegate next)
{
//Get header
var requestHeaders = context.HttpContext.Request.Headers["somekey"];
// do some async actions ..etc.
await base.OnActionExecutionAsync(context, next);
}
}
then register it in the Startup :
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Filters.Add(new SomeFilterAttribute());
});
}
this can be applied on both AddControllers
and AddControllersWithViews
.
Upvotes: 2
Reputation: 2954
I solved it using services.BuildServiceProvider()
!
services.AddDbContext<CheesedDbContext>(options =>
{
var serviceProvider = services.BuildServiceProvider();
var db = serviceProvider.GetService<IHttpContextAccessor>()?.HttpContext?.Request?.Headers["Db"];
options.UseSqlServer($"Server=.;Database={db.Value};Trusted_Connection=True;MultipleActiveResultSets=true;");
});
Upvotes: 1