diff --git a/docs/en/Community-Articles/2025-02-12-Migrate-DB-From-Your-Web-Project/Post.md b/docs/en/Community-Articles/2025-02-12-Migrate-DB-From-Your-Web-Project/Post.md new file mode 100644 index 0000000000..0b950f7494 --- /dev/null +++ b/docs/en/Community-Articles/2025-02-12-Migrate-DB-From-Your-Web-Project/Post.md @@ -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 _dbSchemaMigrators; + + public MigrationController( + IDataSeeder dataSeeder, + ITenantRepository tenantRepository, + ICurrentTenant currentTenant, + IEnumerable dbSchemaMigrators) + { + _dataSeeder = dataSeeder; + _tenantRepository = tenantRepository; + _currentTenant = currentTenant; + _dbSchemaMigrators = dbSchemaMigrators; + } + + [HttpGet] + [Route("migrate")] + public async Task 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(); + 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 😊 \ No newline at end of file diff --git a/docs/en/Community-Articles/2025-02-12-Migrate-DB-From-Your-Web-Project/cover.png b/docs/en/Community-Articles/2025-02-12-Migrate-DB-From-Your-Web-Project/cover.png new file mode 100644 index 0000000000..94c7f3d98c Binary files /dev/null and b/docs/en/Community-Articles/2025-02-12-Migrate-DB-From-Your-Web-Project/cover.png differ