-
Notifications
You must be signed in to change notification settings - Fork 32
packages
- Overview
- Configuration basics
- Devon4Net.Infrastructure.CircuitBreaker
- Devon4Net.Infrastructure.Swagger
- Devon4Net.Infrastructure.Logger
- Devon4Net.Infrastructure.Cors
- Devon4Net.Infrastructure.JWT
- Devon4Net.Infrastructure.LiteDb
- Devon4Net.Infrastructure.FluentValidation
- Devon4Net.Infrastructure.Common
- Devon4Net.Infrastructure.Extensions
- Devon4Net.Infrastructure.MediatR
- Devon4Net.Infrastructure.RabbitMQ
- Devon4Net.Infrastructure.Middleware
- Devon4Net.Infrastructure.UnitOfWork
Devon4Net is made up of several components, all of which are described in this document. This components are available in the form of NuGet packages. In the Devon4Net repository this packages are placed in the Infrastructure layer which is a cross-cutting layer that can be referenced from any level on the architecture.

Components are class librarys that collaborate with each other for a purpose. They group the necessary code so that they can work according to the specified configuration. For example, the package Devon4Net.Infrastructure.Swagger
has isolated the swagger essential pieces of code and has been developed in such a manner that you just need to write a few lines and specify a couple options to get it working the way you need.
All of the components follow a similar structure which includes the next directories:
-
Configuration: Static configuration class (or multiple classes) that contains extension methods used to configure the component.
-
Handlers: Classes that are required to manage complex operations or communications.
-
Helpers: Normally static classes that help in small conversions and operations.
-
Constants: Classes that contain static constants to get rid of hard-coded values.
Note
|
Because each component is unique, you may find some more directories or less than tose listed above. |
Any configuration for .Net Core 6.0 projects needs to be done in the Program.cs
files which is placed on the startup application, but we can extract any configuration needed to an extension method and call that method from the component. As a result, the component will group everything required and the configuration will be much easier.
Extension methods allow you to "add" methods to existing types without having to create a new derived type, or modify it in any way. Although they are static methods, they are referred to as instance methods on the extended type. For C# code, there is no difference in calling a extension method and a method defined in a type.
For example, the next extension method will extend the class ExtendedClass
and it will need an OptionalParameter
instance to do some configuration:
public static class ExtensionMethods
{
public static void DoConfiguration(this ExtendedClass class, OptionalParameter extra)
{
// Do your configuration here
class.DoSomething();
class.AddSomething(extra)
}
}
Thanks to the this
modifier preceeding the first parameter, we are able to call the method directly on a instance of ExtendedClass
as follows:
ExtendedClass class = new();
OptionalParameter extra = new();
class.DoConfiguration(extra);
As you can see, we don’t need that a class derived from ExtendedClass
to add some methods and we don’t need those methods placed in the class itself either. This can be seen easily when extending a primitive type such as string
:
public static class ExtensionMethods
{
public static int CountWords(this string word, char[] separationChar = null)
{
if(separationChar == null) separationChar = new char[]{' '};
return word.Split(separationChar, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
In the previous example we created a method that can count words given a list of separation characters. And now we can use it over any string as follows:
string s = "Hello World";
Console.WriteLine(s.CountWords());
2
Note
|
Remember to reference the class so you can use the extension methods (using directive).
|
The options design pattern allows you to have strong typed options and provides you the ability to inject them into your services. To follow this pattern, the configuration present on the appsettings.json
needs to be mapped into an object.
This means, the following configuration:
"essentialoptions" : {
"value1": "Hello",
"value2": "World"
}
Would need the following class:
public class EssentialOptions
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
In .Net we can easily map the configuration thanks to the Configure<T>()
method from IServiceCollection
and GetSection()
method from IConfiguration
. We could be loading the configuration as follows:
services.Configure<EssentialOptions>(configuration.GetSection("essentialoptions"));
And then injecting it making use of IOptions<T>
interface:
public class MyService : IMyService
{
private readonly EssentialOptions _options;
public MyService(IOptions<EssentialOptions> options)
{
_options = options.Value;
}
}
In devon4net, there is an IServiceCollection
extension available that uses the methods described above and also returns the options injected thanks to IOptions<T>
. So, to load the same options, we should use the following:
EssentialOptions options = services.GetTypedOptions<EssentialOptions>(configuration, "essentialoptions");
Dependency Injection is a technique for achieving Inversion of Control Principle. In .Net it is a built-in part that comes with the framework.
Using a service provider IServiceProvider
available in .Net, we are able to add any service or option to a service stack that will be available for injection in constructors of the classes where it’s used.
Services can be registered with one of the following lifetimes:
Lifetime |
Description |
Example |
Transient |
Transient lifetime services are created each time they’re requested from the service container. Disposed at the end of the request. |
services.AddTransient<IDependency, Dependency>(); |
Scoped |
A scoped lifetime indicates that services are created once per client request (connection). Disposed at the end of the request. |
services.AddScoped<IDependency, Dependency>(); |
Singleton |
Singleton lifetime services are created either the first time they’re requested or by the developer. Every subsequent request of the service implementation from the dependency injection container uses the same instance. |
services.AddSingleton<IDependency, Dependency>(); |
This injections would be done in the startup project in Program.cs
file, and then injected in constructors where needed.
The Devon4Net.Infrastructure.CircuitBreaker component implements the retry pattern for HTTP/HTTPS calls. It may be used in both SOAP and REST services.
Component configuration is made on file appsettings.{environment}.json
as follows:
"CircuitBreaker": {
"CheckCertificate": false,
"Endpoints": [
{
"Name": "SampleService",
"BaseAddress": "http://localhost:5001",
"Headers": {
},
"WaitAndRetrySeconds": [
0.0001,
0.0005,
0.001
],
"DurationOfBreak": 0.0005,
"UseCertificate": false,
"Certificate": "localhost.pfx",
"CertificatePassword": "localhost",
"SslProtocol": "Tls12", //Tls, Tls11,Tls12, Tls13, none
"CompressionSupport": true,
"AllowAutoRedirect": true
}
]
}
Property | Description |
---|---|
|
True if HTTPS is required. This is useful when developing an API Gateway needs a secured HTTP, disabling this on development we can use communications with a valid server certificate |
Endpoints |
Array with predefined sites to connect with |
Name |
The name key to identify the destination URL |
Headers |
Not ready yet |
WaitAndRetrySeconds |
Array which determines the number of retries and the lapse period between each retry. The value is in milliseconds. |
Certificate |
Ceritificate client to use to perform the HTTP call |
CertificatePassword |
The password that you assign when exporting the certificate |
|
The secure protocol to use on the call |
Protocol | Key | Description |
---|---|---|
SSl3 |
48 |
Specifies the Secure Socket Layer (SSL) 3.0 security protocol. SSL 3.0 has been superseded by the Transport Layer Security (TLS) protocol and is provided for backward compatibility only. |
TLS |
192 |
Specifies the Transport Layer Security (TLS) 1.0 security protocol. The TLS 1.0 protocol is defined in IETF RFC 2246. |
TLS11 |
768 |
Specifies the Transport Layer Security (TLS) 1.1 security protocol. The TLS 1.1 protocol is defined in IETF RFC 4346. On Windows systems, this value is supported starting with Windows 7. |
TLS12 |
3072 |
Specifies the Transport Layer Security (TLS) 1.2 security protocol. The TLS 1.2 protocol is defined in IETF RFC 5246. On Windows systems, this value is supported starting with Windows 7. |
TLS13 |
12288 |
Specifies the TLS 1.3 security protocol. The TLS protocol is defined in IETF RFC 8446. |
For setting it up using the Devon4NetApi template just configure it in the appsettings.Development.json
file.
Add it using Dependency Injection on this case we instanciate Circuit Breaker in a Service Sample Class
public class SampleService: Service<SampleContext>, ISampleService
{
private readonly ISampleRepository _sampleRepository;
private IHttpClientHandler _httpClientHandler { get; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="uoW"></param>
public SampleService(IUnitOfWork<SampleContext> uoW, IHttpClientHandler httpClientHandler) : base(uoW)
{
_httpClientHandler = httpClientHandler;
_sampleRepository = uoW.Repository<ISampleRepository>();
}
}
Add the necessary references.
using Devon4Net.Infrastructure.CircuitBreaker.Common.Enums;
using Devon4Net.Infrastructure.CircuitBreaker.Handlers;
You must give the following arguments to make a POST call:
await _httpClientHandler.Send<YourOutPutClass>(HttpMethod.POST, NameOfTheService, EndPoint, InputData, MediaType.ApplicationJson);
Where:
Property | Description |
---|---|
YourOutputClass |
The type of the class that you are expecting to retrieve from the call |
NameOftheService |
The key name of the endpoint provided in the appsettings.json file at Endpoints[] node |
|
Part of the url to use with the base address. E.g: /validate |
|
Your instance of the class with values that you want to use in the call |
|
The media type flag for the call |
Install the package on your solution using the Package Manager Console:
Install-Package Devon4Net.Infrastructure.CircuitBreaker
next add via Dependency Injection the circuit breaker instance.On this case we use a Service
public class SampleService : ISampleService
{
private IHttpClientHandler _httpClientHandler { get; }
public SampleService(IHttpClientHandler httpClientHandler)
{
_httpClientHandler = httpClientHandler;
}
}
Don’t forget to provide the necessary references.
using Devon4Net.Infrastructure.CircuitBreaker.Common.Enums;
using Devon4Net.Infrastructure.CircuitBreaker.Handlers;
And configure CircuitBreaker in Program.cs
adding the following lines:
using Devon4Net.Infrastructure.CircuitBreaker;
.
.
.
builder.Services.SetupCircuitBreaker(builder.Configuration);
You must add the default configuration shown in the configuration section and at this point you can use the circuit breaker functionality in your code.
To perform a GET call you should use your circuit breaker instance as follows:
await _httpClientHandler.Send<YourOutPutClass>(HttpMethod.Get, NameOfTheService, EndPoint, InputData, MediaType.ApplicationJson);
Where:
Property | Description |
---|---|
YourOutputClass |
The type of the class that you are expecting to retrieve from the call |
NameOftheService |
The key name of the endpoint provided in the appsettings.json file at Endpoints[] node |
|
Part of the url to use with the base address. E.g: /validate |
|
Your instance of the class with values that you want to use in the call |
|
The media type flag for the call |
Swagger is a set of open source software tools for designing, building, documenting, and using RESTful web services. This component provides a full externalized configuration for the Swagger tool.
It primarily provides the swagger UI for visualizing and testing APIs, as well as automatic documentation generation via annotations in controllers.
Component configuration is made on file appsettings.{environment}.json
as follows:
"Swagger": {
"Version": "v1",
"Title": "My Swagger API",
"Description": "Swagger API for devon4net documentation",
"Terms": "https://www.devonfw.com/terms-of-use/",
"Contact": {
"Name": "devonfw",
"Email": "[email protected]",
"Url": "https://www.devonfw.com"
},
"License": {
"Name": "devonfw - Terms of Use",
"Url": "https://www.devonfw.com/terms-of-use/"
},
"Endpoint": {
"Name": "V1 Docs",
"Url": "/swagger/v1/swagger.json",
"UrlUi": "swagger",
"RouteTemplate": "swagger/v1/{documentName}/swagger.json"
}
},
In the following list all the configuration fields are described:
-
Version
: Actual version of the API. -
Title
: Title of the API. -
Description
: Description of the API. -
Terms
: Link to the terms and conditions agreement. -
Contact
: Your contact information. -
License
: Link to the License agreement. -
Endpoint
: Swagger endpoints information.
For setting it up using the Devon4NetApi template just configure it in the appsettings.{environment}.json
file.
Install the package on your solution using the Package Manager Console:
> install-package Devon4Net.Infrastructure.Swagger
Configure swagger in Program.cs
adding the following lines:
using Devon4Net.Infrastructure.Swagger;
.
.
.
builder.Services.SetupSwagger(builder.Configuration);
.
.
.
app.ConfigureSwaggerEndPoint();
Add the default configuration shown in the configuration section.
-
In order to generate the documentation annotate your actions with summary, remarks and response tags:
/// <summary> /// Method to make a reservation with potential guests. The method returns the reservation token. /// </summary> /// <param name="bookingDto"></param> /// <response code="201">Ok.</response> /// <response code="400">Bad request. Parser data error.</response> /// <response code="401">Unauthorized. Authentication fail.</response> /// <response code="403">Forbidden. Authorization error.</response> /// <response code="500">Internal Server Error. The search process ended with error.</response> [HttpPost] [HttpOptions] [Route("/mythaistar/services/rest/bookingmanagement/v1/booking")] [AllowAnonymous] [EnableCors("CorsPolicy")] public async Task<IActionResult> Booking([FromBody]BookingDto bookingDto) { try { ...
-
You can access the swagger UI on
http://localhost:yourport/swagger/index.html
Previously known as Devon4Net.Infrastructure.Log(v5.0 or lower)
Logging is an essential component of every application’s life cycle. A strong logging system becomes a critical component that assists developers to understand and resolve emerging problems.
Note
|
Starting with .NET 6, logging services no longer register the ILogger type. When using a logger, specify the generic-type alternative ILogger<TCategoryName> or register the ILogger with dependency injection (DI).
|
Default .Net log levels system:
Type |
Description |
Critical |
Used to notify failures that force the program to shut down |
Error |
Used to track major faults that occur during program execution |
Warning |
Used to report non-critical unexpected behavior |
Information |
Informative messages |
Debug |
Used for debugging messages containing additional information about application operations |
Trace |
For tracing the code |
None |
If you choose this option the loggin category will not write any messages |
Component setup is done in the appsettings.{environment}.json
file using the following structure:
"Logging": {
"UseLogFile": true,
"UseSQLiteDb": true,
"UseGraylog": true,
"UseAOPTrace": false,
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
},
"SqliteDatabase": "logs/log.db",
"LogFile": "logs/{0}_devonfw.log",
"SeqLogServerHost": "http://127.0.0.1:5341",
"GrayLog": {
"GrayLogHost": "127.0.0.1",
"GrayLogPort": "12201",
"GrayLogProtocol": "UDP",
"UseSecureConnection": true,
"UseAsyncLogging": true,
"RetryCount": 5,
"RetryIntervalMs": 15,
"MaxUdpMessageSize": 8192
}
}
Where:
-
UseLogFile
: When you set this option to true, you can store the log output to a file. -
UseSQLiteDb
: True when you wish to insert the log output into a SQLiteDb -
UseGrayLog
: This option enables the use of GrayLog for loggin -
UseAOPTrace
: True if you need to trace the attributes of the controllers
Warning
|
Don’t set to true on production environments, doing so may expose critical information. |
-
LogLevel
: Sets the minimum level of logs to be captured -
SqliteDatabase
: path to SQlite database -
LogFile
: path to the log file -
SeqLogServerHost
: url for Seq server, you need to install Seq in order to use it, you can install it clicking here -
GrayLog
: Some configuration parameters for Graylog service you can install it using this link
For setting it up using the Devon4NetApi template just configure it in the appsettings.Development.json
file.
You can use the methods implemented in Devon4NetLogger class, each method corresponds with a log level in .Net log levels system, for example:
Devon4NetLogger.Debug("Executing GetTodo from controller TodoController");
Install the package on your solution using the Package Manager Console:
install-package Devon4Net.Infrastructure.Logger
Add the following line of code to Progam.cs:
builder.Services.SetupLog(builder.Configuration);
Add the default configuration shown in the configuration section.
use the Devon4NetLogger class methods as explanied above:
Devon4NetLogger.Information("Executing GetSample from controller SampleController");
Allows CORS settings for the devon4Net application. Configuration may be used to configure several domains. Web clients (for example, Angular) must follow this rule to avoid performing AJAX calls to another domain.
Cross-Origin Resource Sharing (CORS) is an HTTP-header-based mechanism that allows a server to specify any origin (domain, scheme, or port) outside of its own from which a browser should allow resources to be loaded. CORS also makes use of a process in which browsers send a "preflight" request to the server hosting the cross-origin resource to ensure that the server will allow the actual request. During that preflight, the browser sends headers indicating the HTTP method as well as headers that will be used in the actual request.
You may find out more by going to Microsoft CORS documentation
Component setup is done in the appsettings.{environment}.json
file using the following structure:
"Cors": //[], //Empty array allows all origins with the policy "CorsPolicy"
[
{
"CorsPolicy": "CorsPolicy",
"Origins": "http://localhost:4200,https://localhost:4200,http://localhost,https://localhost;http://localhost:8085,https://localhost:8085",
"Headers": "accept,content-type,origin,x-custom-header,authorization",
"Methods": "GET,POST,HEAD,PUT,DELETE",
"AllowCredentials": true
}
]
You may add as many policies as you like following the JSON format. for example:
"Cors": //[], //Empty array allows all origins with the policy "CorsPolicy"
[
{
"CorsPolicy": "FirstPolicy",
"Origins": "http://localhost:4200",
"Headers": "accept,content-type,origin,x-custom-header,authorization",
"Methods": "GET,POST,DELETE",
"AllowCredentials": true
},
{
"CorsPolicy": "SecondPolicy",
"Origins": "https://localhost:8085",
"Headers": "accept,content-type,origin",
"Methods": "GET,POST,HEAD,PUT,DELETE",
"AllowCredentials": false
}
]
In the following table all the configuration fields are described:
Property |
Description |
CorsPolicy |
Name of the policy |
Origins |
The origin’s url that you wish to accept. |
Headers |
Permitted request headers |
Methods |
Allowed Http methods |
AllowCredentials |
Set true to allow the exchange of credentials across origins |
For setting it up using the Devon4NetApi template just configure it in the appsettings.Development.json
file.
You can enable CORS per action, per controller, or globally for all Web API controllers in your application:
-
Add this annotation in the Controller Class you want to use CORS policy
[EnableCors("CorsPolicy")]
As an example, consider this implementation on the EmployeeController class
namespace Devon4Net.Application.WebAPI.Implementation.Business.EmployeeManagement.Controllers { /// <summary> /// Employees controller /// </summary> [ApiController] [Route("[controller]")] [EnableCors("CorsPolicy")] public class EmployeeController: ControllerBase { . . . } }
The example above enables CORS for all the controller methods.
-
In the same way, you may enable CORS on any controller method:
[EnableCors("FirstPolicy")] public async Task<ActionResult> GetEmployee() { } public async Task<ActionResult> ModifyEmployee(EmployeeDto employeeDto) { } [EnableCors("SecondPolicy")] public async Task<ActionResult> Delete([Required]long employeeId) { }
The example above enables CORS for the GetEmployee and Delete method.
Using the Package Manager Console, install the the next package on your solution:
install-package Devon4Net.Infrastructure.Cors
Add the following lines of code to Progam.cs:
builder.Services.SetupCors(builder.Configuration);
.
.
.
app.SetupCors();
Add the default configuration shown in the configuration section.
You can enable CORS per action, per controller, or globally for all Web API controllers in your application:
-
Add this annotation to the controller class that will be using the CORS policy.
[EnableCors("SamplePolicy")] public class SampleController: ControllerBase { . . . }
Where "SamplePolicy" is the name you give the Policy in the
appsettings.{environment}.json
.The example above enables CORS for all the controller methods.
-
In the same way, you may enable any CORS-policy on any controller method:
[EnableCors("FirstPolicy")] public async Task<ActionResult> GetSample() { } public async Task<ActionResult> Modify(SampleDto sampleDto) { } [EnableCors("SecondPolicy")] public async Task<ActionResult> Delete([Required]long sampleId) { }
The example above enables CORS for the GetSample and Delete method.
-
If you specify the CORS in the
appsettings.{environment}.json
configuration file as empty array, a default CORS-policy will be used with all origins enabled:
"Cors": [], //Empty array allows all origins with the policy "CorsPolicy"
Warning
|
Only use this policy in development environments |
This default CORS-policy is defined as "CorsPolicy," and it should be enabled on the Controller Class as a standard Policy:
[EnableCors("CorsPolicy")]
public IActionResult Index() {
return View();
}
-
if you want to disable the CORS check use the following annotation on any controller method:
[DisableCors]
public IActionResult Index() {
return View();
}
-
If you set the EnableCors attribute at more than one scope, the order of precedence is:
-
Action
-
Controller
-
Global
-
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with theHMAC
algorithm) or a public/private key pair usingRSA
orECDSA
.
In other words, a JSON Web Token is a JSON object encoded into an encrypted string
that can be decoded and verified making use of cryptographic methods and algorithms. This tokens are mostly used to authenticate users in the context of websites, web applications and web services, but they can also be used to securely exchange information between parties.
Component configuration is made on file appsettings.{environment}.json
as follows:
"JWT": {
"Audience": "devon4Net",
"Issuer": "devon4Net",
"ValidateIssuerSigningKey": true,
"ValidateLifetime": true,
"RequireSignedTokens": true,
"RequireExpirationTime": true,
"RequireAudience": true,
"ClockSkew": 5,
"Security": {
"SecretKeyEncryptionAlgorithm": "",
"SecretKey": "",
"Certificate": "",
"CertificatePassword": "",
"CertificateEncryptionAlgorithm": "",
"RefreshTokenEncryptionAlgorithm": ""
}
},
In the following list all the configuration fields are described:
-
Audience
: Represents a valid audience that will be used to check against the token’s audience. -
Issuer
: Represents a valid issuer that will be used to check against the token’s issuer. -
ValidateIssuerSigningKey
: Boolean that controls if validation of the SecurityKey that signed the securityToken is called. -
ValidateLifetime
: Boolean to control if the lifetime will be validated during token validation. -
RequireSignedTokens
: Boolean that indicates wether a security token has to be signed oe not. -
RequireExpirationTime
: Boolean that tells the handler if tokens need an expiration time specified or not. -
RequireAudience
: Boolean that indicates tokens need to have an audience specified to be valid or not. -
ClockSkew
: Expiration time in minutes. -
Security
: Certificate properties will be found in this part.-
SecretKeyEncryptionAlgorithm
: Algorithm used to encrypt the secret key. If no argument is specified,HmacSha512
is used. -
SecretKey
: Private key used to sign with the certificates. This key will be encrypted and hashed using the specified algorithm. -
Certificate
: Name of certificate file or its path (if it is not in the same directory). If it doesn’t exist an exception will be raised. -
CertificatePassword
: Password for the certificate selected. -
CertificateEncryptionAlgorithm
: Algorithm used to encrypt the certificate. If no argument is specified,HmacSha512
is used. -
RefreshTokenEncryptionAlgorithm
: Algorithm used to encrypt the refresh token. If no argument is specified,HmacSha512
is used.
-
There are two ways of using and creating tokens:
-
Secret key: A key to encrypt and decrypt the tokens is specified. This key will be encrypted using the specified algorithm.
-
Certificates: A certificate is used to manage token encryption and decryption.
Note
|
Because the secret key takes precedence over the other option, JWT with the secret key will be used if both configurations are supplied. |
The supported and tested algorithms are the following:
Algorithm |
Value |
|
HS256 |
|
HS384 |
|
HS512 |
|
|
|
|
|
For the refresh token encryption algorithm you will be able to use any algoritm from the previous table and the following table:
Algorithm |
Value |
|
MD5 |
|
SHA |
Note
|
You will need to specify the name of the algorithm (shown in 'algorithm' column) when configuring the component. |
Note
|
Please check Windows Documentation to get the latest updates on supported encryption algorithms. |
For setting it up using the Devon4NetApi template configure it in the appsettings.{environment}.json
file.
You will need to add a certificate that will be used for signing the token, please check the documentation about how to create a new certificate and add it to a project if you are not aware of how it’s done.
Remember to configure your certificates in the JWT configuration.
Navigate to Devon4Net.Application.WebAPI.Implementation.Business.AuthManagement.Controllers
. There you will find AuthController
sample class which is responsible of generating the token thanks to login method.
public AuthController(IJwtHandler jwtHandler)
{
JwtHandler = jwtHandler;
}
You can see how the IJwtHandler
is injected in the constructor via its interface, which allows you to use its methods.
In the following piece of code, you will find how the client token is created using a variety of claims. In this case this end-point will be available to not identified clients thanks to the AllowAnonymous
attribute. The client will also have a sample role asigned, depending on which it will be able to access some end-points and not others.
[AllowAnonymous]
.
.
.
var token = JwtHandler.CreateClientToken(new List<Claim>
{
new Claim(ClaimTypes.Role, AuthConst.DevonSampleUserRole),
new Claim(ClaimTypes.Name,user),
new Claim(ClaimTypes.NameIdentifier,Guid.NewGuid().ToString()),
});
return Ok(new LoginResponse { Token = token });
The following example will require clients to have the sample role to be able to use the end-point, thanks to the attribute Authorize
with the Roles
value specified.
It also shows how you can obtain information directly from the token using the JwtHandler
injection.
[Authorize(AuthenticationSchemes = AuthConst.AuthenticationScheme, Roles = AuthConst.DevonSampleUserRole)]
.
.
.
//Get claims
var token = Request.Headers["Authorization"].ToString().Replace($"{AuthConst.AuthenticationScheme} ", string.Empty);
.
.
.
// Return result with claims values
var result = new CurrentUserResponse
{
Id = JwtHandler.GetClaimValue(userClaims, ClaimTypes.NameIdentifier),
UserName = JwtHandler.GetClaimValue(userClaims, ClaimTypes.Name),
CorporateInfo = new List<CorporateBasicInfo>
{
new CorporateBasicInfo
{
Id = ClaimTypes.Role,
Value = JwtHandler.GetClaimValue(userClaims, ClaimTypes.Role)
}
}
};
return Ok(result);
Note
|
Please check devon documentation of Security and Roles to learn more about method attributtes. |
Install the package on your solution using the Package Manager Console:
> install-package Devon4Net.Infrastructure.JWT
Configure swagger in Program.cs
adding the following lines:
using Devon4Net.Application.WebAPI.Configuration;
.
.
.
builder.Services.SetupJwt(builder.Configuration);
At this moment you’ll need to have at least one certificate added to your project.
Note
|
Please read the documentation of how to create and add certificates to a project. |
Now we will configure the JWT component in appsettings.{environment}.json
as shown in the next piece of code:
"JWT": {
"Audience": "devon4Net",
"Issuer": "devon4Net",
"ValidateIssuerSigningKey": true,
"ValidateLifetime": true,
"RequireSignedTokens": true,
"RequireExpirationTime": true,
"RequireAudience": true,
"ClockSkew": 5,
"Security": {
"SecretKeyLengthAlgorithm": "",
"SecretKeyEncryptionAlgorithm": "",
"SecretKey": "",
"Certificate": "localhost.pfx",
"CertificatePassword": "12345",
"CertificateEncryptionAlgorithm": "HmacSha512",
"RefreshTokenEncryptionAlgorithm": "Sha"
}
},
For using it, you will need a method that provides you a token. So lets create an AuthController
controller and add those methods:
[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
private readonly IJwtHandler _jwtHandler;
public AuthController(IJwtHandler jwtHandler)
{
_jwtHandler = jwtHandler;
}
[HttpGet]
[Route("/Auth")]
[AllowAnonymous]
public IActionResult GetToken()
{
var token = _jwtHandler.CreateClientToken(new List<Claim>
{
new Claim(ClaimTypes.Role, "MyRole"),
new Claim(ClaimTypes.Name, "MyName"),
new Claim(ClaimTypes.NameIdentifier, Guid.NewGuid().ToString()),
});
return Ok(token);
}
[HttpGet]
[Route("/Auth/CheckToken")]
[Authorize(AuthenticationSchemes = "Bearer", Roles = "MyRole")]
public IActionResult CheckToken()
{
var token = Request.Headers["Authorization"].ToString().Replace($"Bearer ", string.Empty);
var userClaims = _jwtHandler.GetUserClaims(token).ToList();
var result = new
{
Id = _jwtHandler.GetClaimValue(userClaims, ClaimTypes.NameIdentifier),
UserName = _jwtHandler.GetClaimValue(token, ClaimTypes.Name),
Role = _jwtHandler.GetClaimValue(userClaims, ClaimTypes.Role)
};
return Ok(result);
}
}
Reading the code of this controller you have to take in mind a few things:
-
IJwtHandler
class is injected via dependency injection.-
string CreateClientToken(List<Claim> list)
will allow you to create the token through a list of claims. The claims shown are hard-coded examples. -
List<Claim> GetUserClaims(string token)
will allow you to get a list of claims given a token. -
string GetClaimValue(List<Claim> list, string claim)
will allow you to get the value given the ClaimType and either a list of claims or a token thanks to thestring GetClaimValue(string token, string claim)
overload.
-
-
[AllowAnonymous]
attribute will allow access any client without authentication. -
[Authorize(AuthenticationSchemes = "Bearer", Roles = "MyRole")]
attribute will allow any client authenticated with a bearer token and the role"MyRole"
.
LiteDb is an open-source NoSQL embedded database for .NET. Is a document store inspired by MongoDB database. It stores data in documents, which are JSON objects containing key-value pairs. It uses BSON which is a Binary representation of JSON with additional type information.
One of the advantages of using this type of NoSQL database is that it allows the use of asynchronous programming techniques following ACID properties on its transactions. This properties are: Atomicity, Consistency, Isolation and Durability, and they ensure the highest possible data reliability and integrity. This means that you will be able to use async/await
on your operations.
The component configuration can be done in appsettings.{environment}.json
with the following section:
"LiteDb": {
"EnableLiteDb": true,
"DatabaseLocation": "devon4net.db"
}
-
EnableLiteDb
: Boolean to activate the use of LiteDb. -
DatabaseLocation
: Relative path of the file containing all the documents.
For setting it up using the Devon4Net WebApi template just configure it in the appsettings.Development.json
.
Then you will need to inject the repositories. For that go to Devon4Net.Application.WebAPI.Implementation.Configuration.DevonConfiguration
and add the folowing lines in SetupDependencyInjection
method:
using Devon4Net.Infrastructure.LiteDb.Repository;
.
.
.
services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
Now you can use the IRepository<T>
by injecting it wherever you want to use it. T
will be the entity you will be working with in the repository.
private readonly IRepository<Todo> _todoRepository;
public TodoController(IRepository<Todo> todoRepository)
{
_todoRepository = todoRepository;
}
For setting it up in other projects install it running the followin command in the Package Manager Console, or using the Package Manager in Visual Studio:
install-package Devon4Net.Infrastructure.LiteDb
Now set the configuration in the appsettings.{enviroment}.json
:
"LiteDb": {
"EnableLiteDb": true,
"DatabaseLocation": "devon_database.db"
}
Note
|
Remember to set EnableLiteDb to true .
|
Navigate to your Program.cs
file and add the following line to configure the component:
using Devon4Net.Application.WebAPI.Configuration;
.
.
.
builder.Services.SetupLiteDb(builder.Configuration);
You will need also to add the repositories you will be using to your services, either by injecting the generic:
builder.Services.AddTransient(typeof(IRepository<>), typeof(Repository<>));
Or by choosing to inject them one by one:
builder.Services.AddTransient<IRepository<WeatherForecast>, Repository<WeatherForecast>>();
Now you will be able to use the repositories in your class using dependency injection, for example:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IRepository<WeatherForecast> _weatherForecastRepository;
public WeatherForecastController(IRepository<WeatherForecast> weatherForecastRepository)
{
_weatherForecastRepository = weatherForecastRepository;
}
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return _weatherForecastRepository.Get();
}
[HttpPost]
public IEnumerable<WeatherForecast> PostAndGetAll(WeatherForecast weatherForecast)
{
_weatherForecastRepository.Create(weatherForecast);
return _weatherForecastRepository.Get();
}
}
Validation is an automatic check to ensure that data entered is sensible and feasible. It is critical to add validation for data inputs when programming. This avoids unexpected or anomalous data from crashing your application and from obtaining unrealistic garbage outputs.
In the following table some validation methods are described:
Validation Method |
Description |
Range check |
Checks if the data is inside a given range. |
Type check |
Checks that the data entered is of an expected type |
Length check |
Checks the number of characters meets expectations |
Presence check |
Checks that the user has at least inputted something |
Check digit |
An additional digit added to a number that is computed from the other digits; this verifies that the remainder of the number has been input correctly. |
FluentValidation is a.NET library that allows users to create strongly-typed validation rules.
To establish a set of validation criteria for a specific object, build a class that inherits from CustomFluentValidator<T>
, where T
is the type of class to validate. For example:
public class EmployeeFluentValidator : CustomFluentValidator<Employee>
{
}
Where Employee is the class to validate.
Create a constructor for this class that will handle validation exceptions, and override the CustomValidate() method from the CustomFluentValidator<T>
class to include the validation rules.
public class EmployeeFluentValidator : CustomFluentValidator<Employee>
{
/// <summary>
///
/// </summary>
/// <param name="launchExceptionWhenError"></param>
public EmployeeFluentValidator(bool launchExceptionWhenError) : base(launchExceptionWhenError)
{
}
/// <summary>
///
/// </summary>
public override void CustomValidate()
{
RuleFor(Employee => Employee.Name).NotNull();
RuleFor(Employee => Employee.Name).NotEmpty();
RuleFor(Employee => Employee.SurName).NotNull();
RuleFor(Employee => Employee.Surname).NotEmpty();
}
}
In this example, we want Employee entity to not accept Null or empty data. We can notice this error if we do not enter the needed data:

We can also develop Custom Validators by utilizing the Predicate Validator to define a custom validation function. In the example above we can add:
RuleFor(x => x.Todos).Must(list => list.Count < 10)
.WithMessage("The list must contain fewer than 10 items");
This rule restricts the Todo List from having more than ten items.
Note
|
For more information about Validators (Rules, Custom Validators, etc…) please refer to this link |
Install the package on your solution using the Package Manager Console:
install-package Devon4Net.Infrastructure.FluentValidation
Follow the instructions described in the previous section.
Library that contains common classes to manage the web api template configuration.
The main classes are described in the table below:
Folder |
Classes |
Description |
Common |
AutoRegisterData.cs |
Contains the data supplied between the various stages of the AutoRegisterDi extension methods |
Http |
ProtocolOperation.cs |
Contains methods to obtain the Http or Tls protocols |
IO |
FileOperations.cs |
Contains methods for managing file operations. |
Constants |
AuthConst.cs |
Default values for AuthenticationScheme property in the JwtBearerAuthenticationOptions |
Enums |
MediaType.cs |
Static class providing constants for different media types for the CircuitBreaker Handlers. |
Exceptions |
HttpCustomRequestException.cs |
Public class that enables to create Http Custom Request Exceptions |
Exceptions |
IWebApiException.cs |
Interface for webapi exceptions |
Handlers |
OptionsHandler.cs |
Class with a method for retrieving the configuration of the components implementing the options pattern |
Helpers |
AutoRegisterHelpers.cs |
Contains the extension methods for registering classes automatically |
Helpers |
StaticConstsHelper.cs |
Assists in the retrieval of an object’s value through reflection |
The options pattern uses classes to provide strongly typed access to groups of related settings.
It is usually preferable to have a group of related settings packed together in a highly typed object rather than simply a plain key-value pair collection.
For the other hand strong typing will always ensure that the configuration settings have the required data types.
Keeping related settings together ensures that the code meets two crucial design criteria: encapsulation and separation of concerns.
Note
|
If you require more information of the options pattern, please see the official Microsoft documentation. |
On this component, we have an Options folder that has the classes with all the attributes that store all of the configuration parameters.
Miscellaneous extension library which contains :
-
ObjectTypeHelper
-
JsonHelper
-
GuidExtension
-
HttpContextExtensions
-
HttpRequestExtensions
Provides a method for converting an instance of an object in the type of an object of a specified class name.
Serialization is the process of transforming an object’s state into a form that can be saved or transmitted. Deserialization is the opposite of serialization in that it transforms a stream into an object. These procedures, when combined, allow data to be stored and transferred.
Note
|
More information about serializacion may be found in the official Microsoft documentation. |
This helper is used in the devon4net components CircuitBreaker
, MediatR
, and RabbitMQ
.
This class has basic methods for managing GUIDs. Some devon4net components, such as MediatR
or RabbitMQ
, implement it in their Backup Services.
Provides methods for managing response headers for example:
-
TryAddHeader
method is used ondevon4Net.Infrastructure.Middleware
component to add automatically response header options such authorization. -
TryRemoveHeader
method is used ondevon4Net.Infrastructure.Middleware
component to remove automatically response header such AspNetVersion header.
Provides methods for obtaining Culture and Language information from a HttpRequest
object.
This component employs the MediatR
library, which is a tool for implementing CQRS and Mediator patterns in .Net.
MediatR
handles the decoupling of the in-process sending of messages from handling messages.
-
Mediator pattern:
The mediator pattern is a behavioral design pattern that aids in the reduction of object dependencies. The pattern prevents the items from communicating directly with one another, forcing them to collaborate only through a mediator object. Mediator is used to decrease the communication complexity between multiple objects or classes. This pattern offers a mediator class that manages all communications between distinct classes and allows for easy code maintenance through loose coupling.
-
CQRS pattern:
The acronym CQRS stands for Command and Query Responsibility Segregation, and it refers to a design that separates read and update processes for data storage. By incorporating CQRS into your application, you may improve its performance, scalability, and security. The flexibility gained by moving to CQRS enables a system to grow more effectively over time and prevents update instructions from triggering merge conflicts at the domain level.
Figure 3. CQRS DiagramIn this figure, we can see how we may implement this design by utilizing a Relational Database for Write operations and a Materialized view of this Database that is synchronized and updated via events.
In MediatR
, you build a basic class that is identified as an implementation of the IRequest or IAsyncRequest interface.
All of the properties that are required to be in the message will be defined in your message class.
In the case of this component the messages are created in the ActionBase<T>
class:
public class ActionBase<T> : IRequest<T> where T : class
{
public DateTime Timestamp { get; }
public string MessageType { get; }
public Guid InternalMessageIdentifier { get; }
protected ActionBase()
{
Timestamp = DateTime.Now;
InternalMessageIdentifier = Guid.NewGuid();
MessageType = GetType().Name;
}
}
This ActionBase<T>
class is then inherited by the CommandBase<T>
and QueryBase<T>
classes.
Now that we’ve built a request message, we can develop a handler to reply to any messages of that type. We must implement the IRequestHandler
or IAsyncRequestHandler
interfaces, describing the input and output types.
In the case of this component MediatrRequestHandler<TRequest, TResponse>
abstract class is used for making this process generecic
public abstract class MediatrRequestHandler<TRequest, TResponse> : IRequestHandler<TRequest, TResponse> where TRequest : IRequest<TResponse>
This interface defines a single method called Handle, which returns a Task of your output type.
This expects your request message object as an argument. In the MediatrRequestHandler<TRequest, TResponse>
class has been implemented in this way.
public async Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken)
{
MediatrActions status;
TResponse result = default;
try
{
result = await HandleAction(request, cancellationToken).ConfigureAwait(false);
status = MediatrActions.Handled;
}
catch (Exception ex)
{
Devon4NetLogger.Error(ex);
status = MediatrActions.Error;
}
await BackUpMessage(request, status).ConfigureAwait(false);
return result;
}
The HandleAction
method is defined in the following lines:
public abstract Task<TResponse> HandleAction(TRequest request, CancellationToken cancellationToken);
This method should be overridden in the application’s business layer Handlers.
Component configuration is made on file appsettings.{environment}.json
as follows:
"MediatR": {
"EnableMediatR": true,
"Backup": {
"UseLocalBackup": true,
"DatabaseName": "devon4netMessageBackup.db"
}
},
Property |
Description |
EnableMediatR |
True for enabling the use of MediatR component |
UseLocalBackup |
True for using a LiteDB database as a local backup for the |
DatabaseName |
The name of the LiteDB database |
For setting it up using the Devon4NetApi template just configure it in the appsettings.Development.json
file.
A template is available in the MediatRManagement folder of the Devon4Net.Application.WebAPI.Implementation
Business Layer:

