422.1 Abp microserices and Multi‐Tenancy - chempkovsky/CS82ANGULAR GitHub Wiki

Note
  • We are using Abp community edition
  • We discussed Multi-tenancy in articles
  • We used the XXX.DbMigrator.csproj project to create database schemas for each tenant that has a separate database connection string.
  • Now it's time to look into the code of the XXX.DbMigrator.csproj project and create something similar for the microservices solution.
    • First we will show that XXX.DbMigrator.csproj is only suitable for development phase, even for monolithic applications.
    • Finally, we will create a basic console application (similar to the XXX.DbMigrator.csproj project) that can be used in production for microservices applications.

Tools

Default DbMigrationService

  • Let's create new app with a command
abp new rupbes.testapp --template app --ui-framework angular --theme leptonx-lite --database-provider ef --database-management-system SqlServer --no-tests --create-solution-folder
  • open testappDbMigrationService.cs-file of the rupbes.testapp.Domain.csproj-project
    • please take a look at the GetEntityFrameworkCoreProjectFolderPath() and GetSolutionDirectoryPath()-methods:
Click to show the code
    private string? GetEntityFrameworkCoreProjectFolderPath()
    {
        var slnDirectoryPath = GetSolutionDirectoryPath();

        if (slnDirectoryPath == null)
        {
            throw new Exception("Solution folder not found!");
        }

        var srcDirectoryPath = Path.Combine(slnDirectoryPath, "src");

        return Directory.GetDirectories(srcDirectoryPath)
            .FirstOrDefault(d => d.EndsWith(".EntityFrameworkCore"));
    }

    private string? GetSolutionDirectoryPath()
    {
        var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());

        while (currentDirectory != null && Directory.GetParent(currentDirectory.FullName) != null)
        {
            currentDirectory = Directory.GetParent(currentDirectory.FullName);

            if (currentDirectory != null && Directory.GetFiles(currentDirectory.FullName).FirstOrDefault(f => f.EndsWith(".sln")) != null)
            {
                return currentDirectory.FullName;
            }
        }

        return null;
    }
  • So, the default implementation of DbMigrationService can only be used by developers.

Initial solution

  • Suppose we have a folder E:\Development
  • start cmd.exe and run the commands
E:
cd E:\Development
abp new rupbes.portal --template module --database-provider ef --database-management-system SqlServer --create-solution-folder --version 9.3.1 --old
  • We ignore all errors
  • Launch Abp Studio Community Edition and delete the following projects (as we are not working with Blazor and MongoDB)
    • rupbes.portal.Blazor.Host.Client
    • rupbes.portal.Blazor.Server.Host
    • rupbes.portal.Blazor.Host
    • rupbes.portal.Blazor
    • rupbes.portal.Blazor.Server
    • rupbes.portal.Blazor.WebAssembly
    • rupbes.portal.Blazor.WebAssembly.Bundling
    • rupbes.portal.MongoDB
    • rupbes.portal.Web.Unified
  • open appsettings.json of the rupbes.portal.HttpApi.Host.csproj-project. "ConnectionStrings" consists of two strings: "Default" and "portal".
 {
  "ConnectionStrings": {
    "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=portal_Main;Trusted_Connection=True;TrustServerCertificate=True",
    "portal": "Server=(LocalDb)\\MSSQLLocalDB;Database=portal_Module;Trusted_Connection=True;TrustServerCertificate=True"
  }
 }
  • The "Default" connection string will be used by the DBContext to manage tables of the following modules:
    • Permission
    • Setting
    • AuditLogging
    • Identity
    • Feature
    • Tenant
  • "portal" connection string will be used by the DBContext of application layer.

Create DbMigrator console app

  • Launch ABP Studio community edition and open rupbes.portal-solution
    • right click rupbes.portal/host-folder and choose Add/Package/New Package menu item
    • In the dialog select C# console application
      • Set "Package name" = rupbes.portal.DbMigrator
      • Click Create-button

Modify DbMigrator project file

  • Open solution with Visual Studio and modify rupbes.portal.DbMigrator.csproj-file as follows:
<Project Sdk="Microsoft.NET.Sdk">

	<PropertyGroup>
		<OutputType>Exe</OutputType>
		<TargetFramework>net9.0</TargetFramework>
		<ImplicitUsings>enable</ImplicitUsings>
		<Nullable>enable</Nullable>
	</PropertyGroup>

	<ItemGroup>
		<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
		<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0" />
		<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
		<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
		<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
	</ItemGroup>

	<ItemGroup>
		<PackageReference Include="Volo.Abp.Autofac" Version="9.3.1" />
	</ItemGroup>

</Project>
  • We removed <ImplicitUsings>enable</ImplicitUsings> from the <PropertyGroup>...</PropertyGroup> and we added two <ItemGroup>...</ItemGroup>.

Modify DbMigrator Program file

  • open Program.cs-file of the rupbes.portal.DbMigrator.csproj-project and modify as follows:
Click to show the code
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using Serilog.Events;

namespace rupbes.portal.DbMigrator;

class Program
{
    static async Task Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Information()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
            .MinimumLevel.Override("Volo.Abp", LogEventLevel.Warning)
#if DEBUG
                .MinimumLevel.Override("rupbes.testapp", LogEventLevel.Debug)
#else
                .MinimumLevel.Override("rupbes.testapp", LogEventLevel.Information)
#endif
                .Enrich.FromLogContext()
            .WriteTo.Async(c => c.File("Logs/logs.txt"))
            .WriteTo.Async(c => c.Console())
            .CreateLogger();

        await CreateHostBuilder(args).RunConsoleAsync();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .AddAppSettingsSecretsJson()
            .ConfigureLogging((context, logging) => logging.ClearProviders())
            .ConfigureServices((hostContext, services) =>
            {
                services.AddHostedService<DbMigratorHostedService>();
            });
}
  • We just copied the content of the Program.cs-file from the project and replaced the namespace with namespace rupbes.portal.DbMigrator;
    • services.AddHostedService<DbMigratorHostedService>(); is the only line of code that is still wrong.

Add appsettings to DbMigrator

  • Add appsettings.json to DbMigrator package
 {
  "ConnectionStrings": {
    "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=portal_Main;Trusted_Connection=True;TrustServerCertificate=True",
    "portal": "Server=(LocalDb)\\MSSQLLocalDB;Database=portal_Module;Trusted_Connection=True;TrustServerCertificate=True"
  }
 }
  • Add appsettings.secrets.json to DbMigrator package
 {
 }
  • modify rupbes.portal.DbMigrator.csproj-file as follows:
<Project Sdk="Microsoft.NET.Sdk">

        ...

	<ItemGroup>
	  <None Remove="appsettings.json" />
	  <None Remove="appsettings.secrets.json" />
	</ItemGroup>

	<ItemGroup>
	  <Content Include="appsettings.json">
	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
	  </Content>
	  <Content Include="appsettings.secrets.json">
	    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
	  </Content>
	</ItemGroup>

</Project>

Add DbMigratorHostedService to DbMigrator

  • Add a DbMigratorHostedService-class as follows:
Click to show the code
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using rupbes.portal.DbMigrator.Data;
using Serilog;
using Volo.Abp;
using Volo.Abp.Data;

namespace rupbes.portal.DbMigrator;

public class DbMigratorHostedService : IHostedService
{
    private readonly IHostApplicationLifetime _hostApplicationLifetime;
    private readonly IConfiguration _configuration;

    public DbMigratorHostedService(IHostApplicationLifetime hostApplicationLifetime, IConfiguration configuration)
    {
        _hostApplicationLifetime = hostApplicationLifetime;
        _configuration = configuration;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        using (var application = await AbpApplicationFactory.CreateAsync<DbMigratorModule>(options =>
        {
            options.Services.ReplaceConfiguration(_configuration);
            options.UseAutofac();
            options.Services.AddLogging(c => c.AddSerilog());
            options.AddDataMigrationEnvironment();
        }))
        {
            await application.InitializeAsync();

            await application
                .ServiceProvider
                .GetRequiredService<DbMigrationService>()
                .MigrateAsync();

            await application.ShutdownAsync();

            _hostApplicationLifetime.StopApplication();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}
  • We just copied the content of the ...HostedService.cs-file from the project , we replaced the namespace with namespace rupbes.portal.DbMigrator; and replaced using with using rupbes.portal.DbMigrator.Data;
  • We also changed the names: DbMigratorModule and DbMigrationService

Add DbMigratorModule to DbMigrator

  • DbMigratorHostedService-class references DbMigratorModule-class. Let's create DbMigratorModule
    • Add the DbMigratorModule-class to the rupbes.testapp.DbMigrator.csproj-project as follows:
Click to show the code
using Microsoft.Extensions.DependencyInjection;
using rupbes.portal.DbMigrator.EntityFrameworkCore;
using rupbes.portal.EntityFrameworkCore;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.Autofac;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.SqlServer;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.Modularity;
using Volo.Abp.OpenIddict.EntityFrameworkCore;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.TenantManagement.EntityFrameworkCore;

namespace rupbes.portal.DbMigrator
{

    [DependsOn(
        typeof(AbpAutofacModule),
        typeof(portalEntityFrameworkCoreModule),

        typeof(AbpOpenIddictEntityFrameworkCoreModule),
        typeof(AbpAuditLoggingEntityFrameworkCoreModule),
        typeof(AbpTenantManagementEntityFrameworkCoreModule),
        typeof(AbpFeatureManagementEntityFrameworkCoreModule),
        typeof(AbpPermissionManagementEntityFrameworkCoreModule),
        typeof(AbpIdentityEntityFrameworkCoreModule),
        typeof(AbpSettingManagementEntityFrameworkCoreModule),
        typeof(AbpEntityFrameworkCoreSqlServerModule)


    )]
    public class DbMigratorModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            base.ConfigureServices(context);

            context.Services.AddAbpDbContext<defaultMigratorDbContext>(options =>
            {
                
                options.AddDefaultRepositories(includeAllEntities: true);
                /* Add custom repositories here. Example:
                 * options.AddRepository<Question, EfCoreQuestionRepository>();
                 */
            });
            context.Services.AddAbpDbContext<portalMigratorDbContext>(options =>
            {
                options.AddDefaultRepositories(includeAllEntities: true);
                /* Add custom repositories here. Example:
                 * options.AddRepository<Question, EfCoreQuestionRepository>();
                 */
            });

            Configure<AbpDbContextOptions>(options =>
            {
                options.UseSqlServer();
            });


            /*
            PostConfigure<AbpDataSeedOptions>(options =>
            {
                options.Contributors.RemoveAll(x => x == typeof(IdentityDataSeedContributor));
            });
            */
        }

    }
}
  • Also we have to modify rupbes.portal.DbMigrator.csproj-file as follows:
