mirror of https://github.com/abpframework/abp.git
2 changed files with 181 additions and 0 deletions
@ -0,0 +1,181 @@ |
|||
# Migrate Your DB from the Web Application — Adding a DB Migration Controller in ABP Framework |
|||
|
|||
In some scenarios, you might prefer to **update your ABP project's database by simply visiting a URL** rather than running a console application (such as a typical `DbMigrator` project). This post demonstrates how to do that in an ABP application. In my sample application I used the single-layer project but you can benefit the same approach for other templates. My sample project name was created with this name: **Acme.BookStore**. |
|||
|
|||
------ |
|||
|
|||
## Step 1: Remove the Existing Localization Middleware |
|||
|
|||
Open your web project's module class file—this is typically in `BookStoreModule.cs` if you’re using the no-layer template. |
|||
Or if your project is multi-layered, it's `BookStoreWebModule.cs`. Find the below line and **remove** it: |
|||
|
|||
```csharp |
|||
app.UseAbpRequestLocalization(); |
|||
``` |
|||
|
|||
The reason is that the localization middleware depends on the database, which does not yet exist! |
|||
We'll add this middleware conditionally in the next step. |
|||
|
|||
------ |
|||
|
|||
## Step 2: Conditionally Use the Localization Middleware |
|||
|
|||
We only want `AbpRequestLocalizationMiddleware` for routes **other than** our new migration endpoint (`/api/migrate`). To achieve this, we use [Middleware Branching](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/). Our new controller will work on `/api/migrate` endpoint, so let's add a condition for this specific URL. |
|||
|
|||
```csharp |
|||
app.UseWhen(httpContext => |
|||
!httpContext.Request.Path.StartsWithSegments("/api/migrate", StringComparison.OrdinalIgnoreCase), |
|||
appBuilder => |
|||
{ |
|||
appBuilder.UseAbpRequestLocalization(); |
|||
} |
|||
); |
|||
``` |
|||
|
|||
This ensures all requests except `/api/migrate` will still use `AbpRequestLocalization` middleware. |
|||
|
|||
------ |
|||
|
|||
## Step 3: Create the Migration Controller |
|||
|
|||
Create a new file named `MigrationController.cs` under your `Controllers` folder or wherever you store controllers. |
|||
Then copy-paste the following class: |
|||
|
|||
```csharp |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Saas.Tenants; |
|||
|
|||
namespace Acme.BookStore.Web.Controllers //TODO: you may need to fix this namespace! |
|||
{ |
|||
[Route("api")] |
|||
[IgnoreAntiforgeryToken] |
|||
public class MigrationController : AbpController |
|||
{ |
|||
private const string DefaultAdminEmail = "admin@admin.com"; |
|||
private const string DefaultAdminPassword = "My-Admin-Password"; |
|||
|
|||
private readonly IDataSeeder _dataSeeder; |
|||
private readonly ITenantRepository _tenantRepository; |
|||
private readonly ICurrentTenant _currentTenant; |
|||
private readonly IEnumerable<DbSchemaMigrator> _dbSchemaMigrators; |
|||
|
|||
public MigrationController( |
|||
IDataSeeder dataSeeder, |
|||
ITenantRepository tenantRepository, |
|||
ICurrentTenant currentTenant, |
|||
IEnumerable<DbSchemaMigrator> dbSchemaMigrators) |
|||
{ |
|||
_dataSeeder = dataSeeder; |
|||
_tenantRepository = tenantRepository; |
|||
_currentTenant = currentTenant; |
|||
_dbSchemaMigrators = dbSchemaMigrators; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("migrate")] |
|||
public async Task<IActionResult> MigrateAsync(CancellationToken cancellationToken) |
|||
{ |
|||
try |
|||
{ |
|||
Logger.LogInformation("Started database migrations..."); |
|||
|
|||
await MigrateDatabaseSchemaAsync(); |
|||
await SeedDataAsync(); |
|||
|
|||
Logger.LogInformation("Successfully completed host database migrations. Started migrating tenant databases..."); |
|||
|
|||
if (_currentTenant.IsAvailable) // or check MultiTenancyConsts.IsEnabled |
|||
{ |
|||
await MigrateTenantDatabases(cancellationToken); |
|||
} |
|||
|
|||
Logger.LogInformation("Successfully completed all database migrations."); |
|||
return Ok("Migration and seed completed successfully."); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return BadRequest($"An error occurred during migration: {ex.Message}"); |
|||
} |
|||
} |
|||
|
|||
private async Task MigrateTenantDatabases(CancellationToken cancellationToken) |
|||
{ |
|||
var tenants = await _tenantRepository.GetListAsync(includeDetails: true, cancellationToken: cancellationToken); |
|||
|
|||
var migratedDatabaseSchemas = new HashSet<string>(); |
|||
foreach (var tenant in tenants) |
|||
{ |
|||
using (_currentTenant.Change(tenant.Id)) |
|||
{ |
|||
if (tenant.ConnectionStrings.Any()) |
|||
{ |
|||
var tenantConnectionStrings = tenant.ConnectionStrings |
|||
.Select(x => x.Value) |
|||
.ToList(); |
|||
|
|||
if (!migratedDatabaseSchemas.IsSupersetOf(tenantConnectionStrings)) |
|||
{ |
|||
await MigrateDatabaseSchemaAsync(tenant); |
|||
migratedDatabaseSchemas.AddIfNotContains(tenantConnectionStrings); |
|||
} |
|||
} |
|||
|
|||
await SeedDataAsync(tenant); |
|||
} |
|||
|
|||
Logger.LogInformation($"Successfully completed {tenant.Name} tenant database migrations."); |
|||
} |
|||
} |
|||
|
|||
private async Task MigrateDatabaseSchemaAsync(Tenant tenant = null) |
|||
{ |
|||
Logger.LogInformation($"Migrating schema for {(tenant == null ? "host" : tenant.Name + " tenant")} database..."); |
|||
foreach (var migrator in _dbSchemaMigrators) |
|||
{ |
|||
await migrator.MigrateAsync(); |
|||
} |
|||
} |
|||
|
|||
private async Task SeedDataAsync(Tenant tenant = null) |
|||
{ |
|||
Logger.LogInformation($"Executing {(tenant == null ? "host" : tenant.Name + " tenant")} database seed..."); |
|||
|
|||
await _dataSeeder.SeedAsync( |
|||
new DataSeedContext(tenant?.Id) |
|||
.WithProperty(IdentityDataSeedContributor.AdminEmailPropertyName, DefaultAdminEmail) |
|||
.WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName, DefaultAdminPassword) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
|
|||
|
|||
------ |
|||
|
|||
|
|||
|
|||
## Step 4: Navigate to the Migration URL |
|||
|
|||
Build and run your web application. Then visit: |
|||
|
|||
``` |
|||
https://your-website.com/api/migrate |
|||
``` |
|||
|
|||
If your database did not exist, it will be created, and the default data will be seeded. If you enabled multi-tenancy, the tenant databases will also be migrated in this solution. |
|||
|
|||
------ |
|||
|
|||
|
|||
|
|||
**That’s all there is to it!** By adding a simple controller and selectively applying the localization middleware, you can migrate your ABP app’s database with a single URL. |
|||
|
|||
|
|||
|
|||
Happy coding 😊 |
|||
|
After Width: | Height: | Size: 988 KiB |
Loading…
Reference in new issue