From 3e8100e1c584404f17988a2a248d8219a3dfc5db Mon Sep 17 00:00:00 2001 From: Ignacio Gomez Date: Mon, 23 Jun 2025 01:55:17 -0600 Subject: [PATCH] First version of BFF --- .../Controllers/BaseController.cs | 49 +++++ .../Controllers/FurnitureBaseController.cs | 142 +++++++++++++++ .../Controllers/FurnitureVariantController.cs | 167 ++++++++++++++++++ .../Controllers/WeatherForecastController.cs | 33 ---- .../Core.Inventory.BFF.API.csproj | 10 +- Core.Inventory.BFF.API/Program.cs | 130 ++++++++++++-- .../Properties/launchSettings.json | 6 +- Core.Inventory.BFF.API/WeatherForecast.cs | 13 -- Core.Inventory.BFF.API/appsettings.Local.json | 12 ++ Core.Inventory.BFF.sln | 11 ++ .../RegisterClientConfiguration.cs | 65 +++++++ .../Inventory/IInventoryServiceClient.cs | 51 ++++++ .../Base/ChangeFurnitureBaseStatusRequest.cs | 10 ++ .../Base/CreateFurnitureBaseRequest.cs | 16 ++ .../Base/GetAllFurnitureBaseRequest.cs | 6 + .../Base/GetFurnitureBaseByIdRequest.cs | 7 + .../Base/UpdateFurnitureBaseRequest.cs | 19 ++ .../Requests/Common/DimensionsRequest.cs | 9 + .../ChangeFurnitureVariantStatusRequest.cs | 10 ++ .../Variant/CreateFurnitureVariantRequest.cs | 19 ++ ...GetAllFurnitureVariantsByModelIdRequest.cs | 7 + .../Variant/GetFurnitureVariantByIdRequest.cs | 7 + .../GetFurnitureVariantsByIdsRequest.cs | 7 + .../Variant/UpdateFurnitureVariantRequest.cs | 20 +++ .../Core.Inventory.External.csproj | 15 ++ .../GatewayConfiguration.cs | 19 ++ .../GatewaySettingsConfigurations.cs | 48 +++++ .../TrackingMechanismExtension.cs | 18 ++ 28 files changed, 865 insertions(+), 61 deletions(-) create mode 100644 Core.Inventory.BFF.API/Controllers/BaseController.cs create mode 100644 Core.Inventory.BFF.API/Controllers/FurnitureBaseController.cs create mode 100644 Core.Inventory.BFF.API/Controllers/FurnitureVariantController.cs delete mode 100644 Core.Inventory.BFF.API/Controllers/WeatherForecastController.cs delete mode 100644 Core.Inventory.BFF.API/WeatherForecast.cs create mode 100644 Core.Inventory.BFF.API/appsettings.Local.json create mode 100644 Core.Inventory.External/ClientConfiguration/RegisterClientConfiguration.cs create mode 100644 Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Base/ChangeFurnitureBaseStatusRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Base/CreateFurnitureBaseRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Base/GetAllFurnitureBaseRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Base/GetFurnitureBaseByIdRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Base/UpdateFurnitureBaseRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Common/DimensionsRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Variant/ChangeFurnitureVariantStatusRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Variant/CreateFurnitureVariantRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Variant/GetAllFurnitureVariantsByModelIdRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantByIdRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantsByIdsRequest.cs create mode 100644 Core.Inventory.External/Clients/Inventory/Requests/Variant/UpdateFurnitureVariantRequest.cs create mode 100644 Core.Inventory.External/Core.Inventory.External.csproj create mode 100644 Core.Inventory.External/GatewayConfigurations/GatewayConfiguration.cs create mode 100644 Core.Inventory.External/GatewayConfigurations/GatewaySettingsConfigurations.cs create mode 100644 Core.Inventory.External/TrackingMechanismExtension.cs diff --git a/Core.Inventory.BFF.API/Controllers/BaseController.cs b/Core.Inventory.BFF.API/Controllers/BaseController.cs new file mode 100644 index 0000000..5fbe860 --- /dev/null +++ b/Core.Inventory.BFF.API/Controllers/BaseController.cs @@ -0,0 +1,49 @@ + +using Lib.Architecture.BuildingBlocks; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Refit; + +namespace Core.Inventory.BFF.API.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class BaseController(ILogger logger) : ControllerBase + { + private readonly ILogger _logger = logger; + + protected Guid TrackingId => (Guid)(HttpContext.Items["TrackingId"] ?? Guid.NewGuid()); + + protected async Task Handle(Func>> apiCall) where T : class + { + var response = await apiCall().ConfigureAwait(false); + + _logger.LogInformation($"{TrackingId} - {response.RequestMessage?.Method} {response.RequestMessage?.RequestUri} - Status: {response.StatusCode}"); + + return FromAPIResponse(response); + } + + private IActionResult FromAPIResponse(ApiResponse response) where T : class + { + if (response.IsSuccessful) + return StatusCode((int)response.StatusCode, response.Content); + else + { + dynamic errorContent = string.Empty; + + try + { + errorContent = JsonConvert.DeserializeObject(response.Error?.Content ?? string.Empty) ?? string.Empty; + } + catch (Exception) + { + errorContent = JsonConvert.DeserializeObject(response.Error?.Content); + if (errorContent?.Error?.ErrorCode is null && errorContent?.Error?.Message is null && errorContent?.Error?.Target is null) + errorContent = JsonConvert.DeserializeObject(response.Error?.Content); + + } + return StatusCode((int)response.StatusCode, errorContent); + } + } + } +} diff --git a/Core.Inventory.BFF.API/Controllers/FurnitureBaseController.cs b/Core.Inventory.BFF.API/Controllers/FurnitureBaseController.cs new file mode 100644 index 0000000..472a4a3 --- /dev/null +++ b/Core.Inventory.BFF.API/Controllers/FurnitureBaseController.cs @@ -0,0 +1,142 @@ +using Asp.Versioning; +using Core.Adapters.Lib; +using Core.Inventory.External.Clients.Inventory; +using Core.Inventory.External.Clients.Inventory.Requests.Base; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; + +namespace Core.Inventory.BFF.API.Controllers +{ + /// + /// Handles all requests for furniture base operations. + /// + [ApiVersion("1.0")] + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [Consumes("application/json")] + [Produces("application/json")] + public class FurnitureBaseController(IInventoryServiceClient inventoryClient, ILogger logger) : BaseController(logger) + { + /// + /// Gets all furniture base records. + /// + [HttpGet("GetAll")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetAllAsync(CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetAllAsync)} - Request received."); + return await Handle(() => inventoryClient.GetAllAsync(TrackingId.ToString(), cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(GetAllAsync)} - An error occurred."); + throw; + } + } + + /// + /// Gets a furniture base by its identifier. + /// + [HttpPost("GetById")] + [ProducesResponseType(typeof(FurnitureBase), StatusCodes.Status200OK)] + public async Task GetByIdAsync([FromBody] GetFurnitureBaseByIdRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetByIdAsync)} - Request received. Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrWhiteSpace(request.MongoId)) + return BadRequest("FurnitureBase MongoId is required."); + + return await Handle(() => inventoryClient.GetByIdAsync(TrackingId.ToString(), request, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(GetByIdAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Creates a new furniture base record. + /// + [HttpPost("Create")] + [ProducesResponseType(typeof(FurnitureBase), StatusCodes.Status201Created)] + public async Task CreateAsync([FromBody] CreateFurnitureBaseRequest newFurniture, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(CreateAsync)} - Request received. Payload: {JsonSerializer.Serialize(newFurniture)}"); + + if (newFurniture == null) return BadRequest("Invalid furniture object"); + + if (string.IsNullOrEmpty(newFurniture.Name)) return BadRequest("Invalid furniture name"); + + if (string.IsNullOrEmpty(newFurniture.Material)) return BadRequest("Invalid furniture material"); + + if (string.IsNullOrEmpty(newFurniture.Condition)) return BadRequest("Invalid furniture condition"); + + return await Handle(() => inventoryClient.CreateAsync(TrackingId.ToString(), newFurniture, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(CreateAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(newFurniture)}"); + throw; + } + } + + /// + /// Updates a furniture base record. + /// + [HttpPut("Update")] + [ProducesResponseType(typeof(FurnitureBase), StatusCodes.Status200OK)] + public async Task UpdateAsync([FromBody] UpdateFurnitureBaseRequest newFurniture, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(UpdateAsync)} - Request received. Payload: {JsonSerializer.Serialize(newFurniture)}"); + + if (newFurniture == null) return BadRequest("Invalid furniture object"); + + if (string.IsNullOrEmpty(newFurniture.Name)) return BadRequest("Invalid furniture name"); + + if (string.IsNullOrEmpty(newFurniture.Material)) return BadRequest("Invalid furniture material"); + + if (string.IsNullOrEmpty(newFurniture.Condition)) return BadRequest("Invalid furniture condition"); + + if (string.IsNullOrWhiteSpace(newFurniture.Id)) return BadRequest("Id is required."); + + return await Handle(() => inventoryClient.UpdateFurnitureBaseAsync(TrackingId.ToString(), newFurniture, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(UpdateAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(newFurniture)}"); + throw; + } + } + + /// + /// Changes the status of a furniture base. + /// + [HttpPatch("ChangeStatus")] + [ProducesResponseType(typeof(FurnitureBase), StatusCodes.Status200OK)] + public async Task ChangeStatusAsync([FromBody] ChangeFurnitureBaseStatusRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(ChangeStatusAsync)} - Request received. Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrWhiteSpace(request.MongoId)) return BadRequest("Id is required."); + + return await Handle(() => inventoryClient.ChangeFurnitureBaseStatusAsync(TrackingId.ToString(), request, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(ChangeStatusAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(request)}"); + throw; + } + } + } +} diff --git a/Core.Inventory.BFF.API/Controllers/FurnitureVariantController.cs b/Core.Inventory.BFF.API/Controllers/FurnitureVariantController.cs new file mode 100644 index 0000000..e7d8a80 --- /dev/null +++ b/Core.Inventory.BFF.API/Controllers/FurnitureVariantController.cs @@ -0,0 +1,167 @@ +using Asp.Versioning; +using Core.Adapters.Lib; +using Core.Inventory.External.Clients.Inventory; +using Core.Inventory.External.Clients.Inventory.Requests.Variant; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json; + +namespace Core.Inventory.BFF.API.Controllers +{ + /// + /// Handles all requests for furniture variant operations. + /// + [ApiVersion("1.0")] + [Route("api/v{version:apiVersion}/[controller]")] + [ApiController] + [Consumes("application/json")] + [Produces("application/json")] + public class FurnitureVariantController(IInventoryServiceClient inventoryClient, ILogger logger) : BaseController(logger) + { + /// + /// Gets furniture variants by model ID. + /// + [HttpPost("GetAllByModelId")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetAllByModelIdAsync([FromBody] GetAllFurnitureVariantsByModelIdRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetAllByModelIdAsync)} - Request received. Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrWhiteSpace(request.ModelId)) return BadRequest("ModelId is required."); + + return await Handle(() => inventoryClient.GetVariantsByModelIdAsync(TrackingId.ToString(), request, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(GetAllByModelIdAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Gets a furniture variant by its MongoId. + /// + [HttpPost("GetById")] + [ProducesResponseType(typeof(FurnitureVariant), StatusCodes.Status200OK)] + public async Task GetByIdAsync([FromBody] GetFurnitureVariantByIdRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetByIdAsync)} - Request received. Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrWhiteSpace(request.MongoId)) return BadRequest("MongoId is required."); + + return await Handle(() => inventoryClient.GetFurnitureVariantByIdAsync(TrackingId.ToString(), request, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(GetByIdAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Gets furniture variants by a list of IDs. + /// + [HttpPost("GetByIds")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetByIdsAsync([FromBody] GetFurnitureVariantsByIdsRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(GetByIdsAsync)} - Request received. Payload: {JsonSerializer.Serialize(request)}"); + + if (request?.Ids == null || request.Ids.Count == 0) + return BadRequest("At least one Id is required."); + + return await Handle(() => inventoryClient.GetFurnitureVariantsByIdsAsync(TrackingId.ToString(), request, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(GetByIdsAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(request)}"); + throw; + } + } + + /// + /// Creates a new furniture variant. + /// + [HttpPost("Create")] + [ProducesResponseType(typeof(FurnitureVariant), StatusCodes.Status201Created)] + public async Task CreateAsync([FromBody] CreateFurnitureVariantRequest newVariant, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(CreateAsync)} - Request received. Payload: {JsonSerializer.Serialize(newVariant)}"); + + if (newVariant == null) return BadRequest("Invalid furniture object"); + + if (string.IsNullOrEmpty(newVariant.ModelId)) return BadRequest("Invalid furniture modelId"); + + if (string.IsNullOrEmpty(newVariant.Name)) return BadRequest("Invalid furniture name"); + + if (string.IsNullOrEmpty(newVariant.Color)) return BadRequest("Invalid furniture color"); + + return await Handle(() => inventoryClient.CreateFurnitureVariantAsync(TrackingId.ToString(), newVariant, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(CreateAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(newVariant)}"); + throw; + } + } + + /// + /// Updates a furniture variant. + /// + [HttpPut("Update")] + [ProducesResponseType(typeof(FurnitureVariant), StatusCodes.Status200OK)] + public async Task UpdateAsync([FromBody] UpdateFurnitureVariantRequest newVariant, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(UpdateAsync)} - Request received. Payload: {JsonSerializer.Serialize(newVariant)}"); + + if (newVariant == null) return BadRequest("Invalid furniture object"); + + if (string.IsNullOrEmpty(newVariant.ModelId)) return BadRequest("Invalid furniture modelId"); + + if (string.IsNullOrEmpty(newVariant.Name)) return BadRequest("Invalid furniture name"); + + if (string.IsNullOrEmpty(newVariant.Color)) return BadRequest("Invalid furniture color"); + + if (string.IsNullOrWhiteSpace(newVariant.Id)) return BadRequest("Id is required."); + + return await Handle(() => inventoryClient.UpdateFurnitureVariantAsync(TrackingId.ToString(), newVariant.Id, newVariant, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(UpdateAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(newVariant)}"); + throw; + } + } + + /// + /// Changes the status of a furniture variant. + /// + [HttpPatch("ChangeStatus")] + [ProducesResponseType(typeof(FurnitureVariant), StatusCodes.Status200OK)] + public async Task ChangeStatusAsync([FromBody] ChangeFurnitureVariantStatusRequest request, CancellationToken cancellationToken) + { + try + { + logger.LogInformation($"{nameof(ChangeStatusAsync)} - Request received. Payload: {JsonSerializer.Serialize(request)}"); + + if (string.IsNullOrWhiteSpace(request.MongoId)) return BadRequest("Id is required."); + + return await Handle(() => inventoryClient.ChangeFurnitureVariantStatusAsync(TrackingId.ToString(), request, cancellationToken)); + } + catch (Exception ex) + { + logger.LogError(ex, $"{nameof(ChangeStatusAsync)} - An error occurred. Payload: {JsonSerializer.Serialize(request)}"); + throw; + } + } + } +} diff --git a/Core.Inventory.BFF.API/Controllers/WeatherForecastController.cs b/Core.Inventory.BFF.API/Controllers/WeatherForecastController.cs deleted file mode 100644 index 6749026..0000000 --- a/Core.Inventory.BFF.API/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace Core.Inventory.BFF.API.Controllers -{ - [ApiController] - [Route("[controller]")] - public class WeatherForecastController : ControllerBase - { - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } - } -} diff --git a/Core.Inventory.BFF.API/Core.Inventory.BFF.API.csproj b/Core.Inventory.BFF.API/Core.Inventory.BFF.API.csproj index 5419ef0..ceb7811 100644 --- a/Core.Inventory.BFF.API/Core.Inventory.BFF.API.csproj +++ b/Core.Inventory.BFF.API/Core.Inventory.BFF.API.csproj @@ -7,7 +7,15 @@ - + + + + + + + + + diff --git a/Core.Inventory.BFF.API/Program.cs b/Core.Inventory.BFF.API/Program.cs index 48863a6..1ca86ec 100644 --- a/Core.Inventory.BFF.API/Program.cs +++ b/Core.Inventory.BFF.API/Program.cs @@ -1,25 +1,133 @@ +using Core.Blueprint.Logging.Configuration; +using Core.Inventory.External; +using Core.Inventory.External.ClientConfiguration; +using Microsoft.AspNetCore.ResponseCompression; +using OpenTelemetry.Logs; +using OpenTelemetry.Resources; +using Swashbuckle.AspNetCore.SwaggerUI; +using System.IO.Compression; +using System.Reflection; + var builder = WebApplication.CreateBuilder(args); -// Add services to the container. - -builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Configuration + .AddUserSecrets(Assembly.GetExecutingAssembly()) + .AddEnvironmentVariables(); + +builder.Services.AddResponseCompression(); +builder.Services.AddProblemDetails(); +builder.Services.AddLogs(builder); +builder.Services.AddMemoryCache(); +builder.Services.AddResponseCaching(configureOptions => { configureOptions.UseCaseSensitivePaths = true; }); +builder.Logging.AddOpenTelemetry(options => +{ + options.IncludeScopes = true; + options.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("core.inventory.bff.api")).AddConsoleExporter(); +}); + +builder.Host.ConfigureServices((context, services) => +{ + builder.Services.AddHsts(options => + { + options.Preload = true; + options.IncludeSubDomains = true; + options.MaxAge = TimeSpan.FromDays(60); + }); + builder.Services.AddResponseCaching(configureOptions => + { + configureOptions.UseCaseSensitivePaths = true; + configureOptions.MaximumBodySize = 2048; + }); + builder.Services.AddHttpsRedirection(options => + { + options.RedirectStatusCode = 308; + }); + + services.AddHttpLogging(http => + { + http.CombineLogs = true; + }); + services.AddAntiforgery(); + + services.AddCors(options => + { + options.AddPolicy("AllowAll", policyBuilder => + policyBuilder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()); + }); + services.AddMvc().AddJsonOptions(options => + { + options.JsonSerializerOptions.WriteIndented = true; + options.JsonSerializerOptions.MaxDepth = 20; + options.JsonSerializerOptions.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.AllowNamedFloatingPointLiterals; + }); + services.Configure(options => + { + options.Level = CompressionLevel.SmallestSize; + }); + services.Configure(options => + { + options.Level = CompressionLevel.SmallestSize; + }); + services.AddResponseCompression(options => + { + options.EnableForHttps = true; + options.Providers.Add(); + options.Providers.Add(); + }); + services.AddResponseCaching(); + services.AddControllers(); + services.AddEndpointsApiExplorer(); + services.AddSwaggerGen(); + services.AddLogging(); + services.AddProblemDetails(); + services.AddHttpContextAccessor(); + services.AddTransient(); // Register the TrackingIdHandler + services.RegisterExternalLayer(builder.Configuration); + + services.AddApiVersioning(options => options.ReportApiVersions = true) + .AddApiExplorer(options => + { + options.GroupNameFormat = "'v'VVV"; + options.SubstituteApiVersionInUrl = true; + }); +}); + +builder.Services.AddCors(options => +{ + options.AddDefaultPolicy( + builder => + { + builder.AllowAnyOrigin() + .AllowAnyHeader() + .AllowAnyMethod(); + }); +}); + +//*************************************************************************// var app = builder.Build(); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) +app.UseCors(options => options.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); +app.UseSwagger(); +app.UseSwaggerUI(options => { - app.UseSwagger(); - app.UseSwaggerUI(); -} + foreach (var version in app.DescribeApiVersions().Select(version => version.GroupName)) + options.SwaggerEndpoint($"/swagger/{version}/swagger.json", version); + options.DisplayRequestDuration(); + options.EnableTryItOutByDefault(); + options.DocExpansion(DocExpansion.None); +}); +app.UseResponseCompression(); +app.UseResponseCaching(); app.UseHttpsRedirection(); +app.UseAuthentication(); app.UseAuthorization(); - app.MapControllers(); +app.UseHsts(); +app.UseAntiforgery(); +app.UseLogging(builder.Configuration); app.Run(); diff --git a/Core.Inventory.BFF.API/Properties/launchSettings.json b/Core.Inventory.BFF.API/Properties/launchSettings.json index 3d2d3fe..9a32e7b 100644 --- a/Core.Inventory.BFF.API/Properties/launchSettings.json +++ b/Core.Inventory.BFF.API/Properties/launchSettings.json @@ -16,7 +16,7 @@ "launchUrl": "swagger", "applicationUrl": "http://localhost:5101", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Local" } }, "https": { @@ -26,7 +26,7 @@ "launchUrl": "swagger", "applicationUrl": "https://localhost:7223;http://localhost:5101", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Local" } }, "IIS Express": { @@ -34,7 +34,7 @@ "launchBrowser": true, "launchUrl": "swagger", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Local" } } } diff --git a/Core.Inventory.BFF.API/WeatherForecast.cs b/Core.Inventory.BFF.API/WeatherForecast.cs deleted file mode 100644 index 8e65558..0000000 --- a/Core.Inventory.BFF.API/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Core.Inventory.BFF.API -{ - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -} diff --git a/Core.Inventory.BFF.API/appsettings.Local.json b/Core.Inventory.BFF.API/appsettings.Local.json new file mode 100644 index 0000000..309adbb --- /dev/null +++ b/Core.Inventory.BFF.API/appsettings.Local.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "LocalGateways": { + "InventoryService": "https://localhost:7028" + } +} + diff --git a/Core.Inventory.BFF.sln b/Core.Inventory.BFF.sln index 2dfc903..301d0da 100644 --- a/Core.Inventory.BFF.sln +++ b/Core.Inventory.BFF.sln @@ -5,6 +5,10 @@ VisualStudioVersion = 17.14.36212.18 d17.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Inventory.BFF.API", "Core.Inventory.BFF.API\Core.Inventory.BFF.API.csproj", "{A7C30239-BEB4-4DD5-A5C4-AEB4B5154927}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Application", "Application", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Inventory.External", "Core.Inventory.External\Core.Inventory.External.csproj", "{AC69431D-D222-4376-BCE3-31C7201B1F01}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,10 +19,17 @@ Global {A7C30239-BEB4-4DD5-A5C4-AEB4B5154927}.Debug|Any CPU.Build.0 = Debug|Any CPU {A7C30239-BEB4-4DD5-A5C4-AEB4B5154927}.Release|Any CPU.ActiveCfg = Release|Any CPU {A7C30239-BEB4-4DD5-A5C4-AEB4B5154927}.Release|Any CPU.Build.0 = Release|Any CPU + {AC69431D-D222-4376-BCE3-31C7201B1F01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC69431D-D222-4376-BCE3-31C7201B1F01}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC69431D-D222-4376-BCE3-31C7201B1F01}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC69431D-D222-4376-BCE3-31C7201B1F01}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AC69431D-D222-4376-BCE3-31C7201B1F01} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {97B12EA0-3BE9-4A8D-8918-7152ADB153B1} EndGlobalSection diff --git a/Core.Inventory.External/ClientConfiguration/RegisterClientConfiguration.cs b/Core.Inventory.External/ClientConfiguration/RegisterClientConfiguration.cs new file mode 100644 index 0000000..f6a6649 --- /dev/null +++ b/Core.Inventory.External/ClientConfiguration/RegisterClientConfiguration.cs @@ -0,0 +1,65 @@ +using Core.Inventory.External.Clients.Inventory; +using Core.Inventory.External.GatewayConfigurations; +using Lib.Architecture.BuildingBlocks.Helpers.Token; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Refit; + +namespace Core.Inventory.External.ClientConfiguration +{ + public static class RegisterClientConfiguration + { + public static IServiceCollection RegisterExternalLayer(this IServiceCollection services, IConfiguration configuration) + { + var gatewayConfiguration = new GatewayConfiguration(); + var gatewaySettingsConfiguration = new GatewaySettingsConfigurations(configuration); + + // Register GatewayConfiguration as a singleton + services.AddSingleton(gatewayConfiguration); + + // Register IHttpContextAccessor + services.AddSingleton(); + + // Register ITokenProvider + services.AddSingleton(); + + // Register the custom AuthenticatedHttpClientHandler + services.AddTransient(provider => + { + var tokenProvider = provider.GetRequiredService(); + var trackingIdHandler = new TrackingMechanismExtension(provider.GetRequiredService()); + + var handler = new AuthenticatedHttpClientHandler(tokenProvider) + { + InnerHandler = new HttpClientHandler() // Setting the InnerHandler manually + }; + + // Attach the TrackingIdHandler as the outermost handler + trackingIdHandler.InnerHandler = handler; + + return handler; + }); + + var inventoryServiceApiUrl = GatewaySettingsConfigurations.GetInventoryServiceAPIEndpoint().Endpoint.Url; + + // Register IDashBoardServiceClient with the manually created HttpClient + services.AddScoped(provider => + { + var handler = provider.GetRequiredService(); + var handlerTrackingId = new TrackingMechanismExtension(provider.GetRequiredService()); // Using the TrackingIdHandler here + // Chain the handlers + handlerTrackingId.InnerHandler = handler; //chaining + + var httpClient = new HttpClient(handlerTrackingId) + { + BaseAddress = new Uri(inventoryServiceApiUrl), + Timeout = TimeSpan.FromMinutes(1) + }; + return RestService.For(httpClient); + }); + + return services; + } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs b/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs new file mode 100644 index 0000000..c57f073 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/IInventoryServiceClient.cs @@ -0,0 +1,51 @@ +using Core.Adapters.Lib; +using Core.Inventory.External.Clients.Inventory.Requests.Base; +using Core.Inventory.External.Clients.Inventory.Requests.Variant; +using Refit; + +namespace Core.Inventory.External.Clients.Inventory +{ + public interface IInventoryServiceClient + { + #region Furniture Base + + [Get("/api/v1/FurnitureBase/GetAll")] + Task>> GetAllAsync([Header("TrackingId")] string trackingId, CancellationToken cancellationToken = default); + + [Post("/api/v1/FurnitureBase/GetById")] + Task> GetByIdAsync([Header("TrackingId")] string trackingId, [Body] GetFurnitureBaseByIdRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/FurnitureBase/Create")] + Task> CreateAsync([Header("TrackingId")] string trackingId, [Body] CreateFurnitureBaseRequest request, CancellationToken cancellationToken = default); + + [Put("/api/v1/FurnitureBase/Update")] + Task> UpdateFurnitureBaseAsync([Header("TrackingId")] string trackingId, [Body] UpdateFurnitureBaseRequest request, CancellationToken cancellationToken = default); + + [Patch("/api/v1/FurnitureBase/ChangeStatus")] + Task> ChangeFurnitureBaseStatusAsync([Header("TrackingId")] string trackingId, [Body] ChangeFurnitureBaseStatusRequest request, CancellationToken cancellationToken = default); + + #endregion + + #region Furniture Variant + + [Post("/api/v1/FurnitureVariant/GetAllByModelId")] + Task>> GetVariantsByModelIdAsync([Header("TrackingId")] string trackingId, [Body] GetAllFurnitureVariantsByModelIdRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/FurnitureVariant/GetById")] + Task> GetFurnitureVariantByIdAsync([Header("TrackingId")] string trackingId, [Body] GetFurnitureVariantByIdRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/FurnitureVariant/GetByIds")] + Task>> GetFurnitureVariantsByIdsAsync([Header("TrackingId")] string trackingId, [Body] GetFurnitureVariantsByIdsRequest request, CancellationToken cancellationToken = default); + + [Post("/api/v1/FurnitureVariant/Create")] + Task> CreateFurnitureVariantAsync([Header("TrackingId")] string trackingId, [Body] CreateFurnitureVariantRequest request, CancellationToken cancellationToken = default); + + [Put("/api/v1/FurnitureVariant/Update")] + Task> UpdateFurnitureVariantAsync([Header("TrackingId")] string trackingId, string id, [Body] UpdateFurnitureVariantRequest entity, CancellationToken cancellationToken = default); + + [Patch("/api/v1/FurnitureVariant/ChangeStatus")] + Task> ChangeFurnitureVariantStatusAsync([Header("TrackingId")] string trackingId, [Body] ChangeFurnitureVariantStatusRequest request, CancellationToken cancellationToken = default); + + #endregion + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Base/ChangeFurnitureBaseStatusRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Base/ChangeFurnitureBaseStatusRequest.cs new file mode 100644 index 0000000..5df1e28 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Base/ChangeFurnitureBaseStatusRequest.cs @@ -0,0 +1,10 @@ +using Core.Blueprint.Mongo; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Base +{ + public class ChangeFurnitureBaseStatusRequest + { + public string MongoId { get; set; } = default!; + public StatusEnum Status { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Base/CreateFurnitureBaseRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Base/CreateFurnitureBaseRequest.cs new file mode 100644 index 0000000..4913cdb --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Base/CreateFurnitureBaseRequest.cs @@ -0,0 +1,16 @@ +using Core.Inventory.External.Clients.Inventory.Requests.Common; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Base +{ + public class CreateFurnitureBaseRequest + { + public string Name { get; set; } = default!; + public string Material { get; set; } = default!; + public string Condition { get; set; } = default!; + public string? Description { get; set; } + public string? Representation { get; set; } + public string? Notes { get; set; } + public DimensionsRequest Dimensions { get; set; } = new(); + public List? VariantIds { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Base/GetAllFurnitureBaseRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Base/GetAllFurnitureBaseRequest.cs new file mode 100644 index 0000000..6d3fc13 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Base/GetAllFurnitureBaseRequest.cs @@ -0,0 +1,6 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Base +{ + public class GetAllFurnitureBaseRequest + { + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Base/GetFurnitureBaseByIdRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Base/GetFurnitureBaseByIdRequest.cs new file mode 100644 index 0000000..0a3eb93 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Base/GetFurnitureBaseByIdRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Base +{ + public class GetFurnitureBaseByIdRequest + { + public string MongoId { get; set; } = default!; + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Base/UpdateFurnitureBaseRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Base/UpdateFurnitureBaseRequest.cs new file mode 100644 index 0000000..59e178e --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Base/UpdateFurnitureBaseRequest.cs @@ -0,0 +1,19 @@ +using Core.Blueprint.Mongo; +using Core.Inventory.External.Clients.Inventory.Requests.Common; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Base +{ + public class UpdateFurnitureBaseRequest + { + public string Id { get; set; } = default!; + public string Name { get; set; } = default!; + public string Material { get; set; } = default!; + public string Condition { get; set; } = default!; + public string? Description { get; set; } + public string? Representation { get; set; } + public string? Notes { get; set; } + public DimensionsRequest Dimensions { get; set; } = new(); + public List? VariantIds { get; set; } + public StatusEnum Status { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Common/DimensionsRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Common/DimensionsRequest.cs new file mode 100644 index 0000000..9e8979b --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Common/DimensionsRequest.cs @@ -0,0 +1,9 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Common +{ + public class DimensionsRequest + { + public decimal Width { get; set; } + public decimal Height { get; set; } + public decimal Depth { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Variant/ChangeFurnitureVariantStatusRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Variant/ChangeFurnitureVariantStatusRequest.cs new file mode 100644 index 0000000..e655aaa --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Variant/ChangeFurnitureVariantStatusRequest.cs @@ -0,0 +1,10 @@ +using Core.Blueprint.Mongo; + +namespace Core.Inventory.External.Clients.Inventory.Requests.Variant +{ + public class ChangeFurnitureVariantStatusRequest + { + public string MongoId { get; set; } = default!; + public StatusEnum Status { get; set; } + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Variant/CreateFurnitureVariantRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Variant/CreateFurnitureVariantRequest.cs new file mode 100644 index 0000000..0440a8f --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Variant/CreateFurnitureVariantRequest.cs @@ -0,0 +1,19 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Variant +{ + public class CreateFurnitureVariantRequest + { + public string ModelId { get; set; } = default!; + public string Name { get; set; } = default!; + public string Color { get; set; } = default!; + public string? Line { get; set; } + + public decimal Price { get; set; } + public string Currency { get; set; } = "USD"; + public int Stock { get; set; } + + public Guid CategoryId { get; set; } + public Guid ProviderId { get; set; } + + public Dictionary Attributes { get; set; } = []; + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetAllFurnitureVariantsByModelIdRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetAllFurnitureVariantsByModelIdRequest.cs new file mode 100644 index 0000000..e0f703c --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetAllFurnitureVariantsByModelIdRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Variant +{ + public class GetAllFurnitureVariantsByModelIdRequest + { + public string ModelId { get; set; } = default!; + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantByIdRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantByIdRequest.cs new file mode 100644 index 0000000..1c9d603 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantByIdRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Variant +{ + public class GetFurnitureVariantByIdRequest + { + public string MongoId { get; set; } = default!; + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantsByIdsRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantsByIdsRequest.cs new file mode 100644 index 0000000..346a728 --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Variant/GetFurnitureVariantsByIdsRequest.cs @@ -0,0 +1,7 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Variant +{ + public class GetFurnitureVariantsByIdsRequest + { + public List Ids { get; set; } = []; + } +} diff --git a/Core.Inventory.External/Clients/Inventory/Requests/Variant/UpdateFurnitureVariantRequest.cs b/Core.Inventory.External/Clients/Inventory/Requests/Variant/UpdateFurnitureVariantRequest.cs new file mode 100644 index 0000000..c48ac1c --- /dev/null +++ b/Core.Inventory.External/Clients/Inventory/Requests/Variant/UpdateFurnitureVariantRequest.cs @@ -0,0 +1,20 @@ +namespace Core.Inventory.External.Clients.Inventory.Requests.Variant +{ + public class UpdateFurnitureVariantRequest + { + public string Id { get; set; } = default!; + public string ModelId { get; set; } = default!; + public string Name { get; set; } = default!; + public string Color { get; set; } = default!; + public string? Line { get; set; } + + public int Stock { get; set; } + public decimal Price { get; set; } + public string Currency { get; set; } = "USD"; + + public Guid CategoryId { get; set; } + public Guid ProviderId { get; set; } + + public Dictionary Attributes { get; set; } = new(); + } +} diff --git a/Core.Inventory.External/Core.Inventory.External.csproj b/Core.Inventory.External/Core.Inventory.External.csproj new file mode 100644 index 0000000..f8d46c4 --- /dev/null +++ b/Core.Inventory.External/Core.Inventory.External.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + enable + enable + + + + + + + + + diff --git a/Core.Inventory.External/GatewayConfigurations/GatewayConfiguration.cs b/Core.Inventory.External/GatewayConfigurations/GatewayConfiguration.cs new file mode 100644 index 0000000..bdacc78 --- /dev/null +++ b/Core.Inventory.External/GatewayConfigurations/GatewayConfiguration.cs @@ -0,0 +1,19 @@ +using Core.Blueprint.External; + +namespace Core.Inventory.External.GatewayConfigurations +{ + public record GatewayConfiguration + { + public GatewayConfiguration() + { + InventoryService = new InventoryServiceAPI(); + } + + public InventoryServiceAPI InventoryService { get; set; } + } + public record InventoryServiceAPI + { + public string Channel { get; set; } + public BaseEndpoint Endpoint { get; set; } + } +} diff --git a/Core.Inventory.External/GatewayConfigurations/GatewaySettingsConfigurations.cs b/Core.Inventory.External/GatewayConfigurations/GatewaySettingsConfigurations.cs new file mode 100644 index 0000000..8fe9880 --- /dev/null +++ b/Core.Inventory.External/GatewayConfigurations/GatewaySettingsConfigurations.cs @@ -0,0 +1,48 @@ +using Core.Blueprint.External; +using Microsoft.Extensions.Configuration; + +namespace Core.Inventory.External.GatewayConfigurations +{ + public class GatewaySettingsConfigurations + { + private static GatewayConfiguration GatewayConfigurations { get; set; } = new GatewayConfiguration(); + private readonly IConfiguration _configuration; + + public GatewaySettingsConfigurations(IConfiguration configuration) + { + _configuration = configuration; + SetDashboardServiceAPIEndpoint(); + } + public static InventoryServiceAPI GetInventoryServiceAPIEndpoint() + { + return GatewayConfigurations.InventoryService; + } + private GatewayConfiguration SetDashboardServiceAPIEndpoint() + { + IConfigurationSection source; + var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty; + + if (environment == "Local") + source = _configuration.GetSection("LocalGateways"); + else + source = _configuration.GetSection("Gateways"); + + var endpoint = source["InventoryService"] ?? string.Empty; + + if (string.IsNullOrEmpty(endpoint)) throw new Exception("Inventory Service endpoint is empty or null"); + + GatewayConfigurations.InventoryService = new InventoryServiceAPI() + { + Endpoint = new BaseEndpoint() + { + Uri = new Uri(endpoint), + Url = endpoint, + Token = string.Empty, + APIName = "Inventory Service" + } + }; + + return GatewayConfigurations; + } + } +} diff --git a/Core.Inventory.External/TrackingMechanismExtension.cs b/Core.Inventory.External/TrackingMechanismExtension.cs new file mode 100644 index 0000000..9f75171 --- /dev/null +++ b/Core.Inventory.External/TrackingMechanismExtension.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; + +namespace Core.Inventory.External +{ + public sealed class TrackingMechanismExtension(IHttpContextAccessor httpContextAccessor) : DelegatingHandler + { + private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (_httpContextAccessor.HttpContext != null) + { + request.Headers.Add("TrackingId", Guid.NewGuid().ToString()); + } + return await base.SendAsync(request, cancellationToken).ConfigureAwait(false); + } + } +}