Reputation: 31
I'm trying to get the database name from the request url because i have more than one database
so, i have x1.com, x2.com and x3.com i want the DbContext to generate the connection string from the url at the runtime
what i did is that
public SaaSZeroDbContext()
: base(GetConnectionStringFromUrl())
{
}
//function to get the connection string
private static string GetConnectionStringFromUrl()
{
var url = HttpContext.Current.Request.Url;
string connectionString = @"Data Source=.\MSSQLSERVER2016; Database={DatabaseName}; Integrated Security=True;MultipleActiveResultSets=True";
var databaseName = ""; // you should extract the database name from url here
connectionString = connectionString.Replace("{DatabaseName}", databaseName);
return connectionString;
}
but i'm getting error HttpContext.Current is null, why HttpContext.Current is null ?
Upvotes: 0
Views: 1511
Reputation: 2272
Your approach is not true by design. I will explain why.
This is the DbContext comes with the template:
public class AbpProjectNameDbContext : AbpZeroDbContext<Tenant, Role, User>
{
//TODO: Define an IDbSet for your Entities...
/* NOTE:
* Setting "Default" to base class helps us when working migration commands on Package Manager Console.
* But it may cause problems when working Migrate.exe of EF. If you will apply migrations on command line, do not
* pass connection string name to base classes. ABP works either way.
*/
public AbpProjectNameDbContext()
: base("Default")
{
}
/* NOTE:
* This constructor is used by ABP to pass connection string defined in AbpProjectNameDataModule.PreInitialize.
* Notice that, actually you will not directly create an instance of AbpProjectNameDbContext since ABP automatically handles it.
*/
public AbpProjectNameDbContext(string nameOrConnectionString)
: base(nameOrConnectionString)
{
}
//This constructor is used in tests
public AbpProjectNameDbContext(DbConnection existingConnection)
: base(existingConnection, false)
{
}
public AbpProjectNameDbContext(DbConnection existingConnection, bool contextOwnsConnection)
: base(existingConnection, contextOwnsConnection)
{
}
}
ABP uses the 2nd constructor (AbpProjectNameDbContext(string nameOrConnectionString)
) here, as stated in the comment. Default constructor is only used while you are working with PMC, like executing update-database.
So, you should add a constructor like
public SaaSZeroDbContext(string nameOrConnectionString)
: base(GetConnectionStringFromUrl())
{
}
This ctor will be called by ABP, but you ignore nameOrConnectionString and generating it dynamically. That's OK. But there is still a problem. When you use PMC commands (like update-database), HttpContext.Current will be null. Because this execution will not be in a HTTP Context. Also, in unit tests there is no HttpContext.Current and so on.
So, your GetConnectionStringFromUrl() method should be able to work even if HttpContext.Current is null. If it's null, you can return a default connection string, for example.
Although this approach can work, I don't suggest it. ASP.NET Boilerplate already provides a way of dynamically determining the connection string on the runtime.
Create a new class that implements IConnectionStringResolver. You can derive your class from DefaultConnectionStringResolver and override GetNameOrConnectionString method too. Thus you can call base.GetNameOrConnectionString if HttpContext.Current is null and your code fallbacks to default implementation.
Replace IConnectionStringResolver with your own implementation as documented here.
Upvotes: 1
Reputation: 100527
HttpContext.Current
and other request specific information is obviously available only when code handles request.
Most likely you are initializing your class at a time when there is no request processed even if it clearly needs to be per-request as it uses request's information to select connection string. Reasonable fix would be to align lifetime of SaaSZeroDbContext
to controller's lifetime (i.e. making SaaSZeroDbContext
per-request object injected by a DI framework).
There is another option when context is not available even if you believe you are handling request - using ConfirgureAwiat(false)
will lose access to request's objects after the call, but it does not look you got that far yet.
Security note: make sure user can't actually provide connections string via url, but rather gives your code hint on what existing connection string to use. Allowing user to control connection string (like ?dbname=FooBar
) is huge security issue (read on "SQL injection" for more info).
Upvotes: 0