BEFactoryBusinessLayer 1.0.16
See the version list below for details.
dotnet add package BEFactoryBusinessLayer --version 1.0.16
NuGet\Install-Package BEFactoryBusinessLayer -Version 1.0.16
<PackageReference Include="BEFactoryBusinessLayer" Version="1.0.16" />
paket add BEFactoryBusinessLayer --version 1.0.16
#r "nuget: BEFactoryBusinessLayer, 1.0.16"
// Install BEFactoryBusinessLayer as a Cake Addin #addin nuget:?package=BEFactoryBusinessLayer&version=1.0.16 // Install BEFactoryBusinessLayer as a Cake Tool #tool nuget:?package=BEFactoryBusinessLayer&version=1.0.16
Backend Factory
A library Back End for c# developer
History version
[v1.0.13] 2024-06-16
Added
- property ResponseContentBody on httpsClientHelper
[v1.0.14] 2024-06-21
Code review
- Code review on httpsClientHelper
When it is necessary not to deserialize a response from an httpClient call, it is possible to invoke the sednAsync method passing the object class as the type. In this case the http call payload will be returned. Here is an example:
protected httpsClientHelper _httpsClientHelper;
_httpsClientHelper.sendAsync<object>(
response.URLApiFeed,
response.ContentType,
response.httpMethod,
(exception) => loggerExtension.Trace(response.UrlADM, request.CorrelationId, Serilog.Events.LogEventLevel.Error, null, "Eccezione: {exception}", exception),
(nrretry) => loggerExtension.Trace(response.UrlADM, request.CorrelationId, Serilog.Events.LogEventLevel.Warning, null, "Numero di retry pari a : {nrretry}", nrretry)
).GetAwaiter().GetResult();
string payload = _httpsClientHelper.ResponseContentBody;
Topics
- 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
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
- AppSettings.json:
"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
- AppSettings.json:
"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>>();
/*
Inject RabbitMQ service:
This add delegate to run youir 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
- - MessageType where defined is a string property set on property RMQ to identify your action
addhostedrabbitService inject a AddHostedService to manage queue
*/
builder.Services.addhostedrabbitService<ApplicationDbContext>(
(dbcontext, channelOptions, payload, CorrelationId, MessageType) => new Feedfactory(channelSettings).ConsumeMessage((ApplicationDbContext)dbcontext, channelOptions, payload, CorrelationId, MessageType)
);
In this case all the business logic is performed by the ConsumeMessage method of the Feedfactory class which reads all the queues configured on appSettings.
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
-
-
- Program.cs: Config host and inject services.
#region RabbitMQ
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.AddDbContext<ApplicationDbContext>(options => {
options.UseSqlServer(builder.Configuration.GetConnectionString("default"),
sqlServerOptionsAction: sqloptions => sqloptions.EnableRetryOnFailure());
}, ServiceLifetime.Scoped);
builder.Services.addhostedrabbitService<ApplicationDbContext>(
(dbcontext, channelOptions, payload, CorrelationId, MessageType) => new Feedfactory(channelSettings).ConsumeMessage((feedDbContext)dbcontext, channelOptions, payload, CorrelationId, MessageType)
);
#endregion
For every message consumed by RMQ youcan use lambda like :
(dbcontext, channelOptions, payload, CorrelationId, MessageType) => new Feedfactory(channelSettings).ConsumeMessage((feedDbContext)dbcontext, channelOptions, payload, CorrelationId, MessageType)
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)
- 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)
- 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.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.