<Project Sdk="Microsoft.NET.Sdk">
  ...
  <ItemGroup>
    <ProjectReference Include="../../src/rupbes.portal.EntityFrameworkCore/rupbes.portal.EntityFrameworkCore.csproj" />
    <ProjectReference Include="../rupbes.portal.Host.Shared/rupbes.portal.Host.Shared.csproj" />
  </ItemGroup>
</Project>

Add DbContexts to DbMigrator

  • Create EntityFrameworkCore-folder in the rupbes.portal.DbMigrator.csproj-project
    • In the EntityFrameworkCore-folder create portalMigratorDbContext-class as follows
Click to show the code
using Microsoft.EntityFrameworkCore;
using rupbes.portal.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;

namespace rupbes.portal.DbMigrator.EntityFrameworkCore
{
    [ConnectionStringName("portal")]
    public class portalMigratorDbContext : AbpDbContext<portalMigratorDbContext>
    {
        public portalMigratorDbContext(DbContextOptions<portalMigratorDbContext> options)
            : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Configureportal();
        }
    }
}
  • In the EntityFrameworkCore-folder create portalMigratorDbContextFactory-class as follows
Click to show the code
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;

namespace rupbes.portal.DbMigrator.EntityFrameworkCore
{
    public class portalMigratorDbContextFactory : IDesignTimeDbContextFactory<portalMigratorDbContext>
    {
        public portalMigratorDbContext CreateDbContext(string[] args)
        {
            var configuration = BuildConfiguration();

            var builder = new DbContextOptionsBuilder<portalMigratorDbContext>()
                .UseSqlServer(configuration.GetConnectionString("portal"));

            return new portalMigratorDbContext(builder.Options);
        }

        private static IConfigurationRoot BuildConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false);

            return builder.Build();
        }
    }
}
  • It requires modification of the rupbes.portal.DbMigrator.csproj-file as follows:
<Project Sdk="Microsoft.NET.Sdk">
  ...
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.5" />
    <PackageReference Include="Volo.Abp.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.EntityFrameworkCore.SqlServer" Version="9.3.1" />
  </ItemGroup>
</Project>

In the EntityFrameworkCore-folder create defaultMigratorDbContext-class as follows:

Click to show the code
using Microsoft.EntityFrameworkCore;
using Volo.Abp.AuditLogging.EntityFrameworkCore;
using Volo.Abp.Data;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.FeatureManagement.EntityFrameworkCore;
using Volo.Abp.Identity.EntityFrameworkCore;
using Volo.Abp.OpenIddict.EntityFrameworkCore;
using Volo.Abp.PermissionManagement.EntityFrameworkCore;
using Volo.Abp.SettingManagement.EntityFrameworkCore;
using Volo.Abp.TenantManagement.EntityFrameworkCore;