As we can see, this example adheres to the CQRS pattern structure, with Commands for writing methods and Queries for reading operations, as well as one handler for each method:
-
CreateTodoCommand.cs
:public class CreateTodoCommand : CommandBase<TodoResultDto> { public string Description { get; set; } public CreateTodoCommand(string description) { Description = description; } }
The CreateTodoCommand inherits from
CommandBase<T>
, in this situation, the request message’s additional properties, such asDescription
of theTodo
entity, will be included. -
GetTodoQuery.cs
:public class GetTodoQuery : QueryBase<TodoResultDto> { public long TodoId{ get; set; } public GetTodoQuery(long todoId) { TodoId = todoId; } }
Because GetTodoQuery inherits from
QueryBase<T>
, anTodoId
of theTodo
object will be attached to the message’s properties in this case. -
CreateTodoHandler.cs
:public class CreateTodoHandler : MediatrRequestHandler<CreateTodoCommand, TodoResultDto> { private ITodoService TodoService { get; set; } public CreateTodoHandler(ITodoService todoService, IMediatRBackupService mediatRBackupService, IMediatRBackupLiteDbService mediatRBackupLiteDbService) : base(mediatRBackupService, mediatRBackupLiteDbService) { Setup(todoService); } public CreateTodoHandler(ITodoService todoService, IMediatRBackupLiteDbService mediatRBackupLiteDbService) : base(mediatRBackupLiteDbService) { Setup(todoService); } public CreateTodoHandler(ITodoService todoService, IMediatRBackupService mediatRBackupService) : base(mediatRBackupService) { Setup(todoService); } private void Setup(ITodoService todoService) { TodoService = todoService; } public override async Task<TodoResultDto> HandleAction(CreateTodoCommand request, CancellationToken cancellationToken) { var result = await TodoService.CreateTodo(request.Description).ConfigureAwait(false); return new TodoResultDto { Id = result.Id, Done = result.Done, Description = result.Description }; } }
This class must to inherit from
MediatrRequestHandler<TRequest, TResponse>
class that is explained above. On first place we inject the TodoService via dependency injection using theSetup(ITodoService todoService)
method, and then we overload theHandleAction(TRequest request, CancellationToken cancellationToken)
method calling the service and returning the new DTO -
GetTodoHandler.cs
:All handlers may be configured using the same structure as
CreateTodoHandler.cs.
To do the required operation, just change the method called by the service.
Install the package in your solution using the Package Manager Console:
Install-Package Devon4Net.Infrastructure.MediatR
Create a Configuration static class in order to add the IRequestHandler
services, for example:
public static class Configuration
{
public static void SetupDependencyInjection(this IServiceCollection services, IConfiguration configuration)
{
var mediatR = serviceProvider.GetService<IOptions<MediatROptions>>();
if (mediatR?.Value != null && mediatR.Value.EnableMediatR)
{
SetupMediatRHandlers(services);
}
}
private static void SetupMediatRHandlers(IServiceCollection services)
{
services.AddTransient(typeof(IRequestHandler<GetTodoQuery, TodoResultDto>), typeof(GetTodoHandler));
services.AddTransient(typeof(IRequestHandler<CreateTodoCommand, TodoResultDto>), typeof(CreateTodoHandler));
}
}
Add the following lines in the Program.cs
class:
builder.Services.SetupMediatR(builder.Configuration);
builder.Services.SetupDependencyInjection(builder.Configuration);
After adding the default settings provided in the configuration section, you may use the MediatR component in your code.
RabbitMQ
is an open-source message-broker software (also known as message-oriented middleware) that was developed to support the Advanced Message Queuing Protocol (AMQP) and has since been expanded with a plug-in architecture to support the Streaming Text Oriented Messaging Protocol (STOMP), MQ Telemetry Transport (MQTT), and other protocols.
In RabbitMQ
, queues are defined to store messages sent by producers until they are received and processed by consumer applications.
-
Publisher-Subscriber pattern
Publish-Subscribe is a design pattern that allows loose coupling between the application components.
Message senders, known as publishers, do not configure the messages to be sent directly to specific receivers, known as subscribers. Messages are released with no information of what they are or if any subscribers to that information exist. Delegate is the core of this C# design pattern.
Figure 5. RabbitMQ Queue systemTo summarize :
-
A producer is a user application that sends messages.
-
A queue is a buffer that stores messages.
-
A consumer is a user application that receives messages.
-
In the case of this component the messages are created in the Message
abstract class:
public abstract class Message
{
public string MessageType { get; }
public Guid InternalMessageIdentifier { get; set; }
protected Message()
{
MessageType = GetType().Name;
}
}
Then the Command
serializable class inherits from Message
class:
[Serializable]
public class Command : Message
{
public DateTime Timestamp { get; protected set; }
protected Command()
{
Timestamp = DateTime.Now;
InternalMessageIdentifier = Guid.NewGuid();
}
}
The message will have from base a Timestamp, a Guid as message identifier and the message type.
Component configuration is made on file appsettings.{environment}.json
as follows:
"RabbitMq": {
"EnableRabbitMq": true,
"Hosts": [
{
"Host": "127.0.0.1",
"Port": 5672,
"Ssl": false,
"SslServerName": "localhost",
"SslCertPath": "localhost.pfx",
"SslCertPassPhrase": "localhost",
"SslPolicyErrors": "RemoteCertificateNotAvailable" //None, RemoteCertificateNotAvailable, RemoteCertificateNameMismatch, RemoteCertificateChainErrors
}
],
"VirtualHost": "/",
"UserName": "admin",
"Password": "password",
"Product": "devon4net",
"RequestedHeartbeat": 10, //Set to zero for no heartbeat
"PrefetchCount": 50,
"PublisherConfirms": false,
"PersistentMessages": true,
"Platform": "localhost",
"Timeout": 10,
"Backup": {
"UseLocalBackup": true,
"DatabaseName": "devon4netMessageBackup.db"
}
},
Note
|
Please refer to the official EasyNetQ documentation for further details about connection parameters. |
For setting it up using the Devon4NetApi template configure it in the appsettings.{environment}.json
file.
A template is available in the RabbitMqManagement folder of the Devon4Net.Application.WebAPI.Implementation
Business folder:

