BEFactoryBusinessLayer 1.0.16

dotnet add package BEFactoryBusinessLayer --version 1.0.16
NuGet\Install-Package BEFactoryBusinessLayer -Version 1.0.16
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="BEFactoryBusinessLayer" Version="1.0.16" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add BEFactoryBusinessLayer --version 1.0.16
#r "nuget: BEFactoryBusinessLayer, 1.0.16"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// 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


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 class
    • csharp (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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.16 0 6/28/2024
1.0.15 55 6/25/2024
1.0.14 72 6/21/2024
1.0.13 69 6/20/2024
1.0.12 73 6/16/2024
1.0.11 65 6/16/2024
1.0.10 68 6/15/2024
1.0.9 61 6/12/2024
1.0.8 65 6/9/2024
1.0.7 76 6/6/2024
1.0.6 62 6/4/2024