namespace rupbes.portal.DbMigrator.EntityFrameworkCore
{
    [ConnectionStringName("Default")]
    public class defaultMigratorDbContext : AbpDbContext<defaultMigratorDbContext>
    {
        public defaultMigratorDbContext(DbContextOptions<defaultMigratorDbContext> options)
            : base(options)
        {

        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.ConfigurePermissionManagement();
            modelBuilder.ConfigureSettingManagement();
            modelBuilder.ConfigureAuditLogging();
            modelBuilder.ConfigureIdentity();
            modelBuilder.ConfigureOpenIddict(); // must be removed for Keycloak
            modelBuilder.ConfigureFeatureManagement();
            modelBuilder.ConfigureTenantManagement();
        }

    }
}
  • It requires modification of the rupbes.portal.DbMigrator.csproj-file as follows:
<Project Sdk="Microsoft.NET.Sdk">
  ...
  <ItemGroup>
    <PackageReference Include="Volo.Abp.PermissionManagement.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.AuditLogging.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.SettingManagement.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.FeatureManagement.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.Identity.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.OpenIddict.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.TenantManagement.EntityFrameworkCore" Version="9.3.1" />
    <PackageReference Include="Volo.Abp.TenantManagement.Application" Version="9.3.1" />
  </ItemGroup>
</Project>
  • In the EntityFrameworkCore-folder create defaultMigratorDbContextFactory-class as follows
Click to show the code
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;

namespace rupbes.portal.DbMigrator.EntityFrameworkCore
{
    public class defaultMigratorDbContextFactory : IDesignTimeDbContextFactory<defaultMigratorDbContext>
    {
        public defaultMigratorDbContext CreateDbContext(string[] args)
        {
            var configuration = BuildConfiguration();

            var builder = new DbContextOptionsBuilder<defaultMigratorDbContext>()
                .UseSqlServer(configuration.GetConnectionString("Default"));

            return new defaultMigratorDbContext(builder.Options);
        }

        private static IConfigurationRoot BuildConfiguration()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false);

            return builder.Build();
        }
    }
}

Add DbSchemaMigrator to DbMigrator

  • Create Data-folder in the rupbes.portal.DbMigrator.csproj-project
    • In the Data-folder create IDbSchemaMigrator-interface as follows:
Click to show the code
namespace rupbes.portal.DbMigrator.Data
{
    public interface IDbSchemaMigrator
    {
        Task MigrateAsync();
    }
}
  • In the Data-folder create DbSchemaMigrator-class as follows:
Click to show the code
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using rupbes.portal.DbMigrator.EntityFrameworkCore;
using Volo.Abp.DependencyInjection;

namespace rupbes.portal.DbMigrator.Data
{
    public class DbSchemaMigrator : IDbSchemaMigrator, ITransientDependency
    {
        private readonly IServiceProvider _serviceProvider;

        public DbSchemaMigrator(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider;
        }

        public async Task MigrateAsync()
        {
            /* We intentionally resolving the testappDbContext
             * from IServiceProvider (instead of directly injecting it)
             * to properly get the connection string of the current tenant in the
             * current scope.
             */
            
            await _serviceProvider
                .GetRequiredService<defaultMigratorDbContext>()
                .Database
                .MigrateAsync();
            
            await _serviceProvider
                .GetRequiredService<portalMigratorDbContext>()
                .Database
                .MigrateAsync();
            
        }

    }
}

Add DbMigrationService to DbMigrator

  • Create Data-folder in the rupbes.portal.DbMigrator.csproj-project
    • In the Data-folder create DbMigrationService-class as follows:
Click to show the code
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using rupbes.portal.MultiTenancy;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Identity;
using Volo.Abp.MultiTenancy;
using Volo.Abp.TenantManagement;

namespace rupbes.portal.DbMigrator.Data
{
    public class DbMigrationService : ITransientDependency
    {
        public ILogger<DbMigrationService> Logger { get; set; }

        private readonly IDataSeeder _dataSeeder;
        private readonly IEnumerable<IDbSchemaMigrator> _dbSchemaMigrators;
        private readonly ITenantRepository _tenantRepository;
        private readonly ICurrentTenant _currentTenant;

        public DbMigrationService(
            IDataSeeder dataSeeder,
            ITenantRepository tenantRepository,
            ICurrentTenant currentTenant,
            IEnumerable<IDbSchemaMigrator> dbSchemaMigrators)
        {
            _dataSeeder = dataSeeder;
            _tenantRepository = tenantRepository;
            _currentTenant = currentTenant;
            _dbSchemaMigrators = dbSchemaMigrators;

            Logger = NullLogger<DbMigrationService>.Instance;
        }


        public async Task MigrateAsync()
        {

            Logger.LogInformation("Started database migrations...");

            await MigrateDatabaseSchemaAsync();
            await SeedDataAsync();

            Logger.LogInformation($"Successfully completed host database migrations.");

            if (MultiTenancyConsts.IsEnabled)
            {

                var tenants = await _tenantRepository.GetListAsync(includeDetails: true);

                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.");
                }

                Logger.LogInformation("Successfully completed all database migrations.");
            }
            Logger.LogInformation("You can safely end this process...");
        }

        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,
                    IdentityDataSeedContributor.AdminEmailDefaultValue)
                .WithProperty(IdentityDataSeedContributor.AdminPasswordPropertyName,
                    IdentityDataSeedContributor.AdminPasswordDefaultValue)
            );
        }

    }
}

Create Databases

  • Launch cmd.exe and run the following commands
e:
cd E:\development\rupbes.portal\host\rupbes.portal.DbMigrator
dotnet ef migrations add Initial --context portalMigratorDbContext --output-dir Migrations/portal
dotnet ef migrations add Initial --context defaultMigratorDbContext --output-dir Migrations/default
  • build and launch rupbes.portal.DbMigrator-console application. Two databases will be crated.

DataSeeders

  • We have discussed DataSeeders in previous articles.

Modifying DbMigrator after adding a new module

  • Let's say we need to add Abp CmsKit to the solution.
    • We launch cmd.exe and run the following commands:
e:
cd E:\development\rupbes.portal
abp add-module Volo.CmsKit --skip-db-migrations
  • Next we add portalGlobalFeatureConfigurator-class into rupbes.portal.Domain.Shared.csproj-project.
    • cmsKit.EnableAll(); - just for example
Click to show the code
using Volo.Abp.GlobalFeatures;
using Volo.Abp.Threading;

namespace rupbes.portal
{
    public static class portalGlobalFeatureConfigurator
    {
        private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();

        public static void Configure()
        {
            OneTimeRunner.Run(() =>
            {
                /* You can configure (enable/disable) global features of the used modules here.
                 * Please refer to the documentation to learn more about the Global Features System:
                 * https://docs.abp.io/en/abp/latest/Global-Features
                 */
            });
            GlobalFeatureManager.Instance.Modules.CmsKit(cmsKit =>
            {
                cmsKit.EnableAll();
            });
        }

    }
}
  • In portalDomainSharedModule.cs-class of rupbes.portal.Domain.Shared.csproj-project we add PreConfigureServices- method
Click to show the code
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        portalGlobalFeatureConfigurator.Configure();
    }

In the CreateDbContext()-method of portalMigratorDbContextFactory.cs-class of rupbes.portal.DbMigrator.csproj-project we add a line of code:

Click to show the code
        public portalMigratorDbContext CreateDbContext(string[] args)
        {
            var configuration = BuildConfiguration();

            portalGlobalFeatureConfigurator.Configure();

            var builder = new DbContextOptionsBuilder<portalMigratorDbContext>()
                .UseSqlServer(configuration.GetConnectionString("portal"));

            return new portalMigratorDbContext(builder.Options);
        }

In DbMigratorModule.cs-class of rupbes.portal.DbMigrator.csproj-project we add a method

Click to show the code
        public override void PreConfigureServices(ServiceConfigurationContext context)
        {
            portalGlobalFeatureConfigurator.Configure();
        }

In OnModelCreating(ModelBuilder modelBuilder)-method of portalMigratorDbContext.cs-class of rupbes.portal.DbMigrator.csproj-project we add a line of code

Click to show the code
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Configureportal();
            modelBuilder.ConfigureCmsKit();
        }
  • Read this first
    • In appsettings.json of rupbes.portal.DbMigrator.csproj-project we add "CmsKit"-connection string which is equal to "portal"-connection string
{
  "ConnectionStrings": {
    "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=portal_Main;Trusted_Connection=True;TrustServerCertificate=True",
    "portal": "Server=(LocalDb)\\MSSQLLocalDB;Database=portal_Module;Trusted_Connection=True;TrustServerCertificate=True",
    "CmsKit": "Server=(LocalDb)\\MSSQLLocalDB;Database=portal_Module;Trusted_Connection=True;TrustServerCertificate=True"
  }
}
  • Launch cmd.exe and run the following commands
e:
cd E:\development\rupbes.portal\host\rupbes.portal.DbMigrator
dotnet ef migrations add CmsKitAdded --context portalMigratorDbContext --output-dir Migrations/portal
  • build and launch rupbes.portal.DbMigrator-console application. CmsKit tables will be crated.
⚠️ **GitHub.com Fallback** ⚠️