-
Notifications
You must be signed in to change notification settings - Fork 161
SetupAspNetCoreAndDatabase
The SetupAspNetCoreAndDatabase
extension method used to register / configure the AuthP library is designed to migrate / seed databases on startup of the ASP.NET Core. In AuthP version 1 there was a limitation that this wouldn't work if your application has multiple instances of the applciation were running at the same time (known as Scale Out in Azure and Horizontally Scaling in Amazon).
Version 2 of the AuthP library fixed this limitation by using the Net.RunMethodsSequentially library uses a lock on a global resource (i.e. an resource all the app instances can access) which means your startup code are executed serially, and never in parallel.
The SetupAspNetCoreAndDatabase
needs a global resource to create a lock on, and a database is a good source. But there are some cases that the database doesn't exist, so we need a second global resource to use if the database isn't there. chosen as a FileSystem Directory as the backup, and in ASP.NET Core I use wwwRoot directory found by IWebHostEnvironment.WebRootPath
.
You have to provide the database connection string and the FilePath to the ASP.NET Core's wwwRoot directory via the options part of the RegisterAuthPermissions
extension method - see the setup code below, which was taken from Example5's Startup class, but the bulk load parts were removed as most people won't use them.
public void ConfigureServices(IServiceCollection services)
{
var connectionString = _configuration.GetConnectionString("DefaultConnection");
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(_configuration.GetSection("AzureAd"));
services.AddControllersWithViews();
services.AddRazorPages()
.AddMicrosoftIdentityUI();
//Needed by the SyncAzureAdUsers code
services.Configure<AzureAdOptions>(_configuration.GetSection("AzureAd"));
services.RegisterAuthPermissions<Example5Permissions>(options =>
{
options.PathToFolderToLock = _env.WebRootPath;
})
.AzureAdAuthentication(AzureAdSettings.AzureAdDefaultSettings(false))
.UsingEfCoreSqlServer(connectionString)
.RegisterAuthenticationProviderReader<SyncAzureAdUsers>()
.SetupAspNetCoreAndDatabase();
}
In this case the SetupAspNetCoreAndDatabase
will execute an migration of the AuthP's database on the startup of the application.
NOTE: This example comes from the older Startup
class approach. If you want to see an example of how you would get the connection string and the WebRootPath
in a Net6' 'Program' class approach, then have a look at the 'Program' class with the connection / WebRootPath
lines highlighted.
It you are SURE that you won't have multiple instances, then you can set the AuthP' option UseLocksToUpdateGlobalResources
property to false. This tells the Net.RunMethodsSequentially library that it can run the startup services without obtaining a global lock. See Example2's Startup class for an example of setting the UseLocksToUpdateGlobalResources
property to false.
In cases where you want to migrate and/or seed your own database on startup, then you can add extra startup services to the Net.RunMethodsSequentially inside the SetupAspNetCoreAndDatabase
method. The Net.RunMethodsSequentially will run each of your startup services (and the AuthP startup services) within global lock. This means if you have multiple instances of your app the startup services in each instance can't run at the same time as other startup services in another instance. But remember - each instance WILL run the startup services, so make sure your startup services check if the database has already been updated.
If you want to create a startup service you need to create a class that inherits the IStartupServiceToRunSequentially
interface. I recommend you read this section of the article about the Net.RunMethodsSequentially library which covers this in detail. You can also find a number of implemented startup services in the AuthP's StartupSevices directory that might help you.
If you want to run EF Core's MigrateAsync
method on startup on your DbContext, then there is a generic startup service called StartupServiceMigrateAnyDbContext<TContext>
where you can provide your DbContext class.
When using the AuthP library you often have multiple DbContexts, e.g. in Example3 it has 1) the individual users account DbContext, 2) the AuthP DbContext, and 3) the tenant DbContext. In this case you MUST ensure each DbContext has a unique named migration table. You can set the name of the DbContext's migration table via the MigrationsHistoryTable
extension method.
The AuthP's registering code includes a call to the MigrationsHistoryTable
when registering the AuthP's Dbcontext, but you need to add a MigrationsHistoryTable
when registering your tenant data DbContext - see the code below taken from Example3 that sets up the tenant DbContext.
services.AddDbContext<InvoicesDbContext>(options =>
options.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"), dbOptions =>
dbOptions.MigrationsHistoryTable(InvoicesDbContextHistoryName)));
To register your extra startup services you need to use the RegisterServiceToRunInJob<TService> inside the
SetupAspNetCoreAndDatabase` options.
The code below is taken from Example3's Startup class and shows the SetupAspNetCoreAndDatabase
method where you can add four extra startup services that will be run on startup.
services.RegisterAuthPermissions<Example3Permissions>(options =>
{
options.TenantType = TenantTypes.SingleLevel;
options.AppConnectionString = connectionString;
options.PathToFolderToLock = _env.WebRootPath;
})
//NOTE: This uses the same database as the individual accounts DB
.UsingEfCoreSqlServer(connectionString)
.IndividualAccountsAuthentication()
.RegisterTenantChangeService<InvoiceTenantChangeService>()
.AddRolesPermissionsIfEmpty(Example3AppAuthSetupData.RolesDefinition)
.AddTenantsIfEmpty(Example3AppAuthSetupData.TenantDefinition)
.AddAuthUsersIfEmpty(Example3AppAuthSetupData.UsersRolesDefinition)
.RegisterFindUserInfoService<IndividualAccountUserLookup>()
.RegisterAuthenticationProviderReader<SyncIndividualAccountUsers>()
.AddSuperUserToIndividualAccounts()
.SetupAspNetCoreAndDatabase(options =>
{
//Migrate individual account database
options.RegisterServiceToRunInJob<StartupServiceMigrateAnyDbContext<ApplicationDbContext>>();
//Add demo users to the database (if no individual account exist)
options.RegisterServiceToRunInJob<StartupServicesIndividualAccountsAddDemoUsers>();
//Migrate the application part of the database
options.RegisterServiceToRunInJob<StartupServiceMigrateAnyDbContext<InvoicesDbContext>>();
//This seeds the invoice database (if empty)
options.RegisterServiceToRunInJob<StartupServiceSeedInvoiceDbContext>();
});
NOTE: This runs 6 startup services: the four you can see being added manually via the SetupAspNetCoreAndDatabase
options, plus the migration of the AuthP DbContext and the running of the bulk load setup service that seeds the database with the example Roles, Tenants and AuthP users.
Some startup services have to be run in a certain order, for instance if you wish to seed a database, then should first migrate the database. The order in which startup services is defined by the startup service's OrderNum
property. The OrderNum
defines define the order you want your startup services are run, but if startup services has the same OrderNum value, then the startup services will run in the order they were registered.
Within the AuthP the built-in services the OrderNum
is as follows
-
StartupServiceMigrateAnyDbContext<TContext>
has anOrderNum
of -10, so should run first. NOTE: there may be multiple services using the startup service, but they shouldn't interfere with each other. -
StartupServiceMigrateAuthPDatabase
has anOrderNum
of -5. This runs after any other migration, but before any bulk load startup services. -
StartupServiceIndividualAccountsAddSuperUser<TIdentityUser>
has anOrderNum
of -1. This must be after migrations, and after the adding demo users startup service -
StartupServiceBulkLoadAuthPInfo
has anOrderNum
of 0, so it runs after theStartupServiceMigrateAuthPDatabase
startup service.
NOTE: I have left out some of the startup services that are used to set up demo data, such as StartupServicesIndividualAccountsAddDemoUsers
from this list.
So, if you are using the generic StartupServiceMigrateAnyDbContext<TContext>
to migrate your database (which has an OrderNum
of -10), then your seed database startup service can have a OrderNum
of 0 (default value) and will be run after the migration.
- Intro to multi-tenants (ASP.NET video)
- Articles in date order:
- 0. Improved Roles/Permissions
- 1. Setting up the database
- 2. Admin: adding users and tenants
- 3. Versioning your app
- 4. Hierarchical multi-tenant
- 5. Advanced technique with claims
- 6. Sharding multi-tenant setup
- 7. Three ways to add new users
- 8. The design of the sharding data
- 9. Down for maintenance article
- 10: Three ways to refresh claims
- 11. Features of Multilingual service
- 12. Custom databases - Part1
- Videos (old)
- Authentication explained
- Permissions explained
- Roles explained
- AuthUser explained
- Multi tenant explained
- Sharding explained
- How AuthP handles sharding
- How AuthP handles errors
- Languages & cultures explained
- JWT Token refresh explained
- Setup Permissions
- Setup Authentication
- Startup code
- Setup the custom database feature
- JWT Token configuration
- Multi tenant configuration
- Using Permissions
- Using JWT Tokens
- Creating a multi-tenant app
- Supporting multiple languages
- Unit Test your AuthP app