BEFactoryBusinessLayer 1.0.18
dotnet add package BEFactoryBusinessLayer --version 1.0.18
NuGet\Install-Package BEFactoryBusinessLayer -Version 1.0.18
<PackageReference Include="BEFactoryBusinessLayer" Version="1.0.18" />
paket add BEFactoryBusinessLayer --version 1.0.18
#r "nuget: BEFactoryBusinessLayer, 1.0.18"
// Install BEFactoryBusinessLayer as a Cake Addin #addin nuget:?package=BEFactoryBusinessLayer&version=1.0.18 // Install BEFactoryBusinessLayer as a Cake Tool #tool nuget:?package=BEFactoryBusinessLayer&version=1.0.18
Backend Factory
A library helkper Back End for c# developer
🚩 Table of Contents
- BEFactoryBusinessLayer.Auth
- BEFactoryBusinessLayer.BackgroundJobs
- BEFactoryBusinessLayer.caching
- BEFactoryBusinessLayer.http
- BEFactoryBusinessLayer.Linq
- BEFactoryBusinessLayer.Logger
- BEFactoryBusinessLayer.resilience
- BEFactoryBusinessLayer.RMQ
- BEFactoryBusinessLayer.TaskHelper
- BEFactoryBusinessLayer.Validation
Auth
Library for authorize to get a controller (documentation soon online)
BackgroundJobs
This Library is used principalmente per eseguire chiamate che durano molto tempo , ed è utile soprattutto quando bisogna eseguire un operazione a seguito di un numero considerevole di chiamate client diversi uno dall' altra a meno di parametri che devono effettuare la stessa operazione. In questo caso , soprattutto quando ci troviamo di fronte a una server farm , usare un mecanismo di semaforo centralizzato può essere dispendiodo. In Questo caso subentra hangFire dove impostando un tempo di delay così come si fa con il pattern throttle non viene eseguita la chiamata server prima che scada il tempo indicato.
Injection:
- program.cs:
#region HANGFIRE
GlobalConfiguration.Configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(builder.Configuration.GetSection("Hangfire:ConnectionString").Value)
.UseConsole();
builder.Services.AddHangfireServer(options => new BackgroundJobServerOptions { CancellationCheckInterval = TimeSpan.FromSeconds(2) });
builder.Services.AddHangfire(x => x.UseSqlServerStorage(builder.Configuration.GetSection("Hangfire:ConnectionString").Value));
#endregion
//....
//after
var app = builder.Build();
//....
#region HANGFIRE
app.UseHangfireDashboard("/jobs", new DashboardOptions {
IsReadOnlyFunc = (DashboardContext context) => bool.Parse(builder.Configuration.GetSection("Hangfire:Dashboard:IsReadOnly").Value),
Authorization = new[] { new MyAuthorizationFilter() }
});
#endregion
Configuration:
"Hangfire": {
"Dashboard": {
"IpAuthorized": "::1",
"IsReadOnly": true
},
"ConnectionString": "<YOUR_CONNECTIONsTRING>"
}
- example call:
- [HttpGet("StartJobAfterSecondsValue")]
public async Task<IActionResult> StartJob(int secondDelayToStart) {
ThrottleDispatcher throttleDispatcher = new ThrottleDispatcher(memoryCacheHelper);
throttleDispatcher.Throttle<TaskInbackround>(secondDelayToStart, service => service.yourmethod(), "DownloadSquadreGiocatori");
return Ok();
}
Library for manage Hangfire (documentation soon online)
Caching
This project is used to manage InMemoeryCache and Redis. In practice, with a single call it is possible to read the data from the cache by first checking the presence of the local cache (InMemoryCache) in case it has not been saved previously (because for example we are on a server farm) it is read from the Redis cache. Every time the Get method is executed (passing the type of data to be returned: <T>), the data described above is captured. At each execution of the Get method, the cache is then written to be retrieved later.
Injection:
- program.cs:
builder.Services.InjectCache(builder.Configuration);
Configuration:
"RedisCacheOptions": {
"Host": "10.0.1.243",
"Port": 1111,
"IsRedisCacheEnabled": true,
"IsInMemoryCacheEnabled": true,
"ConnectRetry": 1,
"ReconnectRetryPolicy": 1000,
"ConnectTimeout": 10000,
"SyncTimeout": 10000,
"AbortOnConnectFail": false
}
Paremeter | Value |
---|---|
Host | Endpoint of Reds Cache |
Port | Port of rediCache |
IsRedisCacheEnabled | If true InMemoryCache is Enabled |
IsInMemoryCacheEnabled | If true InMemoryCache is Enabled |
ConnectRetry | Number retry to reconnect |
ReconnectRetryPolicy | Exponential reconnection policy |
ConnectTimeout | Timeout in milliseconds to reconnect |
SyncTimeout | Synchronous operations timeout in milliseconds |
AbortOnConnectFail | Do not interrupt if the connection fails on startup |
Warning: if Redis should not be available, the service does not crash because the initialization of the connection to Redis occurs in a deferred manner via the Lazy command
How to use : memoryCacheHelper.Get<T>(cacheKey, expirationMilliseconds, method, ChangeResponseForAddExtraInfo, WriteLoggerOnException, InvalidCacheWhen, redis)
cacheKey: Key Name of cache
expirationMilliseconds: Number of millisecods for cache
method: string to rapresent action where you are using the cache
ChangeResponseForAddExtraInfo: Func<T, T> in case you want change response of class T
WriteLoggerOnException : Action to execute that accpet paremeter Exception in case of errors
InvalidCacheWhen : Func<T, bool> when condition with class T return true avoid to save cache
example of use
[HttpGet("GetDataFromCache")]
public async Task<IActionResult> GetDataFromCache(string cacheKey, int expirationMilliseconds) {
dynamic responsehttp = memoryCacheHelper
.Get<dynamic>(
cacheKey,
expirationMilliseconds,
() => getDataFromSource(cacheKey),
(data) => {
var name = JObject.Parse(data.ToString())["data"]["first_name"];
return $"found {name}";
},
(ex) => Console.WriteLine($"Eccezione {ex.Message}"),
(dc) => dc is null);
return Ok(responsehttp);
}
private dynamic getDataFromSource(string source) {
dynamic response = new httpsClientHelper(
httpFactory,
source,
(action, HttpRequest, HttpResponse, dtStart, dtEnd, idTransaction, NrRetry, exception, HttpStatusResponse)
=> loggerExtension.Trace("test http", DateTime.Now.ToString(), HttpResponse.StatusCode != System.Net.HttpStatusCode.OK ? Serilog.Events.LogEventLevel.Warning : Serilog.Events.LogEventLevel.Information, null, "Trace HTTP : action = {action}, Request = {HttpRequest}, Response = {HttpResponse}, dtStart = {dtStart}, dtEnd = {dtEnd}, IdTransaction = {idTransaction}, NrRetry = {NrRetry}, Exception {Exception}, status HTTP : {HttpStatusResponse}", action, HttpRequest.ToString(), HttpResponse.ToString(), dtStart, dtEnd, idTransaction, NrRetry, exception, HttpStatusResponse)
, true)
.sendAsync<object>($"http://reqres.in/api/users/{source}", "application/json", "GET").GetAwaiter().GetResult();
return response;
}
- Explanation of the parameters passed to the Get method
- cacheKey : String passed to the method representing the cache key
- expirationMilliseconds : Value passed to the method indicating the number of milliseconds the cache should last
csharp () => getDataFromSource(cacheKey)
Func to invoke-
(data) => { var name = JObject.Parse(data.ToString())["data"]["first_name"]; return $"found {name}"; }``` In case you need to change the answer for some reason you can use this Func<T, T>
csharp (ex) => Console.WriteLine($"Eccezione {ex.Message}")
Action to perform in case of an exception (you could also execute the trace method of the loggerExtension classcsharp (dc) => dc is null;
Action to use if even if there is no exception it is possible to use a condition to not save the cache
httpsClientHelper
This library allows you to manage different scenarios for using named HttpClients via IHttpClientFactory injection
Configuration
"HttpClientOptions": [
{
"name": "reqres",
"certificate": {
"path": "your_path_Certificate",
"password": "your_password_"
},
"RateLimitOptions": {
"AutoReplenishment": true,
"PermitLimit": 150,
"QueueLimit": 10,
"Window": "00:01:00",
"SegmentsPerWindow": 100
}
},
{
"name": "yourclientName2",
"certificate": {
"path": "your_path_Certificate",
"password": "your_password_"
},
"RateLimitOptions": {
"AutoReplenishment": true,
"PermitLimit": 1,
"QueueLimit": 1,
"Window": "00:00:03",
"SegmentsPerWindow": 100
}
}
]
program.cs:
builder.Services.AddHttpClients(builder.Configuration);
Once everything has been configured and the line on the program.cs class has been added we are ready to exploit the httpClient class to satisfy different scenarios
example:
[HttpGet("sample")]
public async Task<IActionResult> sample(bool AcceptAnyCertificate, string RateLimiteName) {
httpsClientHelper httpsClientHelper = new httpsClientHelper(
_httpFactory
,Guid.NewGuid().ToString()
, (action, HttpRequest, HttpResponse, dtStart, dtEnd, idTransaction, NrRetry, exception, HttpStatusResponse)
=> loggerExtension.Trace("test http", DateTime.Now.ToString(), HttpResponse.StatusCode != System.Net.HttpStatusCode.OK ? Serilog.Events.LogEventLevel.Warning : Serilog.Events.LogEventLevel.Information, null, "Trace HTTP : action = {action}, Request = {HttpRequest}, Response = {HttpResponse}, dtStart = {dtStart}, dtEnd = {dtEnd}, IdTransaction = {idTransaction}, NrRetry = {NrRetry}, Exception {Exception}, status HTTP : {HttpStatusResponse}", action, HttpRequest.ToString(), HttpResponse.ToString(), dtStart, dtEnd, idTransaction, NrRetry, exception, HttpStatusResponse)
, AcceptAnyCertificate
);
httpsClientHelper
.LoadHttpHandler(_httpClientOption.Where(a => a.name == RateLimiteName).FirstOrDefault())
.setHeadersAndBasicAuthentication(new Dictionary<string, string> { { "Alex", "Alex" } }, new httpsClientHelper.httpClientAuthenticationBasic("Alex", "Alex"))
.setRetryOptions(new RetryFactoryOptions {
ActionOnRetry = (result, timespan, retryCount) => { /* something to do for alert a retry */},
delayForRetry = new TimeSpan[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5) },
ConditionForRetry = (http) => http.StatusCode == System.Net.HttpStatusCode.NotFound
});
for (int i = 0; i < 30; i++) {
HttpResponseMessage message = await httpsClientHelper.sendAsync<HttpResponseMessage>(
$"https://reqres.in/api/users/{i}",
"application/json",
"get"
);
if (message!= null) {
string response = await message.Content.ReadAsStringAsync();
}
}
return Ok();
}
In the httpsClientHelper constructor, we pass as parameters
the interface IHttpClientFactory ( _httpFactory )
A guide like IdTransaction
An Action that accepts as parameters:
- action: To log the context in which you are making the http call
- HttpRequest The HttpRequestMessage
- HttpResponse The HttpsponseMessage
- dtStart Is time when request is invoked
- dtEnd Corresponds to the time the response arrives
- idTransaction It is a Guid (string) in case you want to check in the logs what happened for that transaction
- NrRetry Matches the number of retries if the setRetryOptions method is also added
- exception Matches the exception ( is a string ) in case the call fails Corresponds to the exception (it is a string) in case the call fails (intended as an unhandled exception, in case you want to track a response with httpStatus other than 200, you can use the httpResponse parameter)
- HttpStatusResponse Corresponds to the HttpStatus of the response The parameters declared by appsettings in the HttpClientOptions:RateLimitOptions path are loaded (where name corresponds to the value passed into the controller)
The next usefull method LoadHttpHandler (loads the DelegatingHandler interface passing the parameters configured to appsettings.json for the desired name) In particular, there are two parameters:
- AcceptAnyCertificate ( if is true bypasses the error in case the certificate is expired or invalid (the certificate is loaded from the parameters declared on appSettings, i.e. HttpClientOptions:certificate:path and HttpClientOptions:certificate:password)
- RateLimiteName ( In this case the SlidingWindowRateLimiter option is loaded as rate limit with the default QueueProcessingOrder value (in a future release the possibility of passing other types of rate limits will be added) )
- method setHeadersWithoutAuthorization ( if you want to pass headers to HTTP calls )
- method setHeadersAndBearerAuthentication ( if you want to pass headers to HTTP calls and Bearer Auhentication)
- method setHeadersAndBasicAuthentication ( if you want to pass headers to HTTP calls and Basic Authentication )
loggerExtension
Use Serilog with several sinks and customizations
- AppSettings.json:
"SerilogConfiguration": {
"SerilogCondition": [
{
"Sink": "Email",
"Level": [ "Error", "Critical" ]
},
{
"Sink": "ElasticSearch",
"Level": [ "Critical" ]
},
{
"Sink": "MSSqlServer",
"Level": [ "Information", "Warning", "Error", "Critical" ]
},
{
"Sink": "Telegram",
"Level": [ "Critical" ]
},
{
"Sink": "File",
"Level": [ "Information", "Error", "Critical" ]
}
],
"SerilogOption": {
"TelegramOption": {
"telegramApiKey": "YOUR_API_KEY",
"telegramChatId": "YOUR_CHAT_ID"
},
"MSSqlServer": {
"connectionString": "default",
"sinkOptionsSection": {
"tableName": "Logs",
"schemaName": "EventLogging",
"autoCreateSqlTable": true,
"batchPostingLimit": 1000,
"period": "0.00:00:30"
},
"columnOptionsSection": {
"addStandardColumns": [ "LogEvent" ],
"removeStandardColumns": [ "Properties" ],
"customColumns": [
{
"ColumnName": "Username",
"DataType": "nvarchar",
"DataLength": 50,
"AllowNull": true
},
{
"ColumnName": "IdTransazione",
"DataType": "nvarchar",
"DataLength": 50,
"AllowNull": true
}
]
}
}
}
},
"Serilog": {
"Using": [ "Serilog.Sinks.Console", "Serilog.Sinks.File", "Serilog.Sinks.MSSqlServer", "Serilog.Sinks.Email" ],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Elasticsearch",
"Args": {
"nodeUris": "http://10.0.1.119:9200",
"indexFormat": "PixeloApp"
}
},
{
"Name": "Email",
"Args": {
"connectionInfo": {
"FromEmail": "alexbypa@gmail.com",
"ToEmail": "alexbypa@gmail.com",
"MailServer": "smtp.gmail.com",
"EmailSubject": "Fatal Error",
"NetworkCredentials": {
"userName": "alexbypa@gmail.com",
"password": "asdasd"
},
"Port": 587,
"EnableSsl": true
},
"restrictedToMinimumLevel": "Verbose"
}
},
{
"Name": "Console"
},
{
"Name": "File",
"Args": {
"path": "logs/log.txt",
"rollingInterval": "Day"
}
}
]
}
For every sink you can define wich logs will write for any level:
SerilogConfiguration:SerilogCondition:Sink ( Nome of sink)
SerilogConfiguration:SerilogCondition:Level ( array level to match) For example with this configuration for sink Email log will sent email only for level Error and Critical
SerilogConfiguration:SerilogOption:MSSqlServer Is classic Serilog configuration for Serilog sinks MSSqlServer
section Serilog Serilog Configuration
definition
- How to call:
/// <summary>
/// method to write log
/// </summary>
/// <param name="Action">Action is the parameter that indicates the area of ​​interest in which the log is being written</param>
/// <param name="IdTransaction">IdTransaction is the reference code for retrieving the log (for example, if you use a warehouse program, each operation on a product could be the barcode of the item)</param>
/// <param name="level">level is the LogEventLevel of serilog</param>
/// <param name="ex">is the text of the error to report</param>
/// <param name="message">indicates the log message</param>
/// <param name="args">are additional parameters that can help identify particular scenarios</param>
public static void Trace(string Action, string IdTransaction, LogEventLevel level, Exception? ex, string message, params object[] args) {
// example :
loggerExtension.Trace("test", "123456789", LogEventLevel.Error, new Exception("exception test"), "Message test with this {value}", "value test");
In this case the log will write like that :
{
"TimeStamp": "2024-06-25T12:26:46.8938230",
"Level": "Error",
"Message": "Message test with this \"value test\" \"123456789\" \"PIXELO30\" \"test\"",
"MessageTemplate": "Message test with this {value} {IdTransaction} {MachineName} {Action}",
"Exception": "System.Exception: exception test",
"Properties": {
"value": "value test",
"IdTransaction": "123456789",
"MachineName": "PIXELO30",
"Action": "test"
}
}
And if you want search on SQL for one of args parameter ( like the example value = "value test")
you can use ths query syntax :
SELECT * FROM Tracert.logs where json_value(LogEvent, '$.Properties.value') = 'value test'
Resilience
Very useful Polly client features (documentation soon online)
RMQ
Use RabbitMQ to admin with more channel and isolate business logic to consume it
Configuration
"rabbitMQChannelsOptions": [
{
"Name": "FirstName",
"IdFeed": 1,
"RabbitEndPoint": {
"HostName": "Your_Endpoint",
"UserName": "Your_UserName",
"Password": "Your_Password",
"ClientProvidedName": "Your_ClientName",
"VirtualHost": "Your_Virtual",
"QueueName": "Your_QueueName_",
"RejectMessageWithError": true
}
},
{
"Name": "SecondName",
"IdFeed": 2,
"RabbitEndPoint": {
"HostName": "Your_Endpoint",
"UserName": "Your_UserName",
"Password": "Your_Password",
"ClientProvidedName": "Your_ClientName",
"VirtualHost": "Your_Virtual",
"QueueName": "Your_QueueName_",
"RejectMessageWithError": true
}
}
]
- Name : used to identify the name of the queue to manage
- IdFeed : Id of channel
- RabbitEndPoint:HostName : Url of RMQ producer
- RabbitEndPoint:UserName : Username of RMQ producer
- RabbitEndPoint:Password : Password of RMQ producer
- RabbitEndPoint:ClientProvidedName : ClientProvidedName of RMQ producer
- RabbitEndPoint:VirtualHost : VirtualHost of RMQ producer
- RabbitEndPoint:QueueName : QueueName of RMQ producer
- RabbitEndPoint:RejectMessageWithError : boolean value, when true the response is inserted into the unacknowledged message queue
program.cs:
//To load Configuration
builder.Services.AddOptions();
var rabbitMQChannelsOptions = builder.Configuration.GetSection("rabbitMQChannelsOptions");
builder.Services.Configure<List<RabbitMQChannelsOptions>>(rabbitMQChannelsOptions);
List<RabbitMQChannelsOptions> channelSettings = builder.Configuration.GetSection("rabbitMQChannelsOptions").Get<List<RabbitMQChannelsOptions>>();
builder.Services.addhostedrabbitService<ApplicationDbContext>(
(dbcontext, channelOptions, payload, CorrelationId, MessageType) => new Feedfactory(channelSettings)
.ConsumeMessage((ApplicationDbContext)dbcontext, channelOptions, payload, CorrelationId, MessageType));
Inject RabbitMQ service: This add delegate to run your businesseLogic, in this case i add Func<DbContext, RabbitMQChannelsOptions, string, string, string, Response> Where :
- DbContext is your custom DbContext to manage SQL Server
- RabbitMQChannelsOptions is option load before
- payload is content sent from RMQ
- CorrelationId is value sent from header RMQ (It's optional, I use it to uniquely identify each message)
- MessageType where defined is a string property set on property RMQ to identify your action
In this case, by injecting the addhostedrabbitService singleton method (it is an AddHostedService), every time a message arrives on the queue configured on appSettings.json, your business logic is executed (in my case I use a Feedfactory class that uses an abstract factory and consumes all messages with the ConsumeMessage main method Note that a custom one named ApplicationDbContext is passed as DbContext`
Also in case you need to use HttpClient to consume an http request you can use the HttpsClientHelper library by taking the IHttpClientFactory class. This is done via the piece of code written below:`
var _httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
IactionParseRMQ iactionParseRMQ = scope.ServiceProvider.GetRequiredService<IactionParseRMQ>();
iactionParseRMQ._httpClientFactory = _httpClientFactory;
responseCheck = iactionParseRMQ.RunCommandOnConsuming(iactionParseRMQ._httpClientFactory, iactionParseRMQ._dbContextClient, feed, payload, CorrelationId, MessageType);
TaskHelper (documentation soon online)
Some example to use Task async
Validation (documentation soon online)
To use a response with some Pattern Design
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- AspNetCore.HealthChecks.SqlServer (>= 8.0.2)
- AspNetCore.HealthChecks.UI (>= 8.0.1)
- AspNetCore.HealthChecks.UI.Client (>= 8.0.1)
- AspNetCore.HealthChecks.UI.Core (>= 8.0.1)
- AspNetCore.HealthChecks.UI.Data (>= 8.0.1)
- AspNetCore.HealthChecks.UI.InMemory.Storage (>= 8.0.1)
- AspNetCore.HealthChecks.UI.SqlServer.Storage (>= 8.0.1)
- AspNetCore.HealthChecks.Uris (>= 8.0.1)
- EntityFramework (>= 6.4.4)
- Hangfire (>= 1.8.11)
- Hangfire.Console (>= 1.4.3)
- Hangfire.Tags (>= 1.8.5)
- Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore (>= 8.0.6)
- Microsoft.EntityFrameworkCore (>= 8.0.6)
- Microsoft.EntityFrameworkCore.Relational (>= 8.0.6)
- Microsoft.EntityFrameworkCore.SqlServer (>= 8.0.6)
- Microsoft.Extensions.Caching.StackExchangeRedis (>= 8.0.5)
- Polly (>= 8.3.1)
- Polly.Core (>= 8.4.1)
- Polly.RateLimiting (>= 8.4.1)
- RabbitMQ.Client (>= 6.8.1)
- Serilog.AspNetCore (>= 8.0.1)
- Serilog.Sinks.Elasticsearch (>= 10.0.0)
- Serilog.Sinks.Email (>= 3.0.0)
- Serilog.Sinks.MSSqlServer (>= 6.6.0)
- Serilog.Sinks.Telegram (>= 0.2.1)
- StackExchange.Redis (>= 2.7.33)
- System.Data.SqlClient (>= 4.8.6)
- System.IdentityModel.Tokens.Jwt (>= 7.5.2)
- System.Reactive (>= 6.0.1)
- System.Threading.RateLimiting (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.