-
TodoCommand.cs
:public class TodoCommand : Command { public string Description { get; set; } }
The
TodoCommand
inherits fromCommand
, in this case, theDescription
will be added to theMessage
. -
TodoRabbitMqHandler.cs
:public class TodoRabbitMqHandler: RabbitMqHandler<TodoCommand> { private ITodoService TodoService { get; set; } public TodoRabbitMqHandler(IServiceCollection services, IBus serviceBus, bool subscribeToChannel = false) : base(services, serviceBus, subscribeToChannel) { } public TodoRabbitMqHandler(IServiceCollection services, IBus serviceBus, IRabbitMqBackupService rabbitMqBackupService, bool subscribeToChannel = false) : base(services, serviceBus, rabbitMqBackupService, subscribeToChannel) { } public TodoRabbitMqHandler(IServiceCollection services, IBus serviceBus, IRabbitMqBackupLiteDbService rabbitMqBackupLiteDbService, bool subscribeToChannel = false) : base(services, serviceBus, rabbitMqBackupLiteDbService, subscribeToChannel) { } public TodoRabbitMqHandler(IServiceCollection services, IBus serviceBus, IRabbitMqBackupService rabbitMqBackupService, IRabbitMqBackupLiteDbService rabbitMqBackupLiteDbService, bool subscribeToChannel = false) : base(services, serviceBus, rabbitMqBackupService, rabbitMqBackupLiteDbService, subscribeToChannel) { } public override async Task<bool> HandleCommand(TodoCommand command) { TodoService = GetInstance<ITodoService>(); var result = await TodoService.CreateTodo(command.Description).ConfigureAwait(false); return result!=null; } }
This class must to inherit from
RabbitMqHandler<T>
class.HandleCommand(T command)
method should be overridden in order to send command to the queue, this method returns true if the message has been published.
Install the package in your solution using the Package Manager Console:
Install-Package Devon4Net.Infrastructure.RabbitMQ
Create a Configuration static class in order to add the RabbitMqHandler
services, for example:
public static class Configuration
{
public static void SetupDependencyInjection(this IServiceCollection services, IConfiguration configuration)
{
var rabbitMq = serviceProvider.GetService<IOptions<RabbitMqOptions>>();
if (rabbitMq?.Value != null && rabbitMq.Value.EnableRabbitMq)
{
SetupRabbitHandlers(services);
}
}
private static void SetupRabbitHandlers(IServiceCollection services)
{
services.AddRabbitMqHandler<TodoRabbitMqHandler>(true);
}
}
Add the following lines in the Program.cs
class:
builder.Services.SetupRabbitMq(builder.Configuration);
builder.Services.SetupDependencyInjection(builder.Configuration);
After adding the default settings provided in the configuration section, you may use the RabbitMQ component in your code.
Note
|
Please see the RabbitMQ official documentation for instructions on installing the RabbitMQ Server. |
Middleware is software that’s assembled into an app pipeline to handle requests and responses. Request delegates are used to construct the request pipeline. Each HTTP request is handled by a request delegate.
The diagram below represents the whole request processing pipeline for ASP.NET Core MVC and Razor Pages apps. You can see how existing middlewares are organized in a typical app and where additional middlewares are implemented.

The ASP.NET Core request pipeline is composed of a number of request delegates that are called one after the other. This concept is illustrated in the diagram below. The execution thread is shown by the black arrows.

In this component there are four custom Middlewares classes, configuration is made on file appsettings.{environment}.json
as follows:
-
ClientCertificatesMiddleware.cs
: For the management of client certificates."Certificates": { "ServerCertificate": { "Certificate": "", "CertificatePassword": "" }, "ClientCertificate": { "EnableClientCertificateCheck": false, "RequireClientCertificate": false, "CheckCertificateRevocation": true, "ClientCertificates": { "Whitelist": [ "" ] } } },
The ClientCertificate Whitelist contains the client’s certificate thumbprint.
-
ExceptionHandlingMiddleware.cs
: Handles a few different types of exceptions. -
CustomHeadersMiddleware.cs
: To add or remove certain response headers."Headers": { "AccessControlExposeHeader": "Authorization", "StrictTransportSecurityHeader": "", "XFrameOptionsHeader": "DENY", "XssProtectionHeader": "1;mode=block", "XContentTypeOptionsHeader": "nosniff", "ContentSecurityPolicyHeader": "", "PermittedCrossDomainPoliciesHeader": "", "ReferrerPolicyHeader": "" },
On the sample above, the server application will add to the response headers the
AccessControlExposeHeader
,XFrameOptionsHeader
,XssProtectionHeader
andXContentTypeOptionsHeader
headers. If the header response attribute does not have a value, it will not be added to the response headers.NotePlease refer to the How To: Customize Headers documentation for more information. Figure 9. Response Headers -
KillSwicthMiddleware.cs
: To enable or disable HTTP requests."KillSwitch": { "UseKillSwitch": false, "EnableRequests": true, "HttpStatusCode": 403 },
Property Description UseKillSwitch
True to enable KillSwtich middleware
EnableRequests
True to enable HTTP requests.
HttpStatusCode
the HTTP status code that will be returned
For setting it up using the Devon4NetApi template just configure it in the appsettings.Development.json file.
Install the package on your solution using the Package Manager Console:
install-package Devon4Net.Infrastructure.Middleware
Configure the component in Program.cs
adding the following lines:
using Devon4Net.Infrastructure.Middleware.Middleware;
.
.
.
builder.Services.SetupMiddleware(builder.Configuration);
.
.
.
app.SetupMiddleware(builder.Services);
Add the default configuration shown in the configuration section.
The idea of Unit of Work is related to the successful implementation of the Repository Pattern. It is necessary to first comprehend the Repository Pattern in order to fully understand this concept.
A repository is a class defined for an entity, that contains all of the operations that may be executed on that entity. For example, a repository for an entity Employee will contain basic CRUD operations as well as any additional potential actions connected to it. The following procedures can be used to implement the Repository Pattern:
-
One repository per entity (non-generic) : This approach makes use of a single repository class for each entity. For instance, if you have two entities, Todo and Employee, each will have its own repository.
-
Generic repository: A generic repository is one that can be used for all entities.
Unit of Work is referred to as a single transaction that involves multiple operations of insert/update/delete. It means that, for a specific user action, all transactions are performed in a single transaction rather than several database transactions.

Connection strings must be added to the configuration in the file appsettings.environment.json
as follows:
"ConnectionStrings": {
"Todo": "Add your database connection string here",
"Employee": "Add your database connection string here"
},
For setting it up using the Devon4NetApi template just configure the connection strings in the appsettings.Development.json
file.
To add Databases, use the SetupDatabase method in the DevonConfiguration.cs
file:
private static void SetupDatabase(IServiceCollection services, IConfiguration configuration)
{
services.SetupDatabase<TodoContext>(configuration, "Todo", DatabaseType.SqlServer, migrate:true).ConfigureAwait(false);
services.SetupDatabase<EmployeeContext>(configuration, "Employee", DatabaseType.SqlServer, migrate:true).ConfigureAwait(false);
}
You must provide the configuration, the connection string key, and the database type.
The supported databases are:
-
SqlServer
-
Sqlite
-
InMemory
-
Cosmos
-
PostgreSQL
-
MySql
-
MariaDb
-
FireBird
-
Oracle
-
MSAccess
Set the migrate property value to true if you need to use migrations, as shown above.
Note
|
For more information about the use of migrations please visit the official microsoft documentation. |
Our typed repositories must inherit from the generic repository of the Unit Of Work component, as seen in the example below:
public class TodoRepository : Repository<Todos>, ITodoRepository
{
public TodoRepository(TodoContext context) : base(context)
{
}
.
.
.
}
Use the methods of the generic repository to perform your CRUD actions:
public Task<Todos> Create(string description)
{
var todo = new Todos {Description = description};
return Create(todo);
}
The default value for AutoSaveChanges
to the Database is true, you may change it to false if you need to employ transactions.
Inject the Repository on the service of the business layer, as shown below:
public class TodoService: Service<TodoContext>, ITodoService
{
private readonly ITodoRepository _todoRepository;
public TodoService(IUnitOfWork<TodoContext> uoW) : base(uoW)
{
_todoRepository = uoW.Repository<ITodoRepository>();
}
.
.
.
}
Install the package on your solution using the Package Manager Console:
install-package Devon4Net.Infrastructure.UnitOfWork
Create a Configuration static class in order to add the IRequestHandler
services, for example:
public static class Configuration
{
public static void SetupDependencyInjection(this IServiceCollection services, IConfiguration configuration)
{
SetupDatabase(services, configuration);
}
private static void SetupDatabase(IServiceCollection services, IConfiguration configuration)
{
services.SetupDatabase<TodoContext>(configuration, "Default", DatabaseType.SqlServer).ConfigureAwait(false);
}
}
Configure the component in Program.cs
adding the following lines:
using Devon4Net.Domain.UnitOfWork;
.
.
.
builder.Services.SetupUnitOfWork(typeof(Configuration));
Add the default configuration shown in the configuration section and follow the same steps as the previous section.
Predicate expression builder
-
Use this expression builder to generate lambda expressions dynamically:
var predicate = PredicateBuilder.True<T>();
Where T
is a class. At this moment, you can build your expression and apply it to obtain your results in a efficient way and not retrieving data each time you apply an expression.
-
Example from My Thai Star .Net Core implementation:
public async Task<PaginationResult<Dish>> GetpagedDishListFromFilter(int currentpage, int pageSize, bool isFav, decimal maxPrice, int minLikes, string searchBy, IList<long> categoryIdList, long userId)
{
var includeList = new List<string>{"DishCategory","DishCategory.IdCategoryNavigation", "DishIngredient","DishIngredient.IdIngredientNavigation","IdImageNavigation"};
//Here we create our predicate builder
var dishPredicate = PredicateBuilder.True<Dish>();
//Now we start applying the different criteria:
if (!string.IsNullOrEmpty(searchBy))
{
var criteria = searchBy.ToLower();
dishPredicate = dishPredicate.And(d => d.Name.ToLower().Contains(criteria) || d.Description.ToLower().Contains(criteria));
}
if (maxPrice > 0) dishPredicate = dishPredicate.And(d=>d.Price<=maxPrice);
if (categoryIdList.Any())
{
dishPredicate = dishPredicate.And(r => r.DishCategory.Any(a => categoryIdList.Contains(a.IdCategory)));
}
if (isFav && userId >= 0)
{
var favourites = await UoW.Repository<UserFavourite>().GetAllAsync(w=>w.IdUser == userId);
var dishes = favourites.Select(s => s.IdDish);
dishPredicate = dishPredicate.And(r=> dishes.Contains(r.Id));
}
// Now we can use the predicate to retrieve data from database with just one call
return await UoW.Repository<Dish>().GetAllIncludePagedAsync(currentpage, pageSize, includeList, dishPredicate);
}
This documentation is licensed under the Creative Commons License (Attribution-NoDerivatives 4.0 International).