-
Notifications
You must be signed in to change notification settings - Fork 0
Docs (ENG)
- To quickly deploy a microservice, you can use my project for initializing a
.NET 8 Web API Onion Architecture Microservice
: NET8-Onion-Architecture-Microservice-Template. - For ease of use, I recommend installing all three libraries into the project right away, as each of them may prove useful to some extent.
- Before adding the libraries, it is advised to remove the automatically added NuGet package
Swashbuckle.AspNetCore
in theASP .NET 8 Web API
project, as this NuGet package is included inAspNetCoreMicroserviceInitializer.TradingDesk
. - To configure the microservice (its backend) and add modules from the library, a new class,
WebApplicationFacade
, is used. This class acts as a "wrapper" forWebApplication
andWebApplicationBuilder
, allowing for flexible configuration of desired modules.
public class Program
{
public static void Main(string[] args)
{
var modules = new List<WebApplicationModules>
{
WebApplicationModules.Database,
WebApplicationModules.Settings,
WebApplicationModules.Services,
WebApplicationModules.Serilog,
WebApplicationModules.HealthChecks
};
var dockerComposeModules = new List<DockerComposeFileModules>
{
DockerComposeFileModules.Server,
DockerComposeFileModules.PostgreSql,
DockerComposeFileModules.Adminer
};
var app = new WebApplicationFacade(modules)
.InitBaseConfig()
.InitBaseDockerComposeFiles(dockerComposeModules)
.AddAdditionalModules(builder =>
{
builder.Services.AddGrpc();
builder.Services.AddScoped<ICatsFactRepository, CatsFactRepository>();
})
.CreateApplication();
app.MapGrpcServices();
app.Run();
}
}
- All modules that can be added to the microservice using the libraries, as well as the conditions for their addition ("summary" and "remarks"), are described below:
/// <summary>
/// Web application modules.
/// </summary>
public enum WebApplicationModules
{
/// <summary>
/// Module for automatic registration of configuration settings.
///
/// For the correct operation of this module, it is necessary:
/// 1. Add a configuration settings model to the application.
/// 2. Create a configuration element for the settings model (the class name and configuration element name must match).
/// 3. Assign the <see cref="AutoRegisterConfigSettingsAttribute"/> attribute to the settings models.
/// </summary>
/// <remarks>Models will be registered automatically using the <see cref="AutoRegisterConfigSettingsAttribute"/> attribute.</remarks>
Settings = 0,
/// <summary>
/// Module for automatic registration of services.
///
/// For the correct operation of this module, it is necessary:
/// 1. Create a service and assign the <see cref="AutoRegisterServiceAttribute"/> attribute to it.
/// </summary>
/// <remarks>
/// If a factory function needs to be added during service registration, inherit the created service from <see cref="ServiceBase"/>,
/// and override the <see cref="ServiceBase.ImplementationFactory"/> method in the created service.
/// </remarks>
Services = 1,
/// <summary>
/// Module for adding SQL database functionality to the application.
///
/// For the correct operation of this module, it is necessary:
/// 1. Create <see cref="DbContext"/> models.
/// 2. Create repositories for working with <see cref="DbContext"/>.
/// 3. Assign the <see cref="AutoRegisterDbContextAttribute"/> attribute to <see cref="DbContext"/> models.
/// 4. Assign the <see cref="AutoRegisterRepositoryAttribute"/> attribute to repository models.
/// </summary>
/// <remarks>Models will be registered automatically using the <see cref="AutoRegisterDbContextAttribute"/> and <see cref="AutoRegisterRepositoryAttribute"/> attributes (repository registration is done as AddScoped).</remarks>
SqlDatabase = 2,
/// <summary>
/// Module for automatic registration of HealthChecks.
///
/// For the correct operation of this module, it is necessary:
/// 1. Add HealthChecks classes to the application, inheriting them from <see cref="IHealthCheck"/> and assigning the <see cref="AutoRegisterHealthCheckAttribute"/> attribute to them.
/// 2. Create a configuration element for the Health Checks settings model <see cref="HealthChecksSettings"/>.
/// </summary>
/// <remarks>1. Models will be registered automatically using the <see cref="IHealthCheck"/> interface and the <see cref="AutoRegisterHealthCheckAttribute"/> attribute.
/// 2. If the <see cref="HealthChecksSettings.UIEnable"/> parameter is enabled in the configuration settings, the UI can be accessed at the URL: /healthchecks-ui.</remarks>
HealthChecks = 3,
/// <summary>
/// Module for automatic registration of <see cref="AutoMapper"/>.
///
/// For the correct operation of this module, it is necessary:
/// 1. Create a base model.
/// 2. Create a DTO model.
/// 3. Create a profile inheriting from <see cref="Profile"/> for mapping the models.
/// 4. Assign the <see cref="AutoRegisterProfileAttribute"/> attribute to the DTO model and pass the required model types as parameters.
/// </summary>
/// <remarks>Models will be registered automatically using the <see cref="AutoRegisterProfileAttribute"/> attribute.</remarks>
AutoMappers = 4,
/// <summary>
/// Module for automatic registration of CORS policies.
///
/// For the correct operation of this module, it is necessary:
/// 1. Create a configuration element for the <see cref="CorsSettings"/> settings model.
/// </summary>
Cors = 5,
/// <summary>
/// Module for working with Hangfire background tasks.
///
/// For the correct operation of this module, it is necessary:
/// 1. Create background tasks implementing the <see cref="IHangfireBackgroundTask"/> interface.
/// 2. Create configuration elements for task settings models, which must inherit from <see cref="HangfireTaskSettingsBase"/> for each task.
/// 3. Create a configuration element for the Hangfire settings model <see cref="HangfireSettings"/>.
/// 4. Create a configuration element for the Hangfire dashboard settings model <see cref="HangfireDashboardSettings"/>.
/// 5. If necessary, create an authorization filter for the Hangfire dashboard, inheriting from <see cref="IDashboardAuthorizationFilter"/> or use existing filters (<see cref="AllAuthorizationFilter"/>).
/// 6. Assign the <see cref="AutoRegisterHangfireTaskAttribute"/> attribute to the tasks and pass the required settings model types as parameters.
/// </summary>
/// <remarks>Models will be registered automatically using the <see cref="AutoRegisterHangfireTaskAttribute"/> attribute.</remarks>
Hangfire = 6,
/// <summary>
/// Swagger module.
/// </summary>
Swagger = 7,
/// <summary>
/// Serilog module.
///
/// The module can be configured in appsettings.json. The basic configuration for Serilog can be initialized using the .InitBaseConfig() method on the WebApplicationFacade.
/// The logger can be accessed using the <see cref="ILogger{TCategoryName}"/> interface or the static <see cref="Serilog.Log"/> class.
/// </summary>
Serilog = 8,
/// <summary>
/// Environment variables module.
/// </summary>
EnvironmentVariables = 9,
/// <summary>
/// API Explorer configuration module (Minimal APIs service).
/// </summary>
EndpointsApiExplorer = 10,
/// <summary>
/// Module for initializing the <see cref="Migrator"/> (applies created migrations to the database) and running migrations on application startup using <see cref="MigrationHostedService"/>.
///
/// For the correct operation of the migrator, it is necessary:
/// 1. Create <see cref="DbContext"/> models.
/// 2. Assign the <see cref="AutoRegisterDbContextAttribute"/> attribute to <see cref="DbContext"/> models.
/// 3. Create migrations using the command <code>dotnet ef migrations add InitialCreate --project your-project/your-project.csproj --startup-project your-project/your-project.csproj --output-dir Migrations</code>.
/// </summary>
EFMigrations = 11,
/// <summary>
/// Controllers module.
/// </summary>
Controllers = 12,
/// <summary>
/// MongoDB database module.
///
/// For the correct operation of the module, it is necessary:
/// 1. Create repository models inheriting from MongoRepositoryBase.cs.
/// 2. Create settings models for each repository. Models must inherit from <see cref="MongoSettingsBase"/> and be assigned the <see cref="AutoRegisterConfigSettingsAttribute"/> attribute.
/// 3. Automatically create or manually populate MongoDB settings models in the appsettings.json file.
/// </summary>
MongoDatabase = 13,
/// <summary>
/// Redis database module.
///
/// 1. Create repository models inheriting from RedisRepositoryBase.cs.
/// 2. Create settings models for each repository. Models must inherit from <see cref="RedisSettingsBase"/> and be assigned the <see cref="AutoRegisterConfigSettingsAttribute"/> attribute.
/// 3. Automatically create or manually populate Redis settings models in the appsettings.json file.
/// </summary>
RedisDatabase = 14
}
- New attributes used for module initialization:
[AutoRegisterConfigSettingsAttribute] - Attribute for automatic registration of config settings in DI for subsequent retrieval using IOptions.
[AutoRegisterDbContextAttribute] - Attribute for automatic registration of the database context.
[AutoRegisterHangfireTaskAttribute] - Attribute for registering Hangfire background tasks.
[AutoRegisterHealthCheckAttribute] - Attribute for automatic registration of IHealthCheck.
[AutoRegisterProfileAttribute] - Attribute for automatic registration of Profile mappings in IMapper.
[AutoRegisterRepositoryAttribute] - Attribute for automatic registration of repositories in DI.
[AutoRegisterServiceAttribute] - Attribute for automatic registration of services in DI.
- Specific examples of module usage can be found in the AspNetCoreMicroserviceInitializer.Examples project. This project was created solely for testing the modules, so no attention was given to "code aesthetics." Once my MVP project with three microservices utilizing this library is completed, I will update the documentation and provide a link to it here, as the code in it will be much cleaner and more elegant.
This library adds the WebApplicationFacade
class to your project. It extends the functionality of the standard WebApplicationBuilder
by introducing features such as:
- Adding any module from
WebApplicationModules (an enum described at the beginning)
, which performs the automatic registration of certain elements.
var modules = new List<WebApplicationModules>
{
WebApplicationModules.Database,
WebApplicationModules.Settings,
WebApplicationModules.Services,
WebApplicationModules.Serilog,
WebApplicationModules.HealthChecks
};
var app = new WebApplicationFacade(modules)
.CreateApplication();
app.Run();
- Adding additional configuration to the
WebApplicationBuilder
using the.AddAdditionalModules()
method.
var app = new WebApplicationFacade(modules)
.AddAdditionalModules(builder =>
{
builder.Services.AddGrpc();
builder.Services.AddScoped<ICatsFactRepository, CatsFactRepository>();
})
.CreateApplication();
- Adding extra
Serilog
configuration, if this module is used, via the.AddAdditionalSerilogConfiguration()
method.
var app = new WebApplicationFacade(modules)
.AddAdditionalSerilogConfiguration((builder, serviceProvider, configuration) =>
{
configuration.Filter.ByExcluding(Matching.WithProperty<string>("RequestPath", path =>
"/health".Equals(path, StringComparison.OrdinalIgnoreCase)));
})
.CreateApplication();
- Initializing the basic configuration of
appsettings.json
using the.InitBaseConfig()
method. When this method is used, configuration sections for setting models are added with default values if such sections do not already exist. The setting models must have the<see cref="AutoRegisterConfigSettingsAttribute"/>
attribute.
By default, the path to
appsettings.json
is used, but if your configuration is in another file, you can change the path by passing it as a method parameter. The path must be either absolute or relative to the executable file ({your-project-path}\bin\Debug\net8.0
).
IMPORTANT! Do not use this method when deploying the application inside
Docker
. It is assumed that files are configured beforehand, and only then is the container deployed inDocker
. In this case, the method is unnecessary, as everything should already be set up.
var app = new WebApplicationFacade(modules)
.InitBaseConfig()
.CreateApplication();
/// <summary>
/// Configuration model for the Cats Facts API.
/// </summary>
[AutoRegisterConfigSettings]
public class CatsFactsApiSettings
{
/// <summary>
/// Endpoint for the Health Check.
/// </summary>
public required string HealthCheckEndpoint { get; set; }
/// <summary>
/// Endpoint for retrieving information.
/// </summary>
public required string Endpoint { get; set; }
/// <summary>
/// Endpoint for retrieving a page.
/// </summary>
public required string PageEndpoint { get; set; }
/// <summary>
/// Maximum page index (used for generating a list of random facts).
/// </summary>
public required int MaxPage { get; set; }
/// <summary>
/// Maximum page limit (used for generating a list of random facts).
/// </summary>
public required int MaxLimit { get; set; }
"CatsFactsApiSettings": {
"Endpoint": "https://catfact.ninja/fact",
"HealthCheckEndpoint": "https://catfact.ninja",
"PageEndpoint": "https://catfact.ninja/facts",
"MaxPage": 20,
"MaxLimit": 10
}
- Initializing standard
docker-compose
files using the.InitBaseDockerComposeFiles()
method.
This method initializes the following files:
develop.env
- a configuration file for Docker. Initialized using theappsettings.json
config. The method transfers all JSON objects from theappsettings.json
file and writes them as Docker-recognized configurations.docker-compose.yml
- adocker-compose
configuration file. Adds template blocks for initializing necessary services indocker-compose
.
The generated files are saved next to the executable file:
{your-project-path}\bin\Debug\net8.0\DockerTemplates
. After the application is launched, you can copy the generated files to a convenient location.
var dockerComposeModules = new List<DockerComposeFileModules>
{
DockerComposeFileModules.Server,
DockerComposeFileModules.PostgreSql,
DockerComposeFileModules.Adminer
};
var app = new WebApplicationFacade(modules)
.InitBaseConfig()
.InitBaseDockerComposeFiles(dockerComposeModules)
.CreateApplication();
/// <summary>
/// Modules for docker-compose files.
/// </summary>
public enum DockerComposeFileModules
{
/// <summary>
/// Server module (the ASP.NET API application itself).
/// </summary>
Server = 0,
/// <summary>
/// Client module (for frontend applications associated with the API).
/// </summary>
Client = 1,
/// <summary>
/// Adminer module for managing databases within docker-compose.
/// </summary>
Adminer = 2,
/// <summary>
/// MongoDB database module.
/// </summary>
MongoDb = 3,
/// <summary>
/// MongoExpress module for managing MongoDB within docker-compose.
/// </summary>
MongoExpress = 4,
/// <summary>
/// ClickHouse database module.
/// </summary>
ClickHouse = 5,
/// <summary>
/// MySQL database module.
/// </summary>
MySql = 6,
/// <summary>
/// Redis database module.
/// </summary>
Redis = 7,
/// <summary>
/// Elasticsearch database module.
/// </summary>
Elasticsearch = 8,
/// <summary>
/// Kibana module for managing Elasticsearch within docker-compose.
/// </summary>
Kibana = 9,
/// <summary>
/// Cassandra database module.
/// </summary>
Cassandra = 10,
/// <summary>
/// RabbitMQ distributed message broker module.
/// </summary>
RabbitMq = 11,
/// <summary>
/// Prometheus monitoring system module.
/// </summary>
Prometheus = 12,
/// <summary>
/// Grafana monitoring system module.
/// </summary>
Grafana = 13,
/// <summary>
/// Nginx web server module.
/// </summary>
Nginx = 14,
/// <summary>
/// PostgreSQL database module.
/// </summary>
PostgreSql = 15
}
# The path to the .env file relative to `docker-compose` (used for specifying .env files in `docker-compose` services).
ENV_FILE=develop.env
# Time zone.
TIME_ZONE=Europe/Moscow
FactsMicroserviceDbContextSettings__ConnectionString=Host=localhost:5432; Database=microservice; Username=postgres; Password=postgres
FactsMicroserviceDbContextSettings__Schema=FactsMicroservice
FactsMicroserviceDbContextSettings__MigrationsTableName=__EFMigrationsHistory
FactsMicroserviceDbContextSettings__MigrationsSchema=FactsMicroservice
CatsFactsApiSettings__Endpoint=https://catfact.ninja/fact
CatsFactsApiSettings__HealthCheckEndpoint=https://catfact.ninja
CatsFactsApiSettings__PageEndpoint=https://catfact.ninja/facts
CatsFactsApiSettings__MaxPage=20
CatsFactsApiSettings__MaxLimit=10
HealthChecksSettings__Endpoint=/health
HealthChecksSettings__UIEnable=True
HealthChecksSettings__EndpointFullUrl=https://localhost:7071/health
HealthChecksSettings__UIEvaluationTimeInSeconds=15
HealthChecksSettings__UIApiMaxActiveRequests=1
Serilog__Using__0=Serilog.Sinks.Console
Serilog__Using__1=Serilog.Sinks.PostgreSQL.Alternative
Serilog__MinimumLevel__Default=Information
Serilog__MinimumLevel__Override__Microsoft=Warning
Serilog__MinimumLevel__Override__System=Warning
Serilog__MinimumLevel__Override__HealthChecks=Warning
Serilog__MinimumLevel__Override__AspNetCore.HealthChecks.UI=Warning
Serilog__MinimumLevel__Override__AspNetCore.HealthChecks.UI.Client=Warning
Serilog__MinimumLevel__Override__AspNetCore.HealthChecks.UI.InMemory.Storage=Warning
Serilog__WriteTo__0__Name=Console
Serilog__WriteTo__0__OutputTemplate=[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}
Serilog__WriteTo__0__Args=null
Serilog__WriteTo__1__Name=PostgreSQL
Serilog__WriteTo__1__OutputTemplate=null
Serilog__WriteTo__1__Args__connectionString=Host=localhost:5432; Database=microservice; Username=postgres; Password=postgres
Serilog__WriteTo__1__Args__schemaName=FactsMicroservice
Serilog__WriteTo__1__Args__tableName=ServerLogs
Serilog__WriteTo__1__Args__needAutoCreateTable=True
Serilog__Properties__ApplicationName=FactsMicroservice
version: "3.9"
networks:
app-network:
services:
server:
build:
# You can either refer to a specific image or to the Dockerfile.
#image:
#context:
#dockerfile:
container_name: server
environment:
TZ: ${TIME_ZONE}
ports:
- "8000:8000"
env_file: ${ENV_FILE}
networks:
- app-network
adminer:
image: adminer:latest
container_name: adminer
ports:
- "8002:8002"
environment:
TZ: ${TIME_ZONE}
networks:
- app-network
postgres:
image: postgres:latest
restart: always
environment:
POSTGRES_USER: postgres
POSTGRES_DB: postgres
TZ: ${TIME_ZONE}
ports:
- "5432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 5s
timeout: 5s
retries: 5
env_file: ${ENV_FILE}
networks:
- app-network
- enum
WebApplicationModules
- class
WebApplicationFacade
- enum
DockerComposeFileModules
public class Program
{
public static void Main(string[] args)
{
var modules = new List<WebApplicationModules>
{
WebApplicationModules.Database,
WebApplicationModules.Settings,
WebApplicationModules.Services,
WebApplicationModules.Serilog,
WebApplicationModules.HealthChecks
};
var dockerComposeModules = new List<DockerComposeFileModules>
{
DockerComposeFileModules.Server,
DockerComposeFileModules.PostgreSql,
DockerComposeFileModules.Adminer
};
var app = new WebApplicationFacade(modules)
.InitBaseConfig()
.InitBaseDockerComposeFiles(dockerComposeModules)
.AddAdditionalModules(builder =>
{
builder.Services.AddGrpc();
builder.Services.AddScoped<ICatsFactRepository, CatsFactRepository>();
})
.CreateApplication();
app.MapGrpcServices();
app.Run();
}
}
/// <summary>
/// Represents the settings model for the database context.
/// </summary>
[AutoRegisterConfigSettings]
public class FactsMicroserviceDbContextSettings : DbContextSettings
{
}
/// <summary>
/// Health Check implementation for <see cref="ICatsFactsApiClient"/>.
/// </summary>
[AutoRegisterHealthCheck]
public class CatsFactsApiHealthCheck : IHealthCheck
{
/// <summary>
/// The client used for interacting with the Cats Facts API.
/// </summary>
private readonly ICatsFactsApiClient _catsFactsApiClient;
public CatsFactsApiHealthCheck(
ICatsFactsApiClient catsFactsApiClient)
{
_catsFactsApiClient = catsFactsApiClient;
}
/// <summary>
/// Asynchronous method to perform the health check.
/// </summary>
/// <param name="context">The health check context.</param>
/// <param name="cancellationToken">A cancellation token.</param>
/// <returns>The result of the health check.</returns>
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
if (await _catsFactsApiClient.Ping())
{
return HealthCheckResult.Healthy();
}
else
{
return HealthCheckResult.Unhealthy();
}
}
}
/// <summary>
/// Service for interacting with the Cats Facts API.
/// </summary>
[AutoRegisterService(ServiceLifetime.Transient, typeof(ICatsFactsApiClient))]
public class CatsFactsApiClient : ICatsFactsApiClient
{
// Implementation
}
- The
.InitBaseConfig()
method initializes JSON Configuration at application startup. Therefore, if you want your configuration to be automatically initialized, you need to: 1) Run the application. 2) Stop it. 3) Fill in the configuration after initialization. 4) Restart the application. - The
.InitBaseDockerComposeFiles()
method creates files on every application startup. After the first use, it is advisable to move these files from{your-project-path}\bin\Debug\net8.0\DockerTemplates
to a location where they cannot be automatically altered. - Some modules have dependencies on each other. For instance, if you add
WebApplicationModule.EFMigrations
but do not include theWebApplicationModule.SqlDatabase
andWebApplicationModule.Settings
modules, they will be added automatically (internal library logic to prevent possible errors). - The
WebApplicationModules.Swagger
module only works in Debug builds. - Modules are sorted within
WebApplicationFacade
, so their addition order does not matter. - The logic for adding modules to
IServiceCollection
is encapsulated withinWebApplicationFacade
and cannot be altered externally.
This library provides the inventory for AspNetCoreMicroserviceInitializer.Registrations, including:
- Attributes described at the beginning of the documentation.
- Enums listed at the beginning of the documentation and in the "What does this library do" section for AspNetCoreMicroserviceInitializer.Registrations.
-
Exceptions -
AttributeException
, thrown in case of an error in any attribute. -
HangfireFilters -
AllAuthorizationFilter
(a filter for automatically authorizing all requests when accessing theHangfire
dashboard. It is configured inappsettings.json
).
"HangfireDashboardSettings": {
"EnableCustomAuthorization": true,
"FilterName": "AllAuthorizationFilter"
}
/// <summary>
/// A filter for automatic authorization of all requests when accessing the <see cref="Hangfire"/> dashboard.
/// </summary>
public class AllAuthorizationFilter : IDashboardAuthorizationFilter
{
/// <summary>
/// Метод авторизации.
/// </summary>
/// <param name="context">Контекст дашборда.</param>
/// <returns><see langword="true"/> для всех случаев.</returns>
public bool Authorize([NotNull] DashboardContext context) => true;
}
-
Helpers - various utilities for working with modules, such as
DockerComposeFilesHelper
,AssemblyHelper
,JsonHelper
. -
Interfaces -
IHangfireBackgroundTask
,IMigrator
(used internally for the migrator implementation),IServiceImplementationFactory{TService}
- an interface for adding a factory function to create a service instance within the Dependency Injection container.
[AutoRegisterHangfireTask(typeof(DateTimeTaskSettings))]
public class DateTimeTask : IHangfireBackgroundTask
{
private readonly DateTimeService _dateTimeService;
private readonly ILogger<DateTimeTask> _logger;
public DateTimeTask(
DateTimeService dateTimeService,
ILogger<DateTimeTask> logger)
{
_dateTimeService = dateTimeService;
_logger = logger;
}
public async Task ExecuteAsync()
{
var currentDateTime = await _dateTimeService.GetDateTimeWithMessageAsync();
_logger.LogInformation(currentDateTime);
}
}
[AutoRegisterService(ServiceLifetime.Singleton, typeof(IRandomWordService))]
public class RandomWordService : IRandomWordService, IServiceImplementationFactory<RandomWordService>
{
...
/// <summary>
/// A factory function,which is added using <see cref="IServiceImplementationFactory{TService}"/> that will be used when registering the service and return <see cref="RandowShortWordService"/>.
/// </summary>
/// <remarks>
/// For a test, you can comment out this line and remove the interface from the class. And you will be able to observe the generation of random words of more than 5 characters.
/// </remarks>
public Func<IServiceProvider, RandomWordService> ImplementationFactory => (sp) => new RandowShortWordService();
...
}
public class RandowShortWordService : RandomWordService
{
protected override int MaxWordLength => 5;
public override string GetRandomWord()
{
return base.GetRandomWord();
}
}
-
Migrations - classes and
HostedService
for the migrations module. -
Settings - abstract base classes for settings (
DbContextSettingsBase
,DbSettingsBase
,HangfireTaskSettingsBase
,MongoSettingsBase
,RedisSettingsBase
) for convenient usage and definition, as well as specific module settings models (containing precise settings for specific modules). Abstract settings can be extended as needed, while specific settings only need to be populated in theappsettings.json
file.
/// <summary>
/// Configuration for Health Checks settings from the appsettings file.
/// </summary>
[AutoRegisterConfigSettings]
[ConfigSettingsModule(WebApplicationModules.HealthChecks)]
public class HealthChecksSettings
{
/// <summary>
/// The endpoint for Health Checks.
///
/// Example: /health.
/// </summary>
public required string Endpoint { get; init; } = "/health";
/// <summary>
/// Enables the Health Checks UI for monitoring.
///
/// Example: true/false.
/// </summary>
public bool UIEnable { get; init; } = true;
/// <summary>
/// Full URL of the Health Check endpoint for requests from the Health Checks UI.
///
/// Example: http://mydomain:80/health.
/// </summary>
public string? EndpointFullUrl { get; init; } = "http://localhost:8000/health";
/// <summary>
/// Evaluation frequency in seconds for the Health Checks UI.
///
/// Example: 10.
/// </summary>
public int? UIEvaluationTimeInSeconds { get; init; } = 5;
/// <summary>
/// Maximum number of concurrent requests to the Health Checks API from the UI.
///
/// Example: 2.
/// </summary>
public int? UIApiMaxActiveRequests { get; init; } = 2;
}
/// <summary>
/// Configuration settings for the EF Migrator module.
/// </summary>
[AutoRegisterConfigSettings]
[ConfigSettingsModule(WebApplicationModules.EFMigrations)]
public class MigratorSettings
{
/// <summary>
/// Flag indicating whether the application should stop after applying migrations.
/// </summary>
public bool IsStopApplicationAfterApplyMigrations { get; init; } = false;
}
/// <summary>
/// Configuration settings for Cross-Origin Resource Sharing (CORS).
/// </summary>
[AutoRegisterConfigSettings]
[ConfigSettingsModule(WebApplicationModules.Cors)]
public class CorsSettings
{
/// <summary>
/// Enables CORS with a named policy.
///
/// Example: true/false.
/// </summary>
public bool EnableCors { get; init; } = true;
/// <summary>
/// The name of the CORS policy.
///
/// Example: AllowAccessFrontendQueries.
/// </summary>
public string? PolicyName { get; init; } = "AllowAccessFrontendQueries";
/// <summary>
/// List of allowed origins for the CORS policy.
///
/// Example: http://localhost:8082.
/// </summary>
public string[ ]? AllowedOrigins { get; init; }
}
This library does not provide any functional features but serves as the inventory for the core library: AspNetCoreMicroserviceInitializer.Registrations.
Listed in the "What does this library do?" section.
Listed in the "What does this library do?" section.
- Abstract settings can be extended as needed, while specific settings only need to be filled in the
appsettings.json
file. - Attributes must be used in conjunction with the added modules. One cannot function without the other.
- AspNetCore.HealthChecks.UI (>= 8.0.2)
- AspNetCore.HealthChecks.UI.Client (>= 8.0.1)
- AspNetCore.HealthChecks.UI.InMemory.Storage (>= 8.0.1)
- AutoMapper (>= 13.0.1)
- Hangfire.AspNetCore (>= 1.8.15)
- Hangfire.Core (>= 1.8.15)
- Hangfire.MemoryStorage (>= 1.8.1.1)
- Hangfire.PostgreSql (>= 1.20.10)
- Microsoft.Extensions.Configuration (>= 9.0.0)
- Microsoft.Extensions.DependencyInjection (>= 9.0.0)
- Microsoft.Extensions.Hosting (>= 9.0.0)
- Microsoft.Extensions.Logging (>= 9.0.0)
- Microsoft.Extensions.Options (>= 9.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 9.0.0)
- Newtonsoft.Json (>= 13.0.3)
- Serilog.AspNetCore (>= 8.0.3)
- Serilog.Sinks.SQLite (>= 6.0.0)
- Swashbuckle.AspNetCore (>= 7.0.0)
This library provides the inventory for AspNetCoreMicroserviceInitializer.Registrations, enabling interaction with databases. It includes base repository classes that can be extended and used in project-specific repositories.
- Provides entity interfaces.
public interface IEntity
{
/// <summary>
/// The creation date of the entity.
/// </summary>
DateTime CreationDate { get; set; }
/// <summary>
/// The date when the entity was last updated.
/// </summary>
DateTime LastUpdateDate { get; set; }
/// <summary>
/// Flag indicating whether the entity is deleted.
/// </summary>
bool IsDeleted { get; set; }
}
/// <summary>
/// Interface for a base SQL entity.
/// </summary>
/// <typeparam name="TId">The type of the Id.</typeparam>
public interface ISqlEntity<TId> : IEntity
where TId : struct
{
/// <summary>
/// The identifier of the entity.
/// </summary>
TId Id { get; set; }
}
/// <summary>
/// Interface for a base Redis entity.
/// </summary>
public interface IRedisEntity
{
/// <summary>
/// The key of the entity.
/// </summary>
string Key { get; set; }
/// <summary>
/// The value of the entity, either as a string or JSON.
/// </summary>
string Value { get; set; }
}
/// <summary>
/// Interface for a base MongoDb entity.
/// </summary>
public interface IMongoEntity : IEntity
{
/// <summary>
/// The identifier of the entity.
/// </summary>
[BsonId]
ObjectId Id { get; set; }
}
- Offers repository interfaces.
/// <summary>
/// Repository interface for reading data from Redis.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IRedisReadRepository<TEntity>
where TEntity : IRedisEntity
{
/// <summary>
/// Get records by keys.
/// </summary>
/// <param name="keys">An array of keys of type <see cref="IEnumerable{string}"/>.</param>
/// <returns>A collection of type <see cref="IEnumerable{TEntity}"/>.</returns>
Task<IEnumerable<TEntity>> GetByKeysAsync(
IEnumerable<string> keys,
CancellationToken cancellationToken = default);
/// <summary>
/// Get a record by its key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>An entity of type <see cref="TEntity"/>, or <see langword="null"/> if not found.</returns>
Task<TEntity?> GetByKeyAsync(
string key,
CancellationToken cancellationToken = default);
/// <summary>
/// Check if an element exists.
/// </summary>
/// <param name="key">The key.</param>
/// <returns><see langword="true"/> if the element exists, <see langword="false"/> if not.</returns>
Task<bool> IsExistsAsync(
string key,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Repository interface for writing data to MongoDb.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface IMongoWriteRepository<TEntity>
where TEntity : IMongoEntity
{
/// <summary>
/// Method for inserting a record into the table.
/// </summary>
/// <param name="entity">An entity of type <see cref="TEntity"/>.</param>
Task InsertAsync(
TEntity entity,
CancellationToken cancellationToken = default);
/// <summary>
/// Method for inserting multiple records into the table.
/// </summary>
/// <param name="entities">An array of entities of type <see cref="IEnumerable{TEntity}"/>.</param>
Task InsertBatchAsync(
IEnumerable<TEntity> entities,
CancellationToken cancellationToken = default);
/// <summary>
/// Update a record in the table.
/// </summary>
/// <param name="entity">An entity of type <see cref="TEntity"/>.</param>
Task UpdateAsync(
TEntity entity,
CancellationToken cancellationToken = default);
/// <summary>
/// Method for updating multiple records in the table.
/// </summary>
/// <param name="entities">An array of entities of type <see cref="IEnumerable{TEntity}"/>.</param>
Task UpdateBatchAsync(
IEnumerable<TEntity> entities,
CancellationToken cancellationToken = default);
/// <summary>
/// Method for deleting a record by its identifier.
/// </summary>
/// <param name="id">The identifier.</param>
Task DeleteByIdAsync(
ObjectId id,
CancellationToken cancellationToken = default);
/// <summary>
/// Method for deleting records by their identifiers.
/// </summary>
/// <param name="ids">An array of identifiers of type <see cref="IEnumerable{ObjectId}"/>.</param>
/// <returns>The number of successfully deleted records.</returns>
Task DeleteByIdsAsync(
IEnumerable<ObjectId> ids,
CancellationToken cancellationToken = default);
}
/// <summary>
/// Base repository interface for MongoDb.
/// </summary>
public interface IMongoRepository<TEntity> :
IMongoReadRepository<TEntity>,
IMongoWriteRepository<TEntity>
where TEntity : IMongoEntity
{
}
/// <summary>
/// Base repository interface for Redis.
/// </summary>
public interface IRedisRepository<TEntity> :
IRedisReadRepository<TEntity>,
IRedisWriteRepository<TEntity>
where TEntity : IRedisEntity
{
}
/// <summary>
/// Base repository interface.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
public interface ISqlRepository<TEntity> :
ISqlReadRepository<TEntity>,
ISqlWriteRepository<TEntity>
where TEntity : ISqlEntity<long>
{
}
- Contains abstract base classes for repositories.
/// <summary>
/// Abstract class for the base MongoDb repository.
/// </summary>
public abstract class MongoRepositoryBase<TEntity> : IMongoRepository<TEntity>
where TEntity : class, IMongoEntity
{
/// <summary>
/// MongoDb client.
/// </summary>
protected readonly IMongoClient _client;
/// <summary>
/// MongoDb database.
/// </summary>
protected readonly IMongoDatabase _database;
/// <summary>
/// MongoDb collection.
/// </summary>
protected readonly IMongoCollection<TEntity> _collection;
/// <summary>
/// Constructor for <see cref="MongoRepositoryBase{TEntity}"/>.
/// </summary>
/// <param name="clientFactory">Factory to obtain a MongoDb client.</param>
/// <param name="connectionString">Connection string to the database.</param>
/// <param name="databaseName">The name of the database.</param>
protected MongoRepositoryBase(
IMongoClientFactory clientFactory,
string connectionString,
string databaseName)
{
_client = clientFactory.GetClientByConnectionString(connectionString);
_database = _client.GetDatabase(databaseName);
var collectionName = MongoCollectionNameResolver.GetCollectionName<TEntity>();
_collection = _database.GetCollection<TEntity>(collectionName);
}
// Implementation
}
/// <summary>
/// Abstract class for the base Redis repository.
/// </summary>
public abstract class RedisRepositoryBase<TEntity> : IRedisRepository<TEntity>
where TEntity : class, IRedisEntity, new()
{
/// <summary>
/// Redis database.
/// </summary>
protected readonly IDatabase _database;
/// <summary>
/// Key prefix for uniformity.
/// </summary>
protected readonly string _keyPrefix;
/// <summary>
/// Model for connecting to the Redis database.
/// </summary>
protected readonly IConnectionMultiplexer _multiplexer;
/// <summary>
/// Repository constructor.
/// </summary>
/// <param name="clientFactory">Factory for obtaining a Redis client.</param>
/// <param name="connectionString">Connection string to Redis.</param>
/// <param name="keyPrefix">Key prefix for uniformity.</param>
protected RedisRepositoryBase(
IRedisClientFactory clientFactory,
string connectionString,
string keyPrefix)
{
_multiplexer = clientFactory.GetClientByConnectionString(connectionString);
_database = _multiplexer.GetDatabase();
_keyPrefix = keyPrefix;
}
//Implementation
}
/// <summary>
/// Abstract class for the base Sql repository.
/// </summary>
public abstract class SqlRepositoryBase<TEntity> : ISqlRepository<TEntity>
where TEntity : class, ISqlEntity<long>
{
/// <summary>
/// Database context.
/// </summary>
protected readonly DbContext _dbContext;
/// <summary>
/// Constructor for <see cref="SqlRepositoryBase{TEntity}"/>.
/// </summary>
/// <param name="dbContext">The database context.</param>
protected SqlRepositoryBase(DbContext dbContext)
{
_dbContext = dbContext;
}
// Implementation
}
- Includes attributes for entities.
/// <summary>
/// Attribute to specify the collection name in string format.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
public class MongoCollectionAttribute : Attribute
{
/// <summary>
/// The name of the collection.
/// </summary>
public string CollectionName { get; }
/// <summary>
/// Constructor of the attribute.
/// </summary>
/// <param name="collectionName">The name of the collection.</param>
public MongoCollectionAttribute(string collectionName)
{
CollectionName = collectionName;
}
}
- Provides public utility services for internal use:
MongoClientWithConnectionString
,RedisClientWithConnectionString
,MongoClientFactory
,RedisClientFactory
.
- Entity interfaces.
- Repository interfaces.
- Abstract repository classes.
- Attributes.
/// <summary>
/// Internal model for storing a fact in the database.
/// </summary>
public class CatsFact : IEntity<long>
{
/// <summary>
/// The model identifier.
/// </summary>
public long Id { get; set; }
/// <summary>
/// The text of the fact.
/// </summary>
public required string Text { get; set; }
/// <summary>
/// The creation date of the model.
/// </summary>
public DateTime CreationDate { get; set; }
/// <summary>
/// Flag indicating whether the model is deleted.
/// </summary>
public bool IsDeleted { get; set; }
}
/// <summary>
/// Repository for working with <see cref="CatsFact"/>.
/// </summary>
[AutoRegisterRepository]
public class CatsFactRepository :
RepositoryBase<CatsFact>,
ICatsFactRepository
{
/// <summary>
/// The database context.
/// </summary>
private readonly FactsMicroserviceDbContext _microserviceDbContext;
public CatsFactRepository(FactsMicroserviceDbContext dbContext) : base(dbContext)
{
_microserviceDbContext = dbContext;
}
/// <summary>
/// Method to delete a record by its identifier.
/// </summary>
/// <param name="id">The record identifier.</param>
public override async Task DeleteByIdAsync(long id)
{
var model = await GetByIdAsync(id);
if (model is null)
{
throw new Exception("Couldn't get the model by ID to delete.");
}
model.IsDeleted = true;
await UpdateAsync(model);
}
/// <summary>
/// Method to get the count of records in the table.
/// </summary>
/// <returns>The count of records in the table.</returns>
public async Task<int> GetCountAsync()
{
return await _microserviceDbContext.Facts.CountAsync();
}
}
[MongoCollection("TestCollection")]
public class DummyMongoDbEntity : IMongoEntity
{
[BsonId]
public ObjectId Id { get; set; }
public DateTime CreationDate { get; set; }
public DateTime LastUpdateDate { get; set; }
public bool IsDeleted { get; set; }
}
[AutoRegisterRepository(interfaceType: typeof(IMongoRepository<DummyMongoDbEntity>))]
public class DummyFirstMongoDbRepository : MongoRepositoryBase<DummyMongoDbEntity>
{
public DummyFirstMongoDbRepository(IMongoClientFactory factory, IOptions<DummyFirstMongoSettings> settings)
: base(factory, settings.Value.ConnectionString, settings.Value.DatabaseName)
{
}
}
[AutoRegisterRepository]
public class DummyRedisSecondRepository : RedisRepositoryBase<DummyRedisEntity>
{
public DummyRedisSecondRepository(IRedisClientFactory factory, IOptions<DummyRedisSecondSettings> settings)
: base(factory, settings.Value.ConnectionString, "test_repository_redis_second")
{
}
}
[AutoRegisterRepository(interfaceType: typeof(ISqlRepository<DummyModel>))]
public class DummyRepository : SqlRepositoryBase<DummyModel>
{
public DummyRepository(DummyDbContext dbContext) : base(dbContext)
{
}
}
public class DatabaseController : ControllerBase
{
private readonly ISqlRepository<DummyModel> _dummyRepository;
// Implementation.
}
- When initializing WebApplicationFacade and using
WebApplicationModules.MongoDatabase
orWebApplicationModules.RedisDatabase
, the servicesIRedisClientFactory
andIMongoClientFactory
are automatically registered in the Dependency Injection (DI) container. You only need to pass them into your repository constructor. If you are not usingWebApplicationFacade
, you must manually register these services in DI.
services.AddSingleton<IMongoClientWithConnectionString>(sp =>
{
return new MongoClientWithConnectionString(mongoSettings.ConnectionString);
});
services.AddTransient<IMongoClientFactory, MongoClientFactory>();
services.AddSingleton<IRedisClientWithConnectionString>(sp =>
{
return new RedisClientWithConnectionString(redisSettings.ConnectionString);
});
services.AddTransient<IRedisClientFactory, RedisClientFactory>();
- To enable automatic registration of
IMongoClientFactory
andIRedisClientFactory
, follow the instructions for WebApplicationModules for the respective modules.
/// <summary>
/// MongoDB database module.
///
/// For the correct operation of the module, it is necessary:
/// 1. Create repository models inheriting from MongoRepositoryBase.cs.
/// 2. Create settings models for each repository. Models must inherit from <see cref="MongoSettingsBase"/> and be assigned the <see cref="AutoRegisterConfigSettingsAttribute"/> attribute.
/// 3. Automatically create or manually populate MongoDB settings models in the appsettings.json file.
/// </summary>
MongoDatabase = 13,
/// <summary>
/// Redis database module.
///
/// 1. Create repository models inheriting from RedisRepositoryBase.cs.
/// 2. Create settings models for each repository. Models must inherit from <see cref="RedisSettingsBase"/> and be assigned the <see cref="AutoRegisterConfigSettingsAttribute"/> attribute.
/// 3. Automatically create or manually populate Redis settings models in the appsettings.json file.
/// </summary>
RedisDatabase = 14
- The factory method in repositories is useful when a single microservice needs to connect to multiple databases of the same type, e.g., two MongoDB instances located at different URLs. In such cases:
- Create the necessary settings classes inheriting from
RedisSettingsBase
orMongoSettingsBase
. - Follow the setup instructions for
WebApplicationModules
.
- Create the necessary settings classes inheriting from
[AutoRegisterConfigSettings]
public class DummyFirstMongoSettings : MongoSettingsBase
{
}
[AutoRegisterConfigSettings]
public class DummySecondMongoSettings : MongoSettingsBase
{
}
var modulens = new List<WebApplicationModules>
{
WebApplicationModules.MongoDatabase
};
var app = new WebApplicationFacade(modulens)
.CreateApplication();
app.Run();
"DummyFirstMongoSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "TestDatabaseFirst"
},
"DummySecondMongoSettings": {
"ConnectionString": "mongodb://localhost:27018",
"DatabaseName": "TestDabaseSecond"
}
- Collections in MongoDB will be named after the
IMongoEntity
class unless theMongoCollectionAttribute
is applied to override the default name.
- Microsoft.EntityFrameworkCore (>= 9.0.0)
- Microsoft.EntityFrameworkCore.Relational (>= 9.0.0)
- MongoDB.Driver (>= 3.0.0)
- StackExchange.Redis (>= 2.8.22)
Repository with the basic Onion-Architecture-Microservice template:
All NuGet packages:
- AspNetCoreMicroserviceInitializer.Registrations
- AspNetCoreMicroserviceInitializer.TradingDesk
- AspNetCoreMicroserviceInitializer.Database
My contacts (if you have any questions):