Skip to content

Repository files navigation

Add a new Module and convert it to a microservice in ABP

In this post we will see how to create a modular abp application and convert it to microservice. We will add a new module to tiered abp app and then use the separate database to store the modules data and then convert the module to a microservice.

In this sample we will create Tiered app which is called MainApp. Then we will add a module called ProjectService.

Creating the abp application and run migrations

abp new MainApp -t app -u mvc --tiered

Run Migrations

Change directory to src/MainApp.DbMigrator and run the migration project

dotnet run

This will apply the migrations to the db and we can run the MainApp.Web project. This will host the UI and API..

Add a new Module

Now we will add a new module to our MainApp. Move back to the root folder of your project.

abp add-module ProjectService --new

This will create a ProjectService and it will be available in the modules folder.

Add and configure the host to the module

We need to add a host for our module. first we have to navigate to the the src folder of the Module.

cd .\modules\ProjectService\src\

Now lets create a Web Api project to host our module.

dotnet new webapi -n ProjectService.HttpApi.Host

Now open the ProjectService solution and the newly created Host project to the solution.

Update the appsettings.json with the following

{
"App": {
"SelfUrl": "{Host Url}",
"CorsOrigins": "https://*.MainApp.com"
},
"AuthServer": {
"Authority": "{ Identity Server Url }",
"RequireHttpsMetadata": "true",
"SwaggerClientId": "ProjectService_Swagger",
"SwaggerClientSecret": "1q2w3e*"
},
"ConnectionStrings": {
"ProjectService": "Server=(LocalDb)\\MSSQLLocalDB;Database=ProjectService;Trusted_Connection=True"
},
"Redis": {
"Configuration": "127.0.0.1"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

Add the following packages to the ProjectService.HttpApi.Host project.

<ItemGroup>
<PackageReferenceInclude="Serilog.AspNetCore"Version="4.1.0" />
<PackageReferenceInclude="Volo.Abp.EntityFrameworkCore"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.AspNetCore.MultiTenancy"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.Autofac"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.Core"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.EntityFrameworkCore.SqlServer"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.Swashbuckle"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.AspNetCore.Authentication.JwtBearer"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.AspNetCore.Serilog"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Serilog.Extensions.Logging"Version="3.0.1" />
<PackageReferenceInclude="Serilog.Sinks.Async"Version="1.4.0" />
<PackageReferenceInclude="Serilog.Sinks.File"Version="4.1.0" />
<PackageReferenceInclude="Serilog.Sinks.Console"Version="3.1.1" />
</ItemGroup>

These are the essential packages for our host.

Add ProjectService.Application, ProjectService.EntityFrameworkCore, ProjectService.HttpApi projects as a reference to your ProjectService.HttpApi.Host

We are adding the projects reference because the host project module will depend on the module from these projects.

Create a ProjectServiceHostModule in the newly created ProjectService.HttpApi.Host. This will be a abp module where we will setup the host.

Here is the sample for the ProductService.

usingMicrosoft.AspNetCore.Authentication.JwtBearer;usingMicrosoft.AspNetCore.Builder;usingMicrosoft.AspNetCore.Cors;usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Hosting;usingMicrosoft.OpenApi.Models;usingProjectService.EntityFrameworkCore;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingVolo.Abp;usingVolo.Abp.AspNetCore.MultiTenancy;usingVolo.Abp.AspNetCore.Mvc;usingVolo.Abp.AspNetCore.Serilog;usingVolo.Abp.Autofac;usingVolo.Abp.Modularity;usingVolo.Abp.Swashbuckle;namespaceProjectService.HttpApi.Host{[DependsOn(typeof(ProjectServiceHttpApiModule),typeof(ProjectServiceApplicationModule),typeof(ProjectServiceEntityFrameworkCoreModule),typeof(AbpAspNetCoreMultiTenancyModule),typeof(AbpAutofacModule),typeof(AbpAspNetCoreSerilogModule),typeof(AbpSwashbuckleModule))]publicclassProjectServiceHostModule:AbpModule{publicoverridevoidConfigureServices(ServiceConfigurationContextcontext){varconfiguration=context.Services.GetConfiguration();context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>{options.Authority=configuration["AuthServer:Authority"];options.RequireHttpsMetadata=Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);options.Audience="ProjectService";});context.Services.AddAbpSwaggerGenWithOAuth(configuration["AuthServer:Authority"],newDictionary<string,string>{{"ProjectService","ProjectService API"}},
options =>{options.SwaggerDoc("v1",newOpenApiInfo{Title="ProjectService API",Version="v1"});options.DocInclusionPredicate((docName,description)=>true);options.CustomSchemaIds(type =>type.FullName);});Configure<AbpAspNetCoreMvcOptions>(options =>{options.ConventionalControllers.Create(typeof(ProjectServiceApplicationModule).Assembly);});context.Services.AddCors(options =>{options.AddDefaultPolicy(builder =>{builder.WithOrigins(configuration["App:CorsOrigins"].Split(",",StringSplitOptions.RemoveEmptyEntries).Select(o =>o.Trim().RemovePostFix("/")).ToArray()).WithAbpExposedHeaders().SetIsOriginAllowedToAllowWildcardSubdomains().AllowAnyHeader().AllowAnyMethod().AllowCredentials();});});}publicoverridevoidOnApplicationInitialization(ApplicationInitializationContextcontext){varapp=context.GetApplicationBuilder();varenv=context.GetEnvironment();if(env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseCorrelationId();app.UseCors();app.UseAbpRequestLocalization();app.UseStaticFiles();app.UseRouting();app.UseAuthentication();app.UseAbpClaimsMap();app.UseMultiTenancy();app.UseAuthorization();app.UseSwagger();app.UseAbpSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json","ProjectService Service API");varconfiguration=context.ServiceProvider.GetRequiredService<IConfiguration>();options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]);options.OAuthClientSecret(configuration["AuthServer:SwaggerClientSecret"]);});app.UseAbpSerilogEnrichers();app.UseAuditing();app.UseUnitOfWork();app.UseConfiguredEndpoints();}}}

In this module the depends on section configures the modules that it depends on in the DependsOn section and we will configure the JWT auth and the swagger UI for the host.

Update the Program.cs

usingSystem;usingSystem.IO;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.Hosting;usingSerilog;usingSerilog.Events;namespaceProjectService{publicclassProgram{publicstaticintMain(string[]args){Log.Logger=newLoggerConfiguration()
#if DEBUG.MinimumLevel.Debug()
#else
.MinimumLevel.Information()
#endif
.MinimumLevel.Override("Microsoft",LogEventLevel.Information).MinimumLevel.Override("Microsoft.EntityFrameworkCore",LogEventLevel.Warning).Enrich.FromLogContext().WriteTo.Async(c =>c.File("Logs/logs.txt"))
#if DEBUG
.WriteTo.Async(c =>c.Console())
#endif
.CreateLogger();try{Log.Information("Starting ProjectService.Host.");CreateHostBuilder(args).Build().Run();return0;}catch(Exceptionex){Log.Fatal(ex,"Host terminated unexpectedly!");return1;}finally{Log.CloseAndFlush();}}internalstaticIHostBuilderCreateHostBuilder(string[]args)=>Host.CreateDefaultBuilder(args).ConfigureAppConfiguration(build =>{build.AddJsonFile("appsettings.secrets.json",optional:true);}).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>();}).UseAutofac().UseSerilog();}}

Program file is updated to use the serilog with enrichers.

Update the Startup.cs

usingMicrosoft.AspNetCore.Builder;usingMicrosoft.AspNetCore.Hosting;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Logging;usingProjectService.HttpApi.Host;namespaceProjectService{publicclassStartup{publicvoidConfigureServices(IServiceCollectionservices){services.AddApplication<ProjectServiceHostModule>();}publicvoidConfigure(IApplicationBuilderapp,IWebHostEnvironmentenv,ILoggerFactoryloggerFactory){app.InitializeApplication();}}}

In the startup file we are configuring our newly created Host module.

Create a HomeController.cs in the Controller folder and update wit the following text.

usingMicrosoft.AspNetCore.Mvc;usingVolo.Abp.AspNetCore.Mvc;namespaceProjectService.HttpApi.Host.Controllers{publicclassHomeController:AbpController{publicActionResultIndex(){returnRedirect("~/swagger");}}}

Now our default route will be swagger.

We are mostly done with the host and now we can create the AppService.

Add new Entity to the ProjectService

We are going to create a simple Project entity and create a AppService for that project. This section of the tutorial is based on the Quick Start Guide

We will create a new Entity inside the ProjectService.Domain called Project.

Create an Entity

Learn more about the Entity in the abp docs.

First step is to create an Entity. Create the Entity in the ProjectService.Domain project.

publicclassProject:Entity<Guid>{publicstringName{get;set;}}

Add Entity to EfCore

Learn more about the ef core in the abp docs.

Next is to add Entity to the EF Core. you will find the DbContext in the ProjectService.EntityFrameworkCore project. Add the DbSet to the DbContext

publicDbSet<Project>Projects{get;set;}

Configure Entity in EfCore

Make sure you have the updated the ef core global tool

Configuration is done in the DbContextModelCreatingExtensions class. This should be available in the ProjectService.EntityFrameworkCore project

builder.Entity<Project>(b =>{//Configure table & schema nameb.ToTable(ProjectServiceDbProperties.DbTablePrefix+"Projects",ProjectServiceDbProperties.Schema);b.ConfigureByConvention();});

Prepare for the migration

dotnet tool update --global dotnet-ef

Add necessary packages to the ProjectService.EntityFrameworkCore project.

<PackageReferenceInclude="Microsoft.EntityFrameworkCore.Design"Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReferenceInclude="Volo.Abp.EntityFrameworkCore"Version="5.0.0-rc.1" />
<PackageReferenceInclude="Volo.Abp.EntityFrameworkCore.SqlServer"Version="5.0.0-rc.1" />

We need to create a ProjectServiceDbContextFactory to support migrations. Check here for more info about this

publicclassProjectServiceDbContextFactory:IDesignTimeDbContextFactory<ProjectServiceDbContext>{publicProjectServiceDbContextCreateDbContext(string[]args){varbuilder=newDbContextOptionsBuilder<ProjectServiceDbContext>().UseSqlServer(GetConnectionStringFromConfiguration());returnnewProjectServiceDbContext(builder.Options);}privatestaticstringGetConnectionStringFromConfiguration(){returnBuildConfiguration().GetConnectionString(ProjectServiceDbProperties.ConnectionStringName);}privatestaticIConfigurationRootBuildConfiguration(){varbuilder=newConfigurationBuilder().SetBasePath(Path.Combine(Directory.GetCurrentDirectory(),$"..{Path.DirectorySeparatorChar}ProjectService.HttpApi.Host")).AddJsonFile("appsettings.json",optional:false);returnbuilder.Build();}}

This is needed for ef core migration to work. We are building the configuration by taking the appsetting.json from the ProjectService.HttpApi.Host.

Adding Migrations

Now the DbContextFactory is configured we can add the migrations.

Go the ProjectService.EntityFrameworkCore project in the terminal and create migrations.

To create migration run this command:

dotnet ef migrations add created_projects

Verify the migrations created in the migrations folder.

To update the database run this command

dotnet ef database update

Create a Entity Dto

Dto are placed in ProjectService.Application.Contracts project

publicclassProjectDto:EntityDto<Guid>{publicstringName{get;set;}}

Create a AppService interface

create IProjectAppService interface in the ProjectService.Application.Contracts project

publicinterfaceIProjectAppService:IApplicationService{Task<List<ProjectDto>>GetListAsync();Task<ProjectDto>CreateAsync(stringtext);TaskDeleteAsync(Guidid);}

Create an Application Services

Learn more about the Application Services in the abp docs.

Application service are created in the ProjectService.Application project

[Authorize]publicclassProjectAppService:ProjectServiceAppService,IProjectAppService{privatereadonlyIRepository<Project,Guid>projectRepository;publicProjectAppService(IRepository<Project,Guid>projectRepository){this.projectRepository=projectRepository;}publicasyncTask<ProjectDto>CreateAsync(stringtext){varprojectItem=awaitprojectRepository.InsertAsync(newProject{Name=text});returnnewProjectDto{Id=projectItem.Id,Name=projectItem.Name};}publicasyncTaskDeleteAsync(Guidid){awaitprojectRepository.DeleteAsync(id);}publicasyncTask<List<ProjectDto>>GetListAsync(){varitems=awaitprojectRepository.GetListAsync();returnitems.Select(item =>newProjectDto{Id=item.Id,Name=item.Name}).ToList();}}

Update the EntityFrameworkCoreModule

Update the ConfigureServices method in the ProjectServiceEntityFrameworkCoreModule file.

context.Services.AddAbpDbContext<ProjectServiceDbContext>(options =>{options.AddDefaultRepositories(includeAllEntities:true);});Configure<AbpDbContextOptions>(options =>{options.UseSqlServer();});

Create API Scope, API Resource and Swagger Client in IdentityServer

We need to do this in the MainApp. We have to update the IdentityServerDataSeedContributor in the MainApp.Domain.

privateasyncTaskCreateApiScopesAsync(){awaitCreateApiScopeAsync("MainApp");awaitCreateApiScopeAsync("ProjectService");}privateasyncTaskCreateApiResourcesAsync(){varcommonApiUserClaims=new[]{"email","email_verified","name","phone_number","phone_number_verified","role"};awaitCreateApiResourceAsync("MainApp",commonApiUserClaims);awaitCreateApiResourceAsync("ProjectService",commonApiUserClaims);}

Now lets update the create the swagger client for the new service.

In the MainApp.DbMigrator project update the appsettings.json with the new swagger client.

"ProjectService_Swagger":{"ClientId":"ProjectService_Swagger","ClientSecret":"1q2w3e*","RootUrl":"{ Your Service url }"}

Update the commonScopes in the CreateClientsAsync method

varcommonScopes=new[]{"email","openid","profile","role","phone","address","MainApp","ProjectService"};

This will add the newly created scope to all the clients.

Update the CreateClientsAsync method to create the swagger client in the IdentityServerDataSeedContributor in the MainApp.Domain.

varswaggerClientIdProjectService=configurationSection["ProjectService_Swagger:ClientId"];if(!swaggerClientIdProjectService.IsNullOrWhiteSpace()){varswaggerRootUrl=configurationSection["ProjectService_Swagger:RootUrl"].TrimEnd('/');awaitCreateClientAsync(name:swaggerClientIdProjectService,scopes:commonScopes,grantTypes:new[]{"authorization_code"},secret:configurationSection["ProjectService_Swagger:ClientSecret"]?.Sha256(),requireClientSecret:false,redirectUri:$"{swaggerRootUrl}/swagger/oauth2-redirect.html",corsOrigins:new[]{swaggerRootUrl.RemovePostFix("/")});}

Run Migrations again for the MainApp

Change directory to MainApp.DbMigrator and run the migration project

dotnet run

This will run the DbMigrator project. The DbMigrator will seed the database with the New Scope, API and Client in our Identity Server.

Once the migration is done lets update the CorsOrigins in the IdentityServer.

"App": {
"SelfUrl": "https://localhost:44373",
"ClientUrl": "http://localhost:4200",
"CorsOrigins": "https://*.MainApp.com,http://localhost:4200,https://localhost:44307,https://localhost:44358,https://localhost:44372,{ Project Service url }", // update the entry here"RedirectAllowedUrls": "http://localhost:4200,https://localhost:44307"
},

Communicating with the Microservice

Now we have the application running lets see how we can communicate between services.

To communicate with the we need to use the Client ProxyCheck docs here

Add the Contract project reference

We need to add ProjectService.Application.Contracts project as project reference to MainApp.HttpApi.Client

<ProjectReferenceInclude="..\..\modules\ProjectService\src\ProjectService.Application.Contracts\ProjectService.Application.Contracts.csproj" />

Update the MainAppHttpApiClientModule dependency and add the ProjectService as a client proxy.

typeof(ProjectServiceApplicationContractsModule)

Update the ConfigureServices service in the MainAppHttpApiClientModule.cs file in the MainApp.HttpApi.Client project.

//Create dynamic client proxiescontext.Services.AddHttpClientProxies(typeof(ProjectServiceApplicationContractsModule).Assembly,"ProjectService");

Now lets add Remote Service Endpoints to the appsettings.json in MainApp.Web

"RemoteServices": {
"Default": {
"BaseUrl": "https://localhost:44358/"
},
"ProjectService": {
"BaseUrl": "https://localhost:44372/"
}
}

Update the scope of the Web App

To connect to the project service we need to add the ProjectService scope as a scope to the AddAbpOpenIdConnect method inside the ConfigureAuthentication method in the MainAppWebModule

.AddAbpOpenIdConnect("oidc", options =>{options.Authority=configuration["AuthServer:Authority"];options.RequireHttpsMetadata=Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]);options.ResponseType=OpenIdConnectResponseType.CodeIdToken;options.ClientId=configuration["AuthServer:ClientId"];options.ClientSecret=configuration["AuthServer:ClientSecret"];options.SaveTokens=true;options.GetClaimsFromUserInfoEndpoint=true;options.Scope.Add("role");options.Scope.Add("email");options.Scope.Add("phone");options.Scope.Add("MainApp");options.Scope.Add("ProjectService");// This is for the new project service});

Create a UI to display the projects

Create a project page to display the project list in the MainApp.Web.

Create a Projects.cshtml

@page
@model MainApp.Web.Pages.ProjectsModel
<divclass="container"><H1>List of projects</H1>
@if(Model.Projects != null && Model.Projects.Count > 0){
@foreach (var item in Model?.Projects) {
<divclass="card"><divclass="card-body"><h5class="card-title">@item.Name</h5></div></div>
} }
</div>

Create a Projects.cshtml.cs

publicclassProjectsModel:MainAppPageModel{privatereadonlyILogger<ProjectsModel>logger;publicList<ProjectDto>Projects{get;set;}privateIProjectAppServiceprojectAppService{get;set;}publicProjectsModel(IProjectAppServiceprojectAppService,ILogger<ProjectsModel>logger){this.projectAppService=projectAppService;this.logger=logger;Projects=newList<ProjectDto>();}publicvoidOnGet(){try{varprojects=projectAppService.GetListAsync().Result;Projects=projects;}catch(Exceptione){logger.LogError(e.Message);}}}

Lets add the newly created page to the Main menu.

Update the ConfigureMainMenuAsync method in the MainAppMenuContributor.cs file.

context.Menu.Items.Insert(3,newApplicationMenuItem(MainAppMenus.Home,l["Projects"],"~/Projects",icon:"fas fa-list",order:0));

Now lets run the application.

Running the application

Since we have run 4 services lets init tye so that it will be easier.

tye init

This command will create the tye.yaml file which will have the project without out ports binding. Update the ports from your solution. Here is the sample tye.yaml.

name: mainappservices:
- name: mainapp-webproject: src/MainApp.Web/MainApp.Web.csprojbindings:
- port: 44343 //update your ports hereprotocol: https
- name: mainapp-identityserverproject: src/MainApp.IdentityServer/MainApp.IdentityServer.csprojbindings:
- port: 44373 //update your ports hereprotocol: https
- name: mainapp-httpapi-hostproject: src/MainApp.HttpApi.Host/MainApp.HttpApi.Host.csprojbindings:
- port: 44358 //update your ports hereprotocol: https
- name: project-serviceproject: modules/ProjectService/src/ProjectService.HttpApi.Host/ProjectService.HttpApi.Host.csprojbindings:
- port: 44372 //update your ports hereprotocol: https

Now run the tye command.

tye run

This will run all the projects. Navigate to the ProjectService and do the swagger authorization to login and call the project api to create new project.

To View the project visit the MainApp.Web project and click Projects menu to see the list of the project you just created.

About

Demo to create abp module and convert it into microservice.

Topics

Resources

Stars

15 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages