Add project files.
This commit is contained in:
parent
9c1958d351
commit
83fc1878c4
55
Core.BluePrint.Packages.sln
Normal file
55
Core.BluePrint.Packages.sln
Normal file
@ -0,0 +1,55 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34728.123
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Blueprint.KeyVault", "Core.Blueprint.KeyVault\Core.Blueprint.KeyVault.csproj", "{0B4D475C-6A41-443C-8FB4-21C759EDCE63}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Mongo", "Core.Blueprint.Mongo\Core.Blueprint.Mongo.csproj", "{27A8E3E1-D613-4D5B-8105-485699409F1E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Redis", "Core.Blueprint.Redis\Core.Blueprint.Redis.csproj", "{11F2AA11-FB98-4A33-AEE4-CD49588D2FE1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Storage", "Core.Blueprint.Storage\Core.Blueprint.Storage.csproj", "{636E4520-79F9-46C8-990D-08F2D24A151C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.SQLServer", "Core.Blueprint.SQLServer\Core.Blueprint.SQLServer.csproj", "{A9CC126A-C187-49EF-9784-0F9F5B8ABDB1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Blueprint.Logging", "Core.Blueprint.Logging\Core.Blueprint.Logging.csproj", "{85B4BC7C-5800-40A9-8310-F4EB2C82AF39}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{0B4D475C-6A41-443C-8FB4-21C759EDCE63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0B4D475C-6A41-443C-8FB4-21C759EDCE63}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0B4D475C-6A41-443C-8FB4-21C759EDCE63}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0B4D475C-6A41-443C-8FB4-21C759EDCE63}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{27A8E3E1-D613-4D5B-8105-485699409F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{27A8E3E1-D613-4D5B-8105-485699409F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{27A8E3E1-D613-4D5B-8105-485699409F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{27A8E3E1-D613-4D5B-8105-485699409F1E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{11F2AA11-FB98-4A33-AEE4-CD49588D2FE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{11F2AA11-FB98-4A33-AEE4-CD49588D2FE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{11F2AA11-FB98-4A33-AEE4-CD49588D2FE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{11F2AA11-FB98-4A33-AEE4-CD49588D2FE1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{636E4520-79F9-46C8-990D-08F2D24A151C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{636E4520-79F9-46C8-990D-08F2D24A151C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{636E4520-79F9-46C8-990D-08F2D24A151C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{636E4520-79F9-46C8-990D-08F2D24A151C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A9CC126A-C187-49EF-9784-0F9F5B8ABDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9CC126A-C187-49EF-9784-0F9F5B8ABDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9CC126A-C187-49EF-9784-0F9F5B8ABDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9CC126A-C187-49EF-9784-0F9F5B8ABDB1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{85B4BC7C-5800-40A9-8310-F4EB2C82AF39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{85B4BC7C-5800-40A9-8310-F4EB2C82AF39}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{85B4BC7C-5800-40A9-8310-F4EB2C82AF39}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{85B4BC7C-5800-40A9-8310-F4EB2C82AF39}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {60FDC812-CC26-4C4A-BCA0-90603A77E99D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
9
Core.Blueprint.KeyVault/Adapters/KeyVaultRequest.cs
Normal file
9
Core.Blueprint.KeyVault/Adapters/KeyVaultRequest.cs
Normal file
@ -0,0 +1,9 @@
|
||||
|
||||
namespace Core.Blueprint.KeyVault
|
||||
{
|
||||
public sealed class KeyVaultRequest
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required string Value { get; set; }
|
||||
}
|
||||
}
|
||||
10
Core.Blueprint.KeyVault/Adapters/KeyVaultResponse.cs
Normal file
10
Core.Blueprint.KeyVault/Adapters/KeyVaultResponse.cs
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
|
||||
namespace Core.Blueprint.KeyVault
|
||||
{
|
||||
public sealed class KeyVaultResponse
|
||||
{
|
||||
public string Name { get; set; } = null!;
|
||||
public string Value { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
35
Core.Blueprint.KeyVault/Configuration/RegisterBlueprint.cs
Normal file
35
Core.Blueprint.KeyVault/Configuration/RegisterBlueprint.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Azure.Identity;
|
||||
using Azure.Security.KeyVault.Secrets;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Core.Blueprint.KeyVault.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the SecretClient for Azure Key Vault as a singleton service.
|
||||
/// </summary>
|
||||
/// <param name="services">The IServiceCollection to add the services to.</param>
|
||||
/// <param name="configuration">The application's configuration.</param>
|
||||
/// <returns>The updated IServiceCollection.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown when the KeyVault URI is missing in the configuration.</exception>
|
||||
public static class RegisterBlueprint
|
||||
{
|
||||
public static IServiceCollection AddKeyVault(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var keyVaultUriString = configuration["ConnectionStrings:KeyVaultDAL"];
|
||||
|
||||
if (string.IsNullOrEmpty(keyVaultUriString))
|
||||
{
|
||||
throw new ArgumentNullException("ConnectionStrings:KeyVault", "KeyVault URI is missing in the configuration.");
|
||||
}
|
||||
|
||||
var keyVaultUri = new Uri(keyVaultUriString);
|
||||
|
||||
// Register SecretClient as a singleton
|
||||
services.AddSingleton(_ => new SecretClient(keyVaultUri, new DefaultAzureCredential()));
|
||||
|
||||
services.AddSingleton<IKeyVaultProvider, KeyVaultProvider>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Core.Blueprint.KeyVault/Contracts/IKeyVaultProvider.cs
Normal file
48
Core.Blueprint.KeyVault/Contracts/IKeyVaultProvider.cs
Normal file
@ -0,0 +1,48 @@
|
||||
|
||||
namespace Core.Blueprint.KeyVault
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for managing secrets in Azure Key Vault.
|
||||
/// </summary>
|
||||
public interface IKeyVaultProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new secret in Azure Key Vault.
|
||||
/// </summary>
|
||||
/// <param name="keyVaultRequest">The request containing the name and value of the secret.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>A <see cref="KeyVaultResponse"/> containing the details of the created secret.</returns>
|
||||
ValueTask<KeyVaultResponse> CreateSecretAsync(KeyVaultRequest keyVaultRequest, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a secret from Azure Key Vault if it exists.
|
||||
/// </summary>
|
||||
/// <param name="secretName">The name of the secret to delete.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Tuple"/> containing a status message and a boolean indicating whether the secret was successfully deleted.
|
||||
/// </returns>
|
||||
ValueTask<Tuple<string, bool>> DeleteSecretAsync(string secretName, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a secret from Azure Key Vault.
|
||||
/// </summary>
|
||||
/// <param name="secretName">The name of the secret to retrieve.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Tuple"/> containing the <see cref="KeyVaultResponse"/> with secret details
|
||||
/// and an optional error message if the secret was not found.
|
||||
/// </returns>
|
||||
ValueTask<Tuple<KeyVaultResponse, string?>> GetSecretAsync(string secretName, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing secret in Azure Key Vault. If the secret does not exist, an error is returned.
|
||||
/// </summary>
|
||||
/// <param name="newSecret">The updated secret information.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Tuple"/> containing the updated <see cref="KeyVaultResponse"/> and an optional error message if the secret was not found.
|
||||
/// </returns>
|
||||
ValueTask<Tuple<KeyVaultResponse, string>> UpdateSecretAsync(KeyVaultRequest newSecret, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
16
Core.Blueprint.KeyVault/Core.Blueprint.KeyVault.csproj
Normal file
16
Core.Blueprint.KeyVault/Core.Blueprint.KeyVault.csproj
Normal file
@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.13.1" />
|
||||
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
93
Core.Blueprint.KeyVault/Provider/KeyVaultProvider.cs
Normal file
93
Core.Blueprint.KeyVault/Provider/KeyVaultProvider.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using Azure;
|
||||
using Azure.Security.KeyVault.Secrets;
|
||||
|
||||
namespace Core.Blueprint.KeyVault
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides operations for managing secrets in Azure Key Vault.
|
||||
/// </summary>
|
||||
public sealed class KeyVaultProvider(SecretClient keyVaultProvider): IKeyVaultProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new secret in Azure Key Vault.
|
||||
/// </summary>
|
||||
/// <param name="keyVaultRequest">The request containing the name and value of the secret.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>A <see cref="KeyVaultResponse"/> containing the details of the created secret.</returns>
|
||||
public async ValueTask<KeyVaultResponse> CreateSecretAsync(KeyVaultRequest keyVaultRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
KeyVaultResponse _response = new();
|
||||
KeyVaultSecret azureResponse = await keyVaultProvider.SetSecretAsync(new KeyVaultSecret(keyVaultRequest.Name, keyVaultRequest.Value), cancellationToken);
|
||||
|
||||
_response.Value = azureResponse.Value;
|
||||
_response.Name = azureResponse.Name;
|
||||
|
||||
return _response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a secret from Azure Key Vault if it exists.
|
||||
/// </summary>
|
||||
/// <param name="secretName">The name of the secret to delete.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Tuple"/> containing a status message and a boolean indicating whether the secret was successfully deleted.
|
||||
/// </returns>
|
||||
public async ValueTask<Tuple<string, bool>> DeleteSecretAsync(string secretName, CancellationToken cancellationToken)
|
||||
{
|
||||
var existingSecret = await this.GetSecretAsync(secretName, cancellationToken);
|
||||
if (existingSecret != null)
|
||||
{
|
||||
await keyVaultProvider.StartDeleteSecretAsync(secretName, cancellationToken);
|
||||
return new("Key Deleted", true);
|
||||
}
|
||||
|
||||
return new("Key Not Found", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a secret from Azure Key Vault.
|
||||
/// </summary>
|
||||
/// <param name="secretName">The name of the secret to retrieve.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Tuple"/> containing the <see cref="KeyVaultResponse"/> with secret details
|
||||
/// and an optional error message if the secret was not found.
|
||||
/// </returns>
|
||||
public async ValueTask<Tuple<KeyVaultResponse, string?>> GetSecretAsync(string secretName, CancellationToken cancellationToken)
|
||||
{
|
||||
KeyVaultSecret azureResponse = await keyVaultProvider.GetSecretAsync(secretName, cancellationToken: cancellationToken);
|
||||
|
||||
if (azureResponse == null)
|
||||
{
|
||||
return new(new KeyVaultResponse(), "Key Not Found");
|
||||
}
|
||||
|
||||
return new(new KeyVaultResponse { Name = secretName, Value = azureResponse.Value }, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing secret in Azure Key Vault. If the secret does not exist, an error is returned.
|
||||
/// </summary>
|
||||
/// <param name="newSecret">The updated secret information.</param>
|
||||
/// <param name="cancellationToken">The cancellation token to cancel the operation.</param>
|
||||
/// <returns>
|
||||
/// A <see cref="Tuple"/> containing the updated <see cref="KeyVaultResponse"/> and an optional error message if the secret was not found.
|
||||
/// </returns>
|
||||
public async ValueTask<Tuple<KeyVaultResponse, string>> UpdateSecretAsync(KeyVaultRequest newSecret, CancellationToken cancellationToken)
|
||||
{
|
||||
KeyVaultResponse _response = new();
|
||||
var existingSecret = await this.GetSecretAsync(newSecret.Name, cancellationToken);
|
||||
if (existingSecret == null)
|
||||
{
|
||||
return new(new KeyVaultResponse(), "Key Not Found");
|
||||
}
|
||||
KeyVaultSecret azureResponse = await keyVaultProvider.SetSecretAsync(new KeyVaultSecret(newSecret.Name, newSecret.Value), cancellationToken);
|
||||
|
||||
_response.Value = azureResponse.Value;
|
||||
_response.Name = azureResponse.Name;
|
||||
|
||||
return new(new KeyVaultResponse { Name = newSecret.Name, Value = azureResponse.Value }, string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Core.Blueprint.Logging/Adapters/ErrorDetails.cs
Normal file
43
Core.Blueprint.Logging/Adapters/ErrorDetails.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="ErrorDetailsDto.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// The service error details transfer object.
|
||||
/// </summary>
|
||||
public class ErrorDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the service error code.
|
||||
/// </summary>
|
||||
/// <example>healthy</example>
|
||||
[DisplayName(DisplayNames.ErrorCode)]
|
||||
[JsonPropertyName(DisplayNames.ErrorCode)]
|
||||
public string? ErrorCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service error message.
|
||||
/// </summary>
|
||||
/// <example>This is an example message.</example>
|
||||
[DisplayName(DisplayNames.Message)]
|
||||
[JsonPropertyName(DisplayNames.Message)]
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service target.
|
||||
/// </summary>
|
||||
/// <example>healthy</example>
|
||||
[DisplayName(DisplayNames.Target)]
|
||||
[JsonPropertyName(DisplayNames.Target)]
|
||||
public string? Target { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
45
Core.Blueprint.Logging/Adapters/HttpError.cs
Normal file
45
Core.Blueprint.Logging/Adapters/HttpError.cs
Normal file
@ -0,0 +1,45 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpErrorDto.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// The service HTTP error data transfer object.
|
||||
/// </summary>
|
||||
public class HttpError
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the error.
|
||||
/// </summary>
|
||||
[DisplayName(DisplayNames.Error)]
|
||||
[JsonPropertyName(DisplayNames.Error)]
|
||||
public ErrorDetails Error { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HttpError{TMessage}"/>
|
||||
/// with custom parameters.
|
||||
/// </summary>
|
||||
/// <param name="message">The HTTP error message.</param>
|
||||
/// <param name="errorCode">The HTTP error code.</param>
|
||||
/// <param name="target">The HTTP error target.</param>
|
||||
public HttpError(
|
||||
string? message,
|
||||
string? errorCode,
|
||||
string? target)
|
||||
{
|
||||
Error = new ErrorDetails
|
||||
{
|
||||
ErrorCode = errorCode,
|
||||
Message = message,
|
||||
Target = target,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
Core.Blueprint.Logging/Adapters/HttpException.cs
Normal file
41
Core.Blueprint.Logging/Adapters/HttpException.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpException.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// The service HTTP exception.
|
||||
/// Extends the <see cref="Exception"/> class.
|
||||
/// </summary>
|
||||
public class HttpException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the exception error code.
|
||||
/// </summary>
|
||||
public string? ErrorCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception status code.
|
||||
/// </summary>
|
||||
public int StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HttpException"/>.
|
||||
/// </summary>
|
||||
/// <param name="statusCode">The exception status code.</param>
|
||||
/// <param name="errorCode">The exception error code.</param>
|
||||
/// <param name="message">The exception message.</param>
|
||||
public HttpException(
|
||||
int statusCode,
|
||||
string errorCode,
|
||||
string message)
|
||||
: base(message)
|
||||
{
|
||||
ErrorCode = errorCode;
|
||||
StatusCode = statusCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
120
Core.Blueprint.Logging/Adapters/LogDetail.cs
Normal file
120
Core.Blueprint.Logging/Adapters/LogDetail.cs
Normal file
@ -0,0 +1,120 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="LogDetail.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// The service logger detail object.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The generic message type.</typeparam>
|
||||
public class LogDetail<TMessage>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the log severity.
|
||||
/// </summary>
|
||||
/// <example>info</example>
|
||||
[DisplayName(DisplayNames.Severity)]
|
||||
[JsonPropertyName(DisplayNames.Severity)]
|
||||
public LogSeverity Severity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp.
|
||||
/// </summary>
|
||||
[DisplayName(DisplayNames.Timestamp)]
|
||||
[JsonPropertyName(DisplayNames.Timestamp)]
|
||||
public DateTime Timestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the environment.
|
||||
/// </summary>
|
||||
/// <example>Development</example>
|
||||
[DisplayName(DisplayNames.Environment)]
|
||||
[JsonPropertyName(DisplayNames.Environment)]
|
||||
public string? Environment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target.
|
||||
/// </summary>
|
||||
[DisplayName(DisplayNames.Target)]
|
||||
[JsonPropertyName(DisplayNames.Target)]
|
||||
public LogTarget? Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the x-forwarded-for header.
|
||||
/// </summary>
|
||||
/// <example>localhost</example>
|
||||
[DisplayName(DisplayNames.XForwardedFor)]
|
||||
[JsonPropertyName(DisplayNames.XForwardedFor)]
|
||||
public string? XForwardedFor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the service identifier.
|
||||
/// </summary>
|
||||
/// <example><see cref="Guid.NewGuid()"/></example>
|
||||
[DisplayName(DisplayNames.ServiceId)]
|
||||
[JsonPropertyName(DisplayNames.ServiceId)]
|
||||
public string? ServiceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the request identifier.
|
||||
/// </summary>
|
||||
/// <example><see cref="Guid.NewGuid()"/></example>
|
||||
[DisplayName(DisplayNames.RequestId)]
|
||||
[JsonPropertyName(DisplayNames.RequestId)]
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keyVaultProvider identifier.
|
||||
/// </summary>
|
||||
/// <example><see cref="Guid.NewGuid()"/></example>
|
||||
[DisplayName(DisplayNames.ClientId)]
|
||||
[JsonPropertyName(DisplayNames.ClientId)]
|
||||
public string? ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keyVaultProvider identifier.
|
||||
/// </summary>
|
||||
/// <example>keyVaultProviderRequest</example>
|
||||
[DisplayName(DisplayNames.Operation)]
|
||||
[JsonPropertyName(DisplayNames.Operation)]
|
||||
public LogOperation Operation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user name.
|
||||
/// </summary>
|
||||
/// <example>keyVaultProviderRequest</example>
|
||||
[DisplayName(DisplayNames.User)]
|
||||
[JsonPropertyName(DisplayNames.User)]
|
||||
public string? User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets user's email.
|
||||
/// </summary>
|
||||
/// <example>keyVaultProviderRequest</example>
|
||||
[DisplayName(DisplayNames.Email)]
|
||||
[JsonPropertyName(DisplayNames.Email)]
|
||||
public string? Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user identifier.
|
||||
/// </summary>
|
||||
/// <example>keyVaultProviderRequest</example>
|
||||
[DisplayName(DisplayNames.UserId)]
|
||||
[JsonPropertyName(DisplayNames.UserId)]
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message.
|
||||
/// </summary>
|
||||
/// <example>A custom log message.</example>
|
||||
[DisplayName(DisplayNames.Message)]
|
||||
[JsonPropertyName(DisplayNames.Message)]
|
||||
public TMessage? Message { get; set; }
|
||||
}
|
||||
}
|
||||
55
Core.Blueprint.Logging/Adapters/LogOperation.cs
Normal file
55
Core.Blueprint.Logging/Adapters/LogOperation.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="LogOperation.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents all possible values for log operation.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public enum LogOperation
|
||||
{
|
||||
/// <summary>
|
||||
/// The keyVaultProvider request log operation type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.ClientRequest)]
|
||||
[JsonPropertyName(DisplayNames.ClientRequest)]
|
||||
ClientRequest = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The keyVaultProvider response log operation type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.ClientResponse)]
|
||||
ClientResponse = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The external request log operation type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.ExternalRequest)]
|
||||
ExternalRequest = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The external response log operation type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.ExternalResponse)]
|
||||
ExternalResponse = 3,
|
||||
|
||||
/// <summary>
|
||||
/// The error log operation type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.Error)]
|
||||
Error = 4,
|
||||
|
||||
/// <summary>
|
||||
/// The info log operation type.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.Info)]
|
||||
Info = 5,
|
||||
}
|
||||
}
|
||||
41
Core.Blueprint.Logging/Adapters/LogSeverity.cs
Normal file
41
Core.Blueprint.Logging/Adapters/LogSeverity.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="LogSeverity.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents all possible values for log severity.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public enum LogSeverity
|
||||
{
|
||||
/// <summary>
|
||||
/// The information severity level.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.Information)]
|
||||
Info = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The warning severity level.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.Warning)]
|
||||
Warn = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The error severity level.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.Error)]
|
||||
Error = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The fatal severity level.
|
||||
/// </summary>
|
||||
[EnumMember(Value = DisplayNames.Fatal)]
|
||||
Fatal = 3,
|
||||
}
|
||||
}
|
||||
41
Core.Blueprint.Logging/Adapters/LogTarget.cs
Normal file
41
Core.Blueprint.Logging/Adapters/LogTarget.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="LogTarget.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// The service logger target object.
|
||||
/// </summary>
|
||||
public class LogTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the log target method.
|
||||
/// </summary>
|
||||
/// <example>GET</example>
|
||||
[DisplayName(DisplayNames.Method)]
|
||||
[JsonPropertyName(DisplayNames.Method)]
|
||||
public string? Method { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log target host.
|
||||
/// </summary>
|
||||
/// <example>GET</example>
|
||||
[DisplayName(DisplayNames.Host)]
|
||||
[JsonPropertyName(DisplayNames.Host)]
|
||||
public string? Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the log target route.
|
||||
/// </summary>
|
||||
/// <example>GET</example>
|
||||
[DisplayName(DisplayNames.Route)]
|
||||
[JsonPropertyName(DisplayNames.Route)]
|
||||
public string? Route { get; set; }
|
||||
}
|
||||
}
|
||||
20
Core.Blueprint.Logging/Adapters/ServiceSettings.cs
Normal file
20
Core.Blueprint.Logging/Adapters/ServiceSettings.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="ServiceSettings.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// The service settings.
|
||||
/// </summary>
|
||||
public class ServiceSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the service identifier.
|
||||
/// </summary>
|
||||
public string? ApplicationName { get; set; }
|
||||
public string? LayerName { get; set; }
|
||||
}
|
||||
}
|
||||
70
Core.Blueprint.Logging/Configuration/Registerblueprint.cs
Normal file
70
Core.Blueprint.Logging/Configuration/Registerblueprint.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace Core.Blueprint.Logging.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for configuring logging in the application.
|
||||
/// </summary>
|
||||
public static class Registerblueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers logging services in the application.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
|
||||
/// <param name="builder">The <see cref="WebApplicationBuilder"/> for accessing configuration and application setup.</param>
|
||||
/// <returns>The updated <see cref="IServiceCollection"/>.</returns>
|
||||
public static IServiceCollection AddLogs(this IServiceCollection services, WebApplicationBuilder builder)
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Information)
|
||||
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
|
||||
.CreateLogger();
|
||||
|
||||
builder.Host.UseSerilog(Log.Logger);
|
||||
|
||||
services.AddScoped<ILoggerProvider, LoggerProvider>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures middleware for logging and error handling in the application.
|
||||
/// </summary>
|
||||
/// <param name="app">The <see cref="IApplicationBuilder"/> used to configure the middleware pipeline.</param>
|
||||
/// <param name="serviceSettings">The service settings required by the middleware.</param>
|
||||
public static void UseLogging(this IApplicationBuilder app, IConfiguration configuration)
|
||||
{
|
||||
var serviceSettings = new ServiceSettings();
|
||||
configuration.GetSection(nameof(ServiceSettings)).Bind(serviceSettings);
|
||||
|
||||
app.UseCustomHttpLogging(serviceSettings);
|
||||
app.UseHttpExceptionHandler(serviceSettings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds middleware to handle HTTP exceptions globally.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
|
||||
/// <param name="settings">The settings used by the exception handler middleware.</param>
|
||||
/// <returns>The updated <see cref="IApplicationBuilder"/>.</returns>
|
||||
public static IApplicationBuilder UseHttpExceptionHandler(this IApplicationBuilder builder, ServiceSettings settings)
|
||||
{
|
||||
return builder.UseMiddleware<HttpErrorMiddleware>(settings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds custom HTTP logging middleware to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param>
|
||||
/// <param name="settings">The settings used by the logging middleware.</param>
|
||||
/// <returns>The updated <see cref="IApplicationBuilder"/>.</returns>
|
||||
public static IApplicationBuilder UseCustomHttpLogging(this IApplicationBuilder builder, ServiceSettings settings)
|
||||
{
|
||||
return builder.UseMiddleware<HttpLoggingMiddleware>(settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Core.Blueprint.Logging/Constants/Claims.cs
Normal file
48
Core.Blueprint.Logging/Constants/Claims.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="Claims.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for claims used in JWT tokens.
|
||||
/// </summary>
|
||||
public class Claims
|
||||
{
|
||||
/// <summary>
|
||||
/// Claim name for user's name.
|
||||
/// </summary>
|
||||
public const string Name = "name";
|
||||
|
||||
/// <summary>
|
||||
/// Claim name for user's name.
|
||||
/// </summary>
|
||||
public const string Email = "email";
|
||||
|
||||
/// <summary>
|
||||
/// Claim name for user's ID.
|
||||
/// </summary>
|
||||
public const string Id = "id";
|
||||
|
||||
/// <summary>
|
||||
/// Claim name for user's role ID.
|
||||
/// </summary>
|
||||
public const string Rol = "rol";
|
||||
|
||||
/// <summary>
|
||||
/// Claim name for user's companies.
|
||||
/// </summary>
|
||||
public const string Companies = "companies";
|
||||
|
||||
/// <summary>
|
||||
/// Claim name for user's projects.
|
||||
/// </summary>
|
||||
public const string Projects = "projects";
|
||||
|
||||
/// <summary>
|
||||
/// Claim name for user's surveys.
|
||||
/// </summary>
|
||||
public const string Surveys = "surveys";
|
||||
}
|
||||
}
|
||||
397
Core.Blueprint.Logging/Constants/DisplayNames.cs
Normal file
397
Core.Blueprint.Logging/Constants/DisplayNames.cs
Normal file
@ -0,0 +1,397 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="DisplayNames.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants of the display names for this service.
|
||||
/// </summary>
|
||||
public static class DisplayNames
|
||||
{
|
||||
/// <summary>
|
||||
/// The active patameter.
|
||||
/// </summary>
|
||||
public const string Active = "active";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The keyVaultProvider identifier parameter.
|
||||
/// </summary>
|
||||
public const string ClientId = "keyVaultProviderId";
|
||||
|
||||
/// <summary>
|
||||
/// The keyVaultProvider request parameter.
|
||||
/// </summary>
|
||||
public const string ClientRequest = "keyVaultProviderRequest";
|
||||
|
||||
/// <summary>
|
||||
/// The keyVaultProvider response parameter.
|
||||
/// </summary>
|
||||
public const string ClientResponse = "keyVaultProviderResponse";
|
||||
|
||||
/// <summary>
|
||||
/// The creation date.
|
||||
/// </summary>
|
||||
public const string CreationDate = "creationDate";
|
||||
|
||||
/// <summary>
|
||||
/// The content parameter.
|
||||
/// </summary>
|
||||
public const string Content = "content";
|
||||
|
||||
/// <summary>
|
||||
/// The delete parameter.
|
||||
/// </summary>
|
||||
public const string Delete = "delete";
|
||||
|
||||
/// <summary>
|
||||
/// The description parameter.
|
||||
/// </summary>
|
||||
public const string Description = "description";
|
||||
|
||||
/// <summary>
|
||||
/// The detail parameter.
|
||||
/// </summary>
|
||||
public const string Detail = "detail";
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The environment parameter.
|
||||
/// </summary>
|
||||
public const string Environment = "environment";
|
||||
|
||||
/// <summary>
|
||||
/// The error log severity level parameter.
|
||||
/// </summary>
|
||||
public const string Error = "error";
|
||||
|
||||
/// <summary>
|
||||
/// The error code parameter.
|
||||
/// </summary>
|
||||
public const string ErrorCode = "errorCode";
|
||||
|
||||
/// <summary>
|
||||
/// The external request parameter.
|
||||
/// </summary>
|
||||
public const string ExternalRequest = "externalRequest";
|
||||
|
||||
/// <summary>
|
||||
/// The external response parameter.
|
||||
/// </summary>
|
||||
public const string ExternalResponse = "externalResponse";
|
||||
|
||||
/// <summary>
|
||||
/// The fatal log severity level parameter.
|
||||
/// </summary>
|
||||
public const string Fatal = "fatal";
|
||||
|
||||
/// <summary>
|
||||
/// The host parameter.
|
||||
/// </summary>
|
||||
public const string Host = "host";
|
||||
|
||||
/// <summary>
|
||||
/// The identifier parameter.
|
||||
/// </summary>
|
||||
public const string Id = "id";
|
||||
|
||||
/// <summary>
|
||||
/// The inactive parameter.
|
||||
/// </summary>
|
||||
public const string Inactive = "inactive";
|
||||
|
||||
/// <summary>
|
||||
/// The info log severity level parameter.
|
||||
/// </summary>
|
||||
public const string Info = "info";
|
||||
|
||||
/// <summary>
|
||||
/// The information log severity level parameter.
|
||||
/// </summary>
|
||||
public const string Information = "information";
|
||||
|
||||
/// <summary>
|
||||
/// The media parameter.
|
||||
/// </summary>
|
||||
public const string Media = "media";
|
||||
|
||||
/// <summary>
|
||||
/// The media type parameter.
|
||||
/// </summary>
|
||||
public const string MediaType = "mediaType";
|
||||
|
||||
/// <summary>
|
||||
/// The media use type parameter.
|
||||
/// </summary>
|
||||
public const string MediaUseType = "mediaUseType";
|
||||
|
||||
/// <summary>
|
||||
/// Th message parameter.
|
||||
/// </summary>
|
||||
public const string Message = "message";
|
||||
|
||||
/// <summary>
|
||||
/// The method parameter.
|
||||
/// </summary>
|
||||
public const string Method = "method";
|
||||
|
||||
/// <summary>
|
||||
/// The monday parameter.
|
||||
/// </summary>
|
||||
public const string Monday = "monday";
|
||||
|
||||
/// <summary>
|
||||
/// The MXN parameter.
|
||||
/// </summary>
|
||||
public const string MXN = "MXN";
|
||||
|
||||
/// <summary>
|
||||
/// The name parameter.
|
||||
/// </summary>
|
||||
public const string Name = "name";
|
||||
|
||||
/// <summary>
|
||||
/// The next page parameter.
|
||||
/// </summary>
|
||||
public const string NextPage = "nextPage";
|
||||
|
||||
/// <summary>
|
||||
/// The nick name parameter.
|
||||
/// </summary>
|
||||
public const string NickName = "nickName";
|
||||
|
||||
/// <summary>
|
||||
/// The note parameter.
|
||||
/// </summary>
|
||||
public const string Note = "note";
|
||||
|
||||
/// <summary>
|
||||
/// The not so affordable parameter.
|
||||
/// </summary>
|
||||
public const string NotSoAffordable = "notSoAffordable";
|
||||
|
||||
/// <summary>
|
||||
/// The object status parameter.
|
||||
/// </summary>
|
||||
public const string ObjectStatus = "objectStatus";
|
||||
|
||||
/// <summary>
|
||||
/// The opening time parameter.
|
||||
/// </summary>
|
||||
public const string OpeningTime = "openingTime";
|
||||
|
||||
/// <summary>
|
||||
/// The operation days parameter.
|
||||
/// </summary>
|
||||
public const string OperationDays = "operationDays";
|
||||
|
||||
/// <summary>
|
||||
/// The page parameter.
|
||||
/// </summary>
|
||||
public const string Page = "page";
|
||||
|
||||
/// <summary>
|
||||
/// The page count parameter.
|
||||
/// </summary>
|
||||
public const string PageCount = "pageCount";
|
||||
|
||||
/// <summary>
|
||||
/// The page metadata parameter.
|
||||
/// </summary>
|
||||
public const string PageMetadata = "pageMetadata";
|
||||
|
||||
/// <summary>
|
||||
/// The page size parameter.
|
||||
/// </summary>
|
||||
public const string PageSize = "pageSize";
|
||||
|
||||
/// <summary>
|
||||
/// The parent identifier parameter.
|
||||
/// </summary>
|
||||
public const string ParentId = "ParentId";
|
||||
|
||||
/// <summary>
|
||||
/// The pet ticket price parameter.
|
||||
/// </summary>
|
||||
public const string PetTicketPrice = "petTicketPrice";
|
||||
|
||||
/// <summary>
|
||||
/// The place parameter.
|
||||
/// </summary>
|
||||
public const string Place = "place";
|
||||
|
||||
/// <summary>
|
||||
/// The place type parameter.
|
||||
/// </summary>
|
||||
public const string PlaceType = "placeType";
|
||||
|
||||
/// <summary>
|
||||
/// The previous page parameter.
|
||||
/// </summary>
|
||||
public const string PreviousPage = "previousPage";
|
||||
|
||||
/// <summary>
|
||||
/// The provider identifier parameter.
|
||||
/// </summary>
|
||||
public const string ProviderId = "providerId";
|
||||
|
||||
/// <summary>
|
||||
/// The provider type identifier parameter.
|
||||
/// </summary>
|
||||
public const string ProviderTypeId = "providerTypeId";
|
||||
|
||||
/// <summary>
|
||||
/// The request identifier parameter.
|
||||
/// </summary>
|
||||
public const string RequestId = "requestId";
|
||||
|
||||
/// <summary>
|
||||
/// The RNT identifier parameter.
|
||||
/// </summary>
|
||||
public const string RntId = "rntId";
|
||||
|
||||
/// <summary>
|
||||
/// The route parameter.
|
||||
/// </summary>
|
||||
public const string Route = "route";
|
||||
|
||||
/// <summary>
|
||||
/// The operation parameter.
|
||||
/// </summary>
|
||||
public const string Operation = "operation";
|
||||
|
||||
/// <summary>
|
||||
/// The other ticket price parameter.
|
||||
/// </summary>
|
||||
public const string OtherTicketPrice = "otherTicketPrice";
|
||||
|
||||
/// <summary>
|
||||
/// The path parameter.
|
||||
/// </summary>
|
||||
public const string Path = "path";
|
||||
|
||||
/// <summary>
|
||||
/// The saturday parameter.
|
||||
/// </summary>
|
||||
public const string Saturday = "saturday";
|
||||
|
||||
/// <summary>
|
||||
/// The secondary parameter.
|
||||
/// </summary>
|
||||
public const string Secondary = "secondary";
|
||||
|
||||
/// <summary>
|
||||
/// The service health parameter.
|
||||
/// </summary>
|
||||
public const string ServiceHealth = "serviceHealth";
|
||||
|
||||
/// <summary>
|
||||
/// The service identifier parameter.
|
||||
/// </summary>
|
||||
public const string ServiceId = "serviceId";
|
||||
|
||||
/// <summary>
|
||||
/// The severity parameter.
|
||||
/// </summary>
|
||||
public const string Severity = "severity";
|
||||
|
||||
/// <summary>
|
||||
/// The state identifier parameter.
|
||||
/// </summary>
|
||||
public const string StateId = "stateId";
|
||||
|
||||
/// <summary>
|
||||
/// The region identifier parameter.
|
||||
/// </summary>
|
||||
public const string StateProvinceRegionId = "regionId";
|
||||
|
||||
/// <summary>
|
||||
/// The sunday parameter.
|
||||
/// </summary>
|
||||
public const string Sunday = "sunday";
|
||||
|
||||
/// <summary>
|
||||
/// The tax identifier parameter.
|
||||
/// </summary>
|
||||
public const string TaxId = "taxId";
|
||||
|
||||
/// <summary>
|
||||
/// The target parameter.
|
||||
/// </summary>
|
||||
public const string Target = "target";
|
||||
|
||||
/// <summary>
|
||||
/// The thursday parameter.
|
||||
/// </summary>
|
||||
public const string Thursday = "thursday";
|
||||
|
||||
/// <summary>
|
||||
/// The timestamp parameter.
|
||||
/// </summary>
|
||||
public const string Timestamp = "timestamp";
|
||||
|
||||
/// <summary>
|
||||
/// The total items parameter.
|
||||
/// </summary>
|
||||
public const string TotalItems = "totalItems";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the transaction identifier parameter.
|
||||
/// </summary>
|
||||
public const string TransactionId = "transactionId";
|
||||
|
||||
/// <summary>
|
||||
/// The tuesday parameter.
|
||||
/// </summary>
|
||||
public const string Tuesday = "tuesday";
|
||||
|
||||
/// <summary>
|
||||
/// The URI parameter.
|
||||
/// </summary>
|
||||
public const string Uri = "uri";
|
||||
|
||||
/// <summary>
|
||||
/// The update parameter.
|
||||
/// </summary>
|
||||
public const string Update = "update";
|
||||
|
||||
/// <summary>
|
||||
/// The x-forwarded-for header parameter.
|
||||
/// </summary>
|
||||
public const string XForwardedFor = "xForwardedFor";
|
||||
|
||||
/// <summary>
|
||||
/// The x-forwarded-for header parameter.
|
||||
/// </summary>
|
||||
public const string XForwardedForHeader = "X-Forwarded-For";
|
||||
|
||||
/// <summary>
|
||||
/// The final currency identifier parameter.
|
||||
/// </summary>
|
||||
public const string CurrencyId = "currencyId";
|
||||
|
||||
/// <summary>
|
||||
/// The user identifier parameter.
|
||||
/// </summary>
|
||||
public const string UserId = "userId";
|
||||
|
||||
/// <summary>
|
||||
/// The user parameter.
|
||||
/// </summary>
|
||||
public const string User = "user";
|
||||
|
||||
/// <summary>
|
||||
/// The warning log severity level.
|
||||
/// </summary>
|
||||
public const string Warning = "warning";
|
||||
|
||||
/// <summary>
|
||||
/// The email parameter.
|
||||
/// </summary>
|
||||
public const string Email = "email";
|
||||
|
||||
}
|
||||
}
|
||||
21
Core.Blueprint.Logging/Constants/EnvironmentVariables.cs
Normal file
21
Core.Blueprint.Logging/Constants/EnvironmentVariables.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="EnvironmentVariables.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants of the environment variables for this service.
|
||||
/// </summary>
|
||||
public static class EnvironmentVariables
|
||||
{
|
||||
/// <summary>
|
||||
/// The stage environment vriable.
|
||||
/// </summary>
|
||||
public const string Stage = "ASPNETCORE_ENVIRONMENT";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
79
Core.Blueprint.Logging/Constants/ErrorCodes.cs
Normal file
79
Core.Blueprint.Logging/Constants/ErrorCodes.cs
Normal file
@ -0,0 +1,79 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="ErrorCodes.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for the error codes.
|
||||
/// </summary>
|
||||
public static class ErrorCodes
|
||||
{
|
||||
/// <summary>
|
||||
/// The generic entities not found error code.
|
||||
/// </summary>
|
||||
public const string EntitiesNotFound = "{0}EntitiesNotFound";
|
||||
|
||||
/// <summary>
|
||||
/// The entity already exsits error message.
|
||||
/// </summary>
|
||||
public const string EntityAlreadyExists = "{0}EntityAlreadyExists";
|
||||
|
||||
/// <summary>
|
||||
/// The generic entity not found error code.
|
||||
/// </summary>
|
||||
public const string EntityNotFound = "{0}EntityNotFound";
|
||||
|
||||
/// <summary>
|
||||
/// The generic not supported error code.
|
||||
/// </summary>
|
||||
public const string EntityNotSupported = "{0}NotSupported";
|
||||
|
||||
/// <summary>
|
||||
/// The internal server error code.
|
||||
/// </summary>
|
||||
public const string InternalServerError = "InternalServerError";
|
||||
|
||||
/// <summary>
|
||||
/// The invalid parameters in mapper error code.
|
||||
/// </summary>
|
||||
public const string InvalidParametersMapper = "InvalidParametersMapper";
|
||||
|
||||
/// <summary>
|
||||
/// The page size invalid value error code.
|
||||
/// </summary>
|
||||
public const string PageSizeInvalidValue = "PageSizeInvalidValue";
|
||||
|
||||
/// <summary>
|
||||
/// The page ot of range error code.
|
||||
/// </summary>
|
||||
public const string PageOutOfRange = "PageOutOfRange";
|
||||
|
||||
/// <summary>
|
||||
/// The property does not match the regular expresion error code.
|
||||
/// </summary>
|
||||
public const string PropertyDoesNotMatchRegex = "{0}PropertyDoesNotMatchRegex";
|
||||
|
||||
/// <summary>
|
||||
/// The property is required error code.
|
||||
/// </summary>
|
||||
public const string PropertyIsRequired = "{0}PropertyIsRequired";
|
||||
|
||||
/// <summary>
|
||||
/// The property length invalid error code.
|
||||
/// </summary>
|
||||
public const string PropertyLengthInvalid = "{0}PropertyLengthInvalid";
|
||||
|
||||
/// <summary>
|
||||
/// The property must be in range error code.
|
||||
/// </summary>
|
||||
public const string PropertyMustBeInRange = "{0}PropertyMustBeInRange";
|
||||
|
||||
/// <summary>
|
||||
/// The route not found error code.
|
||||
/// </summary>
|
||||
public const string RouteNotFound = "RouteNotFound";
|
||||
}
|
||||
}
|
||||
10
Core.Blueprint.Logging/Constants/Headers.cs
Normal file
10
Core.Blueprint.Logging/Constants/Headers.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
public static class Headers
|
||||
{
|
||||
/// <summary>
|
||||
/// The authorization header.
|
||||
/// </summary>
|
||||
public const string Authorization = "Authorization";
|
||||
}
|
||||
}
|
||||
148
Core.Blueprint.Logging/Constants/MimeTypes.cs
Normal file
148
Core.Blueprint.Logging/Constants/MimeTypes.cs
Normal file
@ -0,0 +1,148 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="MimeTypes.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants for the mime types.
|
||||
/// </summary>
|
||||
public static class MimeTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// The service application/json mime type.
|
||||
/// </summary>
|
||||
public const string ApplicationJson = "application/json";
|
||||
|
||||
/// <summary>
|
||||
/// The application/pdf mime type.
|
||||
/// </summary>
|
||||
public const string ApplicationPdf = "application/pdf";
|
||||
|
||||
/// <summary>
|
||||
/// The end index.
|
||||
/// </summary>
|
||||
public const int EndIndex = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The JPEG extension.
|
||||
/// </summary>
|
||||
public const string ExtensionGif = "gif";
|
||||
|
||||
/// <summary>
|
||||
/// The JPEG extension.
|
||||
/// </summary>
|
||||
public const string ExtensionJpeg = "jpeg";
|
||||
|
||||
/// <summary>
|
||||
/// The PNG extension.
|
||||
/// </summary>
|
||||
public const string ExtensionPng = "png";
|
||||
|
||||
/// <summary>
|
||||
/// The SVG extension.
|
||||
/// </summary>
|
||||
public const string ExtensionSvg = "svg";
|
||||
|
||||
/// <summary>
|
||||
/// The image/gif mime type.
|
||||
/// </summary>
|
||||
public const string ImageGif = "image/gif";
|
||||
|
||||
/// <summary>
|
||||
/// The image/jpeg mime type.
|
||||
/// </summary>
|
||||
public const string ImageJpeg = "image/jpeg";
|
||||
|
||||
/// <summary>
|
||||
/// The image/png mime type.
|
||||
/// </summary>
|
||||
public const string ImagePng = "image/png";
|
||||
|
||||
/// <summary>
|
||||
/// The image/svg+xml mime type.
|
||||
/// </summary>
|
||||
public const string ImageSvg = "image/svg+xml";
|
||||
|
||||
/// <summary>
|
||||
/// The identifier GIF.
|
||||
/// </summary>
|
||||
public const string IdentifierGif = "R0LGO";
|
||||
|
||||
/// <summary>
|
||||
/// The identifier PNG.
|
||||
/// </summary>
|
||||
public const string IdentifierJpeg = "/9J/4";
|
||||
|
||||
/// <summary>
|
||||
/// The identifier PDF.
|
||||
/// </summary>
|
||||
public const string IdentifierPdf = "JVBER";
|
||||
|
||||
/// <summary>
|
||||
/// The identifier PNG.
|
||||
/// </summary>
|
||||
public const string IdentifierPng = "IVBOR";
|
||||
|
||||
/// <summary>
|
||||
/// The identifier SVG.
|
||||
/// </summary>
|
||||
public const string IdentifierSvg = "PHN2Z";
|
||||
|
||||
/// <summary>
|
||||
/// The parameter name.
|
||||
/// </summary>
|
||||
public const string ParameterName = "MimeType";
|
||||
|
||||
/// <summary>
|
||||
/// The start index.
|
||||
/// </summary>
|
||||
public const int StartIndex = 0;
|
||||
|
||||
/// <summary>
|
||||
/// The mime type dictionary.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string, string> Dictionary = new Dictionary<string, string>
|
||||
{
|
||||
{ IdentifierJpeg, ImageJpeg },
|
||||
{ IdentifierPng, ImagePng },
|
||||
{ IdentifierGif, ImageGif },
|
||||
{ IdentifierSvg, ImageSvg },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The mime type dictionary.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<string, string> DictionaryExtension = new Dictionary<string, string>
|
||||
{
|
||||
{ IdentifierJpeg, ExtensionJpeg },
|
||||
{ IdentifierPng, ExtensionPng },
|
||||
{ IdentifierGif, ExtensionGif },
|
||||
{ IdentifierSvg, ExtensionSvg },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mime type.
|
||||
/// </summary>
|
||||
/// <param name="content">The cpntent with mime type identifier, substring 0, 5 from content.</param>
|
||||
/// <returns>A <see cref="string"/> representing the value.</returns>
|
||||
public static string GetMimeType(this string content)
|
||||
{
|
||||
return Dictionary.FirstOrDefault(_ => _.Key == content[..EndIndex].ToUpper(CultureInfo.InvariantCulture)).Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the extension.
|
||||
/// </summary>
|
||||
/// <param name="content">The mime type identifier, substring 0, 5 from content.</param>
|
||||
/// <returns>A <see cref="string"/> representing the value.</returns>
|
||||
public static string GetExtension(this string content)
|
||||
{
|
||||
return DictionaryExtension.FirstOrDefault(_ => _.Key == content[..EndIndex].ToUpper(CultureInfo.InvariantCulture)).Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Core.Blueprint.Logging/Constants/Responses.cs
Normal file
29
Core.Blueprint.Logging/Constants/Responses.cs
Normal file
@ -0,0 +1,29 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="Responses.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Constants of the responses for this service.
|
||||
/// </summary>
|
||||
public static class Responses
|
||||
{
|
||||
/// <summary>
|
||||
/// The health response.
|
||||
/// </summary>
|
||||
public const string HealthyService = "healthy";
|
||||
|
||||
/// <summary>
|
||||
/// The route does not exist response.
|
||||
/// </summary>
|
||||
public const string RouteDoesNotExist = "The specified route '{0}' does not exist for method '{1}' in this service.";
|
||||
|
||||
/// <summary>
|
||||
/// The target response.
|
||||
/// </summary>
|
||||
public const string Target = "{0}|{1}://{2}{3}";
|
||||
}
|
||||
}
|
||||
12
Core.Blueprint.Logging/Contracts/ILoggerProvider.cs
Normal file
12
Core.Blueprint.Logging/Contracts/ILoggerProvider.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
public interface ILoggerProvider
|
||||
{
|
||||
public void LogInformation(string service, params object[] args);
|
||||
public void LogOperationStarted(string service, params object[] args);
|
||||
public void LogOperationFinished(string service, params object[] args);
|
||||
public void LogWarning(string message, params object[] args);
|
||||
public void LogError(string servicee, params object[] args);
|
||||
public void LogCritical(Exception exception, string message, params object[] args);
|
||||
}
|
||||
}
|
||||
21
Core.Blueprint.Logging/Core.Blueprint.Logging.csproj
Normal file
21
Core.Blueprint.Logging/Core.Blueprint.Logging.csproj
Normal file
@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="9.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
92
Core.Blueprint.Logging/Middleware/HttpErrorMiddleware.cs
Normal file
92
Core.Blueprint.Logging/Middleware/HttpErrorMiddleware.cs
Normal file
@ -0,0 +1,92 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpErrorMiddleware.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Serilog;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles HTTP logging.
|
||||
/// </summary>
|
||||
public class HttpErrorMiddleware
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly RequestDelegate requestProcess;
|
||||
public readonly ServiceSettings settings;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instrance of <see cref="HttpErrorMiddleware"/>.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger representig an instance of <see cref="ILogger"/>.</param>
|
||||
/// <param name="requestProcess">The request delegate process.</param>
|
||||
public HttpErrorMiddleware(ILogger logger, RequestDelegate requestProcess, ServiceSettings settings)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.requestProcess = requestProcess;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke method.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
await requestProcess(context).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException exception)
|
||||
{
|
||||
await HandleErrorResponse(
|
||||
context,
|
||||
exception.Message,
|
||||
exception.ErrorCode,
|
||||
exception.StatusCode).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception defaultException)
|
||||
{
|
||||
await HandleErrorResponse(
|
||||
context,
|
||||
defaultException.Message,
|
||||
ErrorCodes.InternalServerError,
|
||||
StatusCodes.Status500InternalServerError).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles error responses.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="errorCode">The error code.</param>
|
||||
/// <param name="statusCode">The HTTP status code.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
private async Task HandleErrorResponse(HttpContext context, string? message, string? errorCode, int statusCode)
|
||||
{
|
||||
var errorMessage = new HttpError(
|
||||
message,
|
||||
errorCode,
|
||||
string.Format(
|
||||
Responses.Target,
|
||||
context.Request.Method,
|
||||
context.Request.Scheme,
|
||||
context.Request.Host.Host,
|
||||
context.Request.Path));
|
||||
|
||||
logger.LogError<HttpError>(context, errorMessage, $"{settings.ApplicationName}-{settings.LayerName}");
|
||||
|
||||
context.Response.ContentType = MimeTypes.ApplicationJson;
|
||||
context.Response.StatusCode = statusCode;
|
||||
|
||||
await context.Response.WriteAsync(JsonSerializer.Serialize(errorMessage)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
237
Core.Blueprint.Logging/Middleware/HttpLogger.cs
Normal file
237
Core.Blueprint.Logging/Middleware/HttpLogger.cs
Normal file
@ -0,0 +1,237 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpLogger.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IO;
|
||||
using Serilog;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles all logging scenarios.
|
||||
/// </summary>
|
||||
public static class HttpLogger
|
||||
{
|
||||
/// <summary>
|
||||
/// The JSON serializer options for logging methods.
|
||||
/// </summary>
|
||||
public static JsonSerializerOptions serializerOptions = new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Converters = {
|
||||
new JsonStringEnumConverter( JsonNamingPolicy.CamelCase),
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The generic message parameter.</typeparam>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
public static void LogError<TMessage>(this ILogger logger, HttpContext context, TMessage message, string? serviceId)
|
||||
{
|
||||
var logMessage = CreateErrorLog(context, message, serviceId);
|
||||
logger.Error(logMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an information message.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The generic message parameter.</typeparam>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="message">The message.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
public static void LogInfo<TMessage>(this ILogger logger, HttpContext context, TMessage message, string? serviceId)
|
||||
{
|
||||
var logMessage = CreateInfoLog(context, message, serviceId);
|
||||
logger.Information(logMessage);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an incoming HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="recyclableMemoryStreamManager">The recyclable mmory stream manager.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
public static async Task LogRequest(
|
||||
this ILogger logger,
|
||||
HttpContext context,
|
||||
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
|
||||
string? serviceId)
|
||||
{
|
||||
context.Request.EnableBuffering();
|
||||
await using var requestStream = recyclableMemoryStreamManager.GetStream();
|
||||
await context.Request.Body.CopyToAsync(requestStream);
|
||||
|
||||
var logMessage = CreateRequestLog(
|
||||
context,
|
||||
ReadStream(requestStream),
|
||||
serviceId);
|
||||
logger.Information(logMessage);
|
||||
|
||||
context.Request.Body.Position = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an outcome HTTP response.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="recyclableMemoryStreamManager">The recyclable mmory stream manager.</param>
|
||||
/// <param name="requestProcess">The request delegate process.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
///
|
||||
public static async Task LogResponse(
|
||||
this ILogger logger,
|
||||
HttpContext context,
|
||||
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
|
||||
RequestDelegate requestProcess,
|
||||
string? serviceId)
|
||||
{
|
||||
var originalBodyStream = context.Response.Body;
|
||||
await using var responseBody = recyclableMemoryStreamManager.GetStream();
|
||||
context.Response.Body = responseBody;
|
||||
|
||||
await requestProcess(context);
|
||||
|
||||
context.Response.Body.Seek(0, SeekOrigin.Begin);
|
||||
var text = await new StreamReader(context.Response.Body).ReadToEndAsync();
|
||||
context.Response.Body.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var logMessage = CreateResponseLog(context, text, serviceId);
|
||||
logger.Information(logMessage);
|
||||
|
||||
await responseBody.CopyToAsync(originalBodyStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an error log.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The generic message.</typeparam>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="message">The error message.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
/// <returns>A <see cref="string"/> representig the error log.</returns>
|
||||
private static string CreateErrorLog<TMessage>(HttpContext context, TMessage message, string? serviceId)
|
||||
=> CreateLog(context, LogSeverity.Error, LogOperation.Error, message, serviceId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an info log.
|
||||
/// </summary>
|
||||
/// <typeparam name="TMessage">The generic message.</typeparam>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="message">The info message.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
/// <returns>A <see cref="string"/> representig the info log.</returns>
|
||||
private static string CreateInfoLog<TMessage>(HttpContext context, TMessage message, string? serviceId)
|
||||
=> CreateLog(context, LogSeverity.Info, LogOperation.Info, message, serviceId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a request log.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="requestBody">The request body.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
/// <returns>A <see cref="string"/> representig the request log.</returns>
|
||||
private static string CreateRequestLog(HttpContext context, string? requestBody, string? serviceId)
|
||||
=> CreateLog(context, LogSeverity.Info, LogOperation.ClientRequest, requestBody, serviceId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a response log.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="responseBody">The response body.</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
/// <returns>A <see cref="string"/> representig the response log.</returns>
|
||||
private static string CreateResponseLog(HttpContext context, string? responseBody, string? serviceId)
|
||||
=> CreateLog(context, LogSeverity.Info, LogOperation.ClientResponse, responseBody, serviceId);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a generic log.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <param name="severity">The log severity.</param>
|
||||
/// <param name="operation">The log operation.</param>
|
||||
/// <param name="message">The log message</param>
|
||||
/// <param name="serviceId">The service identifier.</param>
|
||||
/// <returns>A <see cref="string"/> representing a generic log.</returns>
|
||||
private static string CreateLog<TMessage>(
|
||||
HttpContext context,
|
||||
LogSeverity severity,
|
||||
LogOperation operation,
|
||||
TMessage message,
|
||||
string? serviceId)
|
||||
{
|
||||
var tokenHeader = context.Request.Headers[Headers.Authorization].FirstOrDefault()?.Split(" ").Last();
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var token = tokenHeader is not null ? tokenHandler.ReadJwtToken(tokenHeader) : null;
|
||||
|
||||
var log = new LogDetail<TMessage>
|
||||
{
|
||||
Severity = severity,
|
||||
Target = new LogTarget
|
||||
{
|
||||
Method = context.Request.Method,
|
||||
Host = context.Request.Host.Host,
|
||||
Route = context.Request.Path,
|
||||
},
|
||||
Email = token?.Claims.FirstOrDefault(c => c.Type == Claims.Email)?.Value,
|
||||
User = token?.Claims.FirstOrDefault(c => c.Type == Claims.Name)?.Value,
|
||||
UserId = token?.Claims.FirstOrDefault(c => c.Type == Claims.Id)?.Value,
|
||||
Environment = Environment.GetEnvironmentVariable(EnvironmentVariables.Stage),
|
||||
Operation = operation,
|
||||
RequestId = context.Request.Headers[DisplayNames.RequestId],
|
||||
ServiceId = serviceId,
|
||||
XForwardedFor = context.Request.Headers[DisplayNames.XForwardedForHeader],
|
||||
Timestamp = DateTime.Now,
|
||||
Message = message,
|
||||
};
|
||||
|
||||
var serializedLog = JsonSerializer.Serialize(log, serializerOptions);
|
||||
|
||||
return serializedLog
|
||||
.Replace("\\u0022", "\"")
|
||||
.Replace("\"{", "{")
|
||||
.Replace("}\"", "}")
|
||||
.Replace("\\u0027", "'")
|
||||
.Replace("\\\u0027", "'")
|
||||
.Replace("\n", "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be read.</param>
|
||||
/// <returns>A <see cref="string?"/> representig the request body.</returns>
|
||||
private static string? ReadStream(Stream stream)
|
||||
{
|
||||
const int readChunkBufferLength = 4096;
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
using var textWriter = new StringWriter();
|
||||
using var reader = new StreamReader(stream);
|
||||
var readChunk = new char[readChunkBufferLength];
|
||||
int readChunkLength;
|
||||
do
|
||||
{
|
||||
readChunkLength = reader.ReadBlock(readChunk, 0, readChunkBufferLength);
|
||||
textWriter.Write(readChunk, 0, readChunkLength);
|
||||
} while (readChunkLength > 0);
|
||||
|
||||
var stringItem = textWriter.ToString();
|
||||
|
||||
return stringItem != string.Empty ? stringItem : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
Core.Blueprint.Logging/Middleware/HttpLoggingMiddleware.cs
Normal file
69
Core.Blueprint.Logging/Middleware/HttpLoggingMiddleware.cs
Normal file
@ -0,0 +1,69 @@
|
||||
// ***********************************************************************
|
||||
// <copyright file="HttpLoggingMiddleware.cs">
|
||||
// Heath
|
||||
// </copyright>
|
||||
// ***********************************************************************
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.IO;
|
||||
using Serilog;
|
||||
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles HTTP logging.
|
||||
/// </summary>
|
||||
public class HttpLoggingMiddleware
|
||||
{
|
||||
private readonly ILogger logger;
|
||||
private readonly RequestDelegate requestProcess;
|
||||
private readonly ServiceSettings settings;
|
||||
private readonly RecyclableMemoryStreamManager recyclableMemoryStreamManager;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instrance of <see cref="HttpLoggingMiddleware"/>.
|
||||
/// </summary>
|
||||
/// <param name="requestProcess">The request delegate process.</param>
|
||||
/// <param name="logger">The logger representig an instance of <see cref="ILogger"/>.</param>
|
||||
/// <param name="settings">The service settings.</param>
|
||||
public HttpLoggingMiddleware(RequestDelegate requestProcess, ILogger logger, ServiceSettings settings)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.requestProcess = requestProcess;
|
||||
this.settings = settings;
|
||||
recyclableMemoryStreamManager = new RecyclableMemoryStreamManager();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke method.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <returns></returns>
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
await LogRequest(context);
|
||||
await LogResponse(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an incoming HTTP request.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
private async Task LogRequest(HttpContext context)
|
||||
{
|
||||
await logger.LogRequest(context, recyclableMemoryStreamManager, $"{settings.ApplicationName}-{settings.LayerName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an outcome HTTP response.
|
||||
/// </summary>
|
||||
/// <param name="context">The HTTP context.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
private async Task LogResponse(HttpContext context)
|
||||
{
|
||||
await logger.LogResponse(context, recyclableMemoryStreamManager, requestProcess, $"{settings.ApplicationName}-{settings.LayerName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
89
Core.Blueprint.Logging/Provider/LoggerProvider.cs
Normal file
89
Core.Blueprint.Logging/Provider/LoggerProvider.cs
Normal file
@ -0,0 +1,89 @@
|
||||
namespace Core.Blueprint.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides logging functionalities using Serilog.
|
||||
/// </summary>
|
||||
public class LoggerProvider : ILoggerProvider
|
||||
{
|
||||
private readonly Serilog.ILogger logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoggerProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The Serilog logger instance.</param>
|
||||
public LoggerProvider(Serilog.ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an informational message for a specific service.
|
||||
/// </summary>
|
||||
/// <param name="service">The name of the service.</param>
|
||||
/// <param name="args">Additional arguments to include in the log.</param>
|
||||
public void LogInformation(string service, params object[] args)
|
||||
{
|
||||
logger.Information("Starting operation in {service} service", service, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message indicating the start of an operation in a specific service.
|
||||
/// </summary>
|
||||
/// <param name="service">The name of the service.</param>
|
||||
/// <param name="args">Additional parameters associated with the operation.</param>
|
||||
public void LogOperationStarted(string service, params object[] args)
|
||||
{
|
||||
logger.Information("Starting operation in {Service} service with parameters: {@Args}", service, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a message indicating the completion of an operation in a specific service.
|
||||
/// </summary>
|
||||
/// <param name="service">The name of the service.</param>
|
||||
/// <param name="args">Additional parameters associated with the operation.</param>
|
||||
public void LogOperationFinished(string service, params object[] args)
|
||||
{
|
||||
logger.Information("Finishing operation in {Service} service with parameters: {@Args}", service, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a general informational message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log.</param>
|
||||
public void LogInformation(string message)
|
||||
{
|
||||
logger.Information(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a warning message with additional context.
|
||||
/// </summary>
|
||||
/// <param name="message">The warning message to log.</param>
|
||||
/// <param name="args">Additional arguments to include in the log.</param>
|
||||
public void LogWarning(string message, params object[] args)
|
||||
{
|
||||
logger.Warning(message, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs an error that occurred in a specific service.
|
||||
/// </summary>
|
||||
/// <param name="service">The name of the service.</param>
|
||||
/// <param name="args">Additional details about the error.</param>
|
||||
public void LogError(string service, params object[] args)
|
||||
{
|
||||
logger.Error("An error occurred in `{service}` Exception: {@Args}", service, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs a critical error with an exception, message, and additional context.
|
||||
/// </summary>
|
||||
/// <param name="exception">The exception associated with the critical error.</param>
|
||||
/// <param name="message">The critical error message.</param>
|
||||
/// <param name="args">Additional arguments to include in the log.</param>
|
||||
public void LogCritical(Exception exception, string message, params object[] args)
|
||||
{
|
||||
logger.Fatal(exception, message, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Core.Blueprint.Mongo/Attributes/CollectionAttributeName.cs
Normal file
24
Core.Blueprint.Mongo/Attributes/CollectionAttributeName.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="CollectionAttributeName"/> attribute is used to specify the name of a MongoDB collection
|
||||
/// that a class should be mapped to. This attribute can be applied to classes that represent MongoDB entities.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)] // This attribute can only be applied to classes.
|
||||
public class CollectionAttributeName : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the MongoDB collection that the class is mapped to.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionAttributeName"/> class with the specified collection name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the MongoDB collection that the class should be mapped to.</param>
|
||||
public CollectionAttributeName(string name)
|
||||
{
|
||||
Name = name ?? throw new ArgumentNullException(nameof(name), "Collection name cannot be null.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
using Azure.Core;
|
||||
using Azure.Identity;
|
||||
using MongoDB.Driver.Authentication.Oidc;
|
||||
|
||||
namespace Core.Blueprint.Mongo.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="HeathIdentityProvider"/> class is responsible for acquiring an OpenID Connect (OIDC)
|
||||
/// access token for MongoDB authentication using Azure Identity and Managed Identity credentials.
|
||||
/// </summary>
|
||||
public class HeathIdentityProvider : IOidcCallback
|
||||
{
|
||||
/// <summary>
|
||||
/// The audience (resource identifier) for which the OIDC token is being requested.
|
||||
/// </summary>
|
||||
private readonly string _audience;
|
||||
|
||||
/// <summary>
|
||||
/// The environment in which the application is running (e.g., Development, Production).
|
||||
/// </summary>
|
||||
private readonly string _environment;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HeathIdentityProvider"/> class with the specified audience.
|
||||
/// </summary>
|
||||
/// <param name="audience">The audience (resource identifier) for which the OIDC token is being requested.</param>
|
||||
public HeathIdentityProvider(string audience)
|
||||
{
|
||||
_audience = audience;
|
||||
_environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronously retrieves the OIDC access token to authenticate to MongoDB.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The callback parameters provided for the OIDC request.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>An OIDC access token to authenticate to MongoDB.</returns>
|
||||
/// <exception cref="Exception">Thrown if an error occurs during the token acquisition process.</exception>
|
||||
public OidcAccessToken GetOidcAccessToken(OidcCallbackParameters parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
AccessToken token;
|
||||
|
||||
TokenRequestContext tokenRequestContext =
|
||||
new TokenRequestContext(
|
||||
new[] { _audience }
|
||||
);
|
||||
|
||||
if (_environment == "Local")
|
||||
{
|
||||
token =
|
||||
new ChainedTokenCredential(
|
||||
new ManagedIdentityCredential(),
|
||||
new VisualStudioCredential(),
|
||||
new VisualStudioCodeCredential(),
|
||||
new SharedTokenCacheCredential()
|
||||
)
|
||||
.GetToken(tokenRequestContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
token =
|
||||
new ManagedIdentityCredential()
|
||||
.GetToken(tokenRequestContext);
|
||||
}
|
||||
|
||||
return new OidcAccessToken(token.Token, expiresIn: null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"An error occurred while trying to get the OIDC token to connect to the database, ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously retrieves the OIDC access token to authenticate to MongoDB.
|
||||
/// </summary>
|
||||
/// <param name="parameters">The callback parameters provided for the OIDC request.</param>
|
||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||
/// <returns>A task that represents the asynchronous operation, with an OIDC access token as the result.</returns>
|
||||
/// <exception cref="Exception">Thrown if an error occurs during the token acquisition process.</exception>
|
||||
public async Task<OidcAccessToken> GetOidcAccessTokenAsync(OidcCallbackParameters parameters, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
TokenRequestContext tokenRequestContext =
|
||||
new TokenRequestContext(
|
||||
new[] { _audience }
|
||||
);
|
||||
|
||||
AccessToken token;
|
||||
|
||||
if (_environment == "Local")
|
||||
{
|
||||
token = await new ChainedTokenCredential(
|
||||
new ManagedIdentityCredential(),
|
||||
new VisualStudioCredential(),
|
||||
new VisualStudioCodeCredential(),
|
||||
new SharedTokenCacheCredential()
|
||||
)
|
||||
.GetTokenAsync(tokenRequestContext, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
token = await new ManagedIdentityCredential()
|
||||
.GetTokenAsync(tokenRequestContext, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return new OidcAccessToken(token.Token, expiresIn: null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception($"An error occurred while trying to get the OIDC token to connect to the database, ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Core.Blueprint.Mongo/Configuration/RegisterBlueprint.cs
Normal file
65
Core.Blueprint.Mongo/Configuration/RegisterBlueprint.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using Core.Blueprint.Mongo;
|
||||
using Core.Blueprint.Mongo.Configuration;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Core.Blueprint.DAL.Mongo.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="RegisterBlueprint"/> class contains extension methods for registering the MongoDB context and configuration settings
|
||||
/// to the <see cref="IServiceCollection"/> in the dependency injection container.
|
||||
/// </summary>
|
||||
public static class RegisterBlueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the MongoDB layer services to the <see cref="IServiceCollection"/>.
|
||||
/// Registers the MongoDB context and configuration settings for MongoDB connection, database name, and audience.
|
||||
/// </summary>
|
||||
/// <param name="services">The <see cref="IServiceCollection"/> to which the services will be added.</param>
|
||||
/// <param name="configuration">The <see cref="IConfiguration"/> used to load MongoDB settings.</param>
|
||||
/// <returns>The updated <see cref="IServiceCollection"/> with MongoDB services registered.</returns>
|
||||
public static IServiceCollection AddMongoLayer(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? string.Empty;
|
||||
|
||||
services.AddSingleton<IMongoContext, MongoContext>();
|
||||
|
||||
var ConnectionString = configuration.GetSection("ConnectionStrings:MongoDB").Value ?? string.Empty;
|
||||
var Databasename = configuration.GetSection("MongoDb:DatabaseName").Value ?? string.Empty;
|
||||
var Audience = (environment == "Local")
|
||||
? configuration.GetSection("MongoDb:LocalAudience").Value
|
||||
: configuration.GetSection("MongoDb:Audience").Value;
|
||||
|
||||
if (string.IsNullOrEmpty(ConnectionString) || string.IsNullOrEmpty(Databasename) || string.IsNullOrEmpty(Audience))
|
||||
throw new InvalidOperationException("Mongo connection is not configured correctly.");
|
||||
|
||||
services.Configure<MongoDbSettings>(options =>
|
||||
{
|
||||
options.ConnectionString = ConnectionString;
|
||||
options.Databasename = Databasename;
|
||||
options.Audience = Audience;
|
||||
});
|
||||
|
||||
services.AddSingleton<IMongoClient>(serviceProvider =>
|
||||
{
|
||||
var settings = serviceProvider.GetRequiredService<IOptions<MongoDbSettings>>().Value;
|
||||
var mongoClientSettings = MongoClientSettings.FromConnectionString(settings.ConnectionString);
|
||||
mongoClientSettings.Credential = MongoCredential.CreateOidcCredential(new HeathIdentityProvider(settings.Audience));
|
||||
return new MongoClient(mongoClientSettings);
|
||||
});
|
||||
|
||||
services.AddSingleton<IMongoDatabase>(serviceProvider =>
|
||||
{
|
||||
var settings = serviceProvider.GetRequiredService<IOptions<MongoDbSettings>>().Value;
|
||||
var client = serviceProvider.GetRequiredService<IMongoClient>();
|
||||
return client.GetDatabase(settings.Databasename);
|
||||
});
|
||||
|
||||
services.AddSingleton<IMongoDbSettings>(serviceProvider => serviceProvider.GetRequiredService<IOptions<MongoDbSettings>>().Value);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Core.Blueprint.Mongo/Context/MongoContext.cs
Normal file
66
Core.Blueprint.Mongo/Context/MongoContext.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="MongoContext"/> class represents the MongoDB context that contains the connection information,
|
||||
/// including the connection string, database name, and audience.
|
||||
/// It implements the <see cref="IMongoContext"/> interface to provide methods for accessing these values.
|
||||
/// </summary>
|
||||
public sealed class MongoContext : IMongoContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the connection string used to connect to the MongoDB instance.
|
||||
/// </summary>
|
||||
public string ConnectionString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the MongoDB database.
|
||||
/// </summary>
|
||||
public string Databasename { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audience (resource identifier) used for MongoDB authentication.
|
||||
/// </summary>
|
||||
public string Audience { get; set; } = string.Empty;
|
||||
|
||||
private readonly IConfiguration configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MongoContext"/> class using the provided <see cref="IConfiguration"/>.
|
||||
/// The configuration is used to retrieve MongoDB connection settings.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration used to retrieve the MongoDB connection settings.</param>
|
||||
public MongoContext(IConfiguration configuration)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the MongoDB connection string from the configuration.
|
||||
/// </summary>
|
||||
/// <returns>The MongoDB connection string, or an empty string if not found.</returns>
|
||||
public string GetConnectionString()
|
||||
{
|
||||
return configuration.GetConnectionString("MongoDb:ConnectionString")?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the MongoDB database name from the configuration.
|
||||
/// </summary>
|
||||
/// <returns>The MongoDB database name, or an empty string if not found.</returns>
|
||||
public string GetDatabasename()
|
||||
{
|
||||
return configuration.GetSection("MongoDb:DatabaseName").Value ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the MongoDB audience (resource identifier) from the configuration.
|
||||
/// </summary>
|
||||
/// <returns>The MongoDB audience, or an empty string if not found.</returns>
|
||||
public string GetAudience()
|
||||
{
|
||||
return configuration.GetSection("MongoDb:Audience").Value ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Core.Blueprint.Mongo/Context/MongoDbSettings.cs
Normal file
25
Core.Blueprint.Mongo/Context/MongoDbSettings.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the MongoDB configuration settings, including the connection string,
|
||||
/// database name, and audience, used for connecting and authenticating to a MongoDB instance.
|
||||
/// Implements the <see cref="IMongoDbSettings"/> interface to provide a strongly typed configuration.
|
||||
/// </summary>
|
||||
public class MongoDbSettings : IMongoDbSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the connection string used to connect to the MongoDB instance.
|
||||
/// </summary>
|
||||
public string ConnectionString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the MongoDB database to connect to.
|
||||
/// </summary>
|
||||
public string Databasename { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audience (resource identifier) used for MongoDB authentication.
|
||||
/// </summary>
|
||||
public string Audience { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
152
Core.Blueprint.Mongo/Contracts/ICollectionRepository.cs
Normal file
152
Core.Blueprint.Mongo/Contracts/ICollectionRepository.cs
Normal file
@ -0,0 +1,152 @@
|
||||
using MongoDB.Driver;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for performing CRUD operations and queries on MongoDB collections.
|
||||
/// The <typeparamref name="TDocument"/> represents the type of documents in the collection,
|
||||
/// which must implement the <see cref="IDocument"/> interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDocument">The type of document in the MongoDB collection, must implement <see cref="IDocument"/>.</typeparam>
|
||||
public interface ICollectionsRepository<TDocument> where TDocument : IDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves all documents from the collection as an enumerable queryable result.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="ValueTask"/> containing an <see cref="IEnumerable{TDocument}"/> representing the collection's documents.</returns>
|
||||
ValueTask<IEnumerable<TDocument>> AsQueryable();
|
||||
|
||||
/// <summary>
|
||||
/// Filters the documents in the collection by the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">An expression used to filter the documents based on the provided condition.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation, with a result of an <see cref="IEnumerable{TDocument}"/> of filtered documents.</returns>
|
||||
Task<IEnumerable<TDocument>> FilterBy(
|
||||
Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
/// <summary>
|
||||
/// Filters the documents in the collection by the provided filter expression and projects them to a different type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProjected">The type to project the documents into.</typeparam>
|
||||
/// <param name="filterExpression">An expression used to filter the documents.</param>
|
||||
/// <param name="projectionExpression">An expression used to project the filtered documents into the <typeparamref name="TProjected"/> type.</param>
|
||||
/// <returns>An <see cref="IEnumerable{TProjected}"/> representing the projected documents.</returns>
|
||||
IEnumerable<TProjected> FilterBy<TProjected>(
|
||||
Expression<Func<TDocument, bool>> filterExpression,
|
||||
Expression<Func<TDocument, TProjected>> projectionExpression);
|
||||
|
||||
/// <summary>
|
||||
/// Filters documents in the collection based on the provided MongoDB filter definition.
|
||||
/// </summary>
|
||||
/// <param name="filterDefinition">A filter definition for MongoDB query.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a list of documents that match the filter.</returns>
|
||||
Task<IEnumerable<TDocument>> FilterByMongoFilterAsync(FilterDefinition<TDocument> filterDefinition);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a single document by the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">An expression used to filter the documents.</param>
|
||||
/// <returns>The first matching <see cref="TDocument"/> or null if no match is found.</returns>
|
||||
TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds a single document by the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">An expression used to filter the documents.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation, with the matching <see cref="TDocument"/> or null.</returns>
|
||||
Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
/// <summary>
|
||||
/// Finds a document by its identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the document.</param>
|
||||
/// <returns>The document with the provided identifier or null if not found.</returns>
|
||||
TDocument FindById(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds a document by its identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the document.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation, with the matching <see cref="TDocument"/> or null.</returns>
|
||||
Task<TDocument> FindByIdAsync(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a single document into the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to insert.</param>
|
||||
void InsertOne(TDocument document);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously inserts a single document into the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to insert.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task InsertOneAsync(TDocument document);
|
||||
|
||||
/// <summary>
|
||||
/// Inserts multiple documents into the collection.
|
||||
/// </summary>
|
||||
/// <param name="documents">The collection of documents to insert.</param>
|
||||
void InsertMany(ICollection<TDocument> documents);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously inserts multiple documents into the collection.
|
||||
/// </summary>
|
||||
/// <param name="documents">The collection of documents to insert.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task InsertManyAsync(ICollection<TDocument> documents);
|
||||
|
||||
/// <summary>
|
||||
/// Replaces an existing document with a new one.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to replace the existing one.</param>
|
||||
void ReplaceOne(TDocument document);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously replaces an existing document with a new one.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to replace the existing one.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task ReplaceOneAsync(TDocument document);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a single document by the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">An expression used to filter the documents to delete.</param>
|
||||
void DeleteOne(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes a single document by the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">An expression used to filter the documents to delete.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task<TDocument> DeleteOneAsync(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a single document by its identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the document to delete.</param>
|
||||
void DeleteById(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes a single document by its identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the document to delete.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task DeleteByIdAsync(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes multiple documents that match the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">An expression used to filter the documents to delete.</param>
|
||||
void DeleteMany(Expression<Func<TDocument, bool>> filterExpression);
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes multiple documents that match the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">An expression used to filter the documents to delete.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task DeleteManyAsync(Expression<Func<TDocument, bool>> filterExpression);
|
||||
}
|
||||
}
|
||||
39
Core.Blueprint.Mongo/Contracts/IDocument.cs
Normal file
39
Core.Blueprint.Mongo/Contracts/IDocument.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Core.Blueprint.Mongo;
|
||||
|
||||
public interface IDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the MongoDB ObjectId for the document.
|
||||
/// </summary>
|
||||
string _Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a unique identifier for the document, represented as a string (GUID).
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp of when the document was created.
|
||||
/// </summary>
|
||||
DateTime CreatedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user or system who created the document.
|
||||
/// </summary>
|
||||
string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp of when the document was last updated.
|
||||
/// </summary>
|
||||
DateTime? UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user or system who last updated the document.
|
||||
/// </summary>
|
||||
string? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the document.
|
||||
/// </summary>
|
||||
StatusEnum? Status { get; set; }
|
||||
}
|
||||
45
Core.Blueprint.Mongo/Contracts/IMongoContext.cs
Normal file
45
Core.Blueprint.Mongo/Contracts/IMongoContext.cs
Normal file
@ -0,0 +1,45 @@
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the context for interacting with MongoDB, providing access to connection-related information,
|
||||
/// such as the connection string, database name, and audience for authentication.
|
||||
/// </summary>
|
||||
public interface IMongoContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the connection string used to connect to the MongoDB instance.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the MongoDB connection string.</returns>
|
||||
string GetConnectionString();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the MongoDB database.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the MongoDB database name.</returns>
|
||||
string GetDatabasename();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the audience (resource identifier) used for MongoDB authentication.
|
||||
/// </summary>
|
||||
/// <returns>A string representing the MongoDB audience (typically the resource identifier for authentication).</returns>
|
||||
string GetAudience();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MongoDB connection string used to connect to the database.
|
||||
/// </summary>
|
||||
/// <value>A string representing the MongoDB connection string.</value>
|
||||
string ConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the MongoDB database.
|
||||
/// </summary>
|
||||
/// <value>A string representing the MongoDB database name.</value>
|
||||
string Databasename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audience (resource identifier) for MongoDB authentication.
|
||||
/// </summary>
|
||||
/// <value>A string representing the MongoDB audience (resource identifier for authentication).</value>
|
||||
string Audience { get; set; }
|
||||
}
|
||||
}
|
||||
32
Core.Blueprint.Mongo/Contracts/IMongoDbSettings.cs
Normal file
32
Core.Blueprint.Mongo/Contracts/IMongoDbSettings.cs
Normal file
@ -0,0 +1,32 @@
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the settings required to connect to a MongoDB instance, including the connection string,
|
||||
/// database name, and audience used for authentication.
|
||||
/// </summary>
|
||||
public interface IMongoDbSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the connection string used to connect to the MongoDB instance.
|
||||
/// The connection string includes details such as server address, port,
|
||||
/// authentication credentials, and any additional options needed for connection.
|
||||
/// </summary>
|
||||
/// <value>A string representing the MongoDB connection string.</value>
|
||||
string ConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the MongoDB database to connect to.
|
||||
/// This value specifies which database to use within the MongoDB instance.
|
||||
/// </summary>
|
||||
/// <value>A string representing the name of the MongoDB database.</value>
|
||||
string Databasename { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audience (resource identifier) for MongoDB authentication.
|
||||
/// This is typically used in token-based authentication schemes (e.g., OAuth or OpenID Connect),
|
||||
/// to specify the intended recipient of an access token.
|
||||
/// </summary>
|
||||
/// <value>A string representing the MongoDB audience (resource identifier for authentication).</value>
|
||||
string Audience { get; set; }
|
||||
}
|
||||
}
|
||||
19
Core.Blueprint.Mongo/Core.Blueprint.Mongo.csproj
Normal file
19
Core.Blueprint.Mongo/Core.Blueprint.Mongo.csproj
Normal file
@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Core" Version="1.44.1" />
|
||||
<PackageReference Include="Azure.Identity" Version="1.13.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="2.1.0" />
|
||||
<PackageReference Include="MongoDB.Bson" Version="3.0.0" />
|
||||
<PackageReference Include="MongoDB.Driver" Version="3.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
81
Core.Blueprint.Mongo/Entities/Document.cs
Normal file
81
Core.Blueprint.Mongo/Entities/Document.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using Core.Blueprint.Mongo;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
public class Document : IDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the MongoDB ObjectId for the document.
|
||||
/// This property is automatically generated if not provided.
|
||||
/// It is used as the primary key for the document in MongoDB.
|
||||
/// </summary>
|
||||
[BsonId]
|
||||
[BsonElement("_id")]
|
||||
[BsonRepresentation(BsonType.ObjectId)]
|
||||
[JsonPropertyName("_id")]
|
||||
public string _Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a unique identifier for the document, represented as a string (GUID).
|
||||
/// This value is automatically generated if not provided and can be used as a secondary key.
|
||||
/// </summary>
|
||||
[BsonElement("id")]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp of when the document was created.
|
||||
/// This value is automatically set to the current UTC time when the document is created.
|
||||
/// </summary>
|
||||
[BsonElement("createdAt")]
|
||||
[BsonRepresentation(BsonType.DateTime)]
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTime CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user or system who created the document.
|
||||
/// This field can be used for audit purposes.
|
||||
/// </summary>
|
||||
[BsonElement("createdBy")]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
[JsonPropertyName("createdBy")]
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp of when the document was last updated.
|
||||
/// This value is nullable and will be set to null if the document has never been updated.
|
||||
/// </summary>
|
||||
[BsonElement("updatedAt")]
|
||||
[BsonRepresentation(BsonType.DateTime)]
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTime? UpdatedAt { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user or system who last updated the document.
|
||||
/// This field can be used for audit purposes.
|
||||
/// </summary>
|
||||
[BsonElement("updatedBy")]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
[JsonPropertyName("updatedBy")]
|
||||
public string? UpdatedBy { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the document.
|
||||
/// The status is represented by an enum and defaults to <see cref="StatusEnum.Active"/>.
|
||||
/// </summary>
|
||||
[BsonElement("status")]
|
||||
[BsonRepresentation(BsonType.String)]
|
||||
[JsonPropertyName("status")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public StatusEnum? Status { get; set; }
|
||||
|
||||
public Document()
|
||||
{
|
||||
_Id = ObjectId.GenerateNewId().ToString();
|
||||
Id = Guid.NewGuid().ToString();
|
||||
CreatedAt = DateTime.UtcNow;
|
||||
Status = StatusEnum.Active;
|
||||
}
|
||||
}
|
||||
28
Core.Blueprint.Mongo/Entities/StatusEnum.cs
Normal file
28
Core.Blueprint.Mongo/Entities/StatusEnum.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the status of a document or entity. This enum is used to track the state of a document
|
||||
/// within the system. The `JsonStringEnumConverter` ensures that the enum values are serialized as strings
|
||||
/// in JSON format, rather than as numeric values.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum StatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an active document or entity.
|
||||
/// </summary>
|
||||
Active = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Represents an inactive document or entity.
|
||||
/// </summary>
|
||||
Inactive = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Represents a deleted document or entity.
|
||||
/// </summary>
|
||||
Deleted = 2
|
||||
}
|
||||
}
|
||||
40
Core.Blueprint.Mongo/Provider/MongoProvider.cs
Normal file
40
Core.Blueprint.Mongo/Provider/MongoProvider.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using MongoDB.Driver;
|
||||
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the MongoDB provider and database connection using the specified configuration settings.
|
||||
/// This class manages the connection to MongoDB and ensures that the correct credentials are used for authentication.
|
||||
/// </summary>
|
||||
public class MongoProvider
|
||||
{
|
||||
private readonly IMongoDatabase _database;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MongoProvider"/> class.
|
||||
/// This constructor sets up the MongoDB provider using the connection string, audience, and other settings provided.
|
||||
/// It also configures authentication using OpenID Connect (OIDC) credentials.
|
||||
/// </summary>
|
||||
/// <param name="mongoDbSettings">The MongoDB settings required for connecting to the database.</param>
|
||||
public MongoProvider(IMongoDatabase database)
|
||||
{
|
||||
_database = database ?? throw new ArgumentNullException(nameof(database));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initialized MongoDB database. If the database is not initialized, an exception is thrown.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when the database connection is not initialized.</exception>
|
||||
protected IMongoDatabase Database
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_database == null)
|
||||
{
|
||||
throw new InvalidOperationException("MongoDB connection is not initialized.");
|
||||
}
|
||||
return _database;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
252
Core.Blueprint.Mongo/Repositories/CollectionRepository.cs
Normal file
252
Core.Blueprint.Mongo/Repositories/CollectionRepository.cs
Normal file
@ -0,0 +1,252 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Driver.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Core.Blueprint.Mongo
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides methods for interacting with a MongoDB collection for a specific document type.
|
||||
/// Inherits from <see cref="MongoProvider"/> and implements <see cref="ICollectionsRepository{TDocument}"/>.
|
||||
/// This class encapsulates common database operations such as querying, inserting, updating, and deleting documents.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDocument">The type of document stored in the collection, which must implement <see cref="IDocument"/>.</typeparam>
|
||||
public class CollectionRepository<TDocument>(IMongoDatabase database) : ICollectionsRepository<TDocument> where TDocument : IDocument
|
||||
{
|
||||
private IMongoCollection<TDocument> _collection;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the MongoDB collection based on the <see cref="CollectionAttributeName"/> attribute
|
||||
/// applied to the <typeparamref name="TDocument"/> type. Throws an exception if the attribute is not present.
|
||||
/// </summary>
|
||||
public void CollectionInitialization()
|
||||
{
|
||||
var collectionAttribute = typeof(TDocument).GetCustomAttribute<CollectionAttributeName>();
|
||||
if (collectionAttribute == null)
|
||||
{
|
||||
throw new InvalidOperationException($"The class {typeof(TDocument).Name} is missing the CollectionAttributeName attribute.");
|
||||
}
|
||||
|
||||
string collectionName = collectionAttribute.Name;
|
||||
|
||||
_collection = database.GetCollection<TDocument>(collectionName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all documents from the collection as an enumerable list.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains an enumerable list of documents.</returns>
|
||||
public virtual async ValueTask<IEnumerable<TDocument>> AsQueryable()
|
||||
{
|
||||
return await _collection.AsQueryable().ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters documents in the collection based on the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the documents.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a list of documents that match the filter.</returns>
|
||||
public virtual async Task<IEnumerable<TDocument>> FilterBy(
|
||||
Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
var objectResult = await _collection.FindAsync(filterExpression).ConfigureAwait(false);
|
||||
return objectResult.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters documents in the collection based on the provided filter and projection expressions.
|
||||
/// Projects the filtered documents into a new type.
|
||||
/// </summary>
|
||||
/// <typeparam name="TProjected">The type to project the documents into.</typeparam>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the documents.</param>
|
||||
/// <param name="projectionExpression">A lambda expression that defines how the documents should be projected.</param>
|
||||
/// <returns>An enumerable collection of projected documents.</returns>
|
||||
public virtual IEnumerable<TProjected> FilterBy<TProjected>(
|
||||
Expression<Func<TDocument, bool>> filterExpression,
|
||||
Expression<Func<TDocument, TProjected>> projectionExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).Project(projectionExpression).ToEnumerable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a single document that matches the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the document.</param>
|
||||
/// <returns>The document that matches the filter, or <c>null</c> if no document is found.</returns>
|
||||
public virtual TDocument FindOne(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return _collection.Find(filterExpression).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Filters documents in the collection based on the provided MongoDB filter definition.
|
||||
/// </summary>
|
||||
/// <param name="filterDefinition">A filter definition for MongoDB query.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a list of documents that match the filter.</returns>
|
||||
public virtual async Task<IEnumerable<TDocument>> FilterByMongoFilterAsync(FilterDefinition<TDocument> filterDefinition)
|
||||
{
|
||||
var objectResult = await _collection.Find(filterDefinition).ToListAsync().ConfigureAwait(false);
|
||||
return objectResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds a single document that matches the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the document.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the document that matches the filter, or <c>null</c> if no document is found.</returns>
|
||||
public virtual Task<TDocument> FindOneAsync(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return Task.Run(() => _collection.Find(filterExpression).FirstOrDefaultAsync());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds a document by its unique identifier (ID).
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the document.</param>
|
||||
/// <returns>The document that matches the specified ID, or <c>null</c> if no document is found.</returns>
|
||||
public virtual TDocument FindById(string id)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc._Id, id);
|
||||
return _collection.Find(filter).SingleOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously finds a document by its unique identifier (ID).
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the document.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the document that matches the specified ID, or <c>null</c> if no document is found.</returns>
|
||||
public virtual Task<TDocument> FindByIdAsync(string id)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var objectId = new ObjectId(id);
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc._Id, id);
|
||||
return _collection.Find(filter).SingleOrDefaultAsync();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a single document into the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to insert.</param>
|
||||
public virtual void InsertOne(TDocument document)
|
||||
{
|
||||
_collection.InsertOne(document);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously inserts a single document into the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document to insert.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public virtual Task InsertOneAsync(TDocument document)
|
||||
{
|
||||
return Task.Run(() => _collection.InsertOneAsync(document));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts multiple documents into the collection.
|
||||
/// </summary>
|
||||
/// <param name="documents">The collection of documents to insert.</param>
|
||||
public void InsertMany(ICollection<TDocument> documents)
|
||||
{
|
||||
_collection.InsertMany(documents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously inserts multiple documents into the collection.
|
||||
/// </summary>
|
||||
/// <param name="documents">The collection of documents to insert.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public virtual async Task InsertManyAsync(ICollection<TDocument> documents)
|
||||
{
|
||||
await _collection.InsertManyAsync(documents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces an existing document in the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document with the updated data.</param>
|
||||
public void ReplaceOne(TDocument document)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc._Id, document._Id);
|
||||
_collection.FindOneAndReplace(filter, document);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously replaces an existing document in the collection.
|
||||
/// </summary>
|
||||
/// <param name="document">The document with the updated data.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public virtual async Task ReplaceOneAsync(TDocument document)
|
||||
{
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc._Id, document._Id);
|
||||
await _collection.FindOneAndReplaceAsync(filter, document);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a single document from the collection based on the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the document to delete.</param>
|
||||
public void DeleteOne(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
_collection.FindOneAndDelete(filterExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes a single document from the collection based on the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the document to delete.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async Task<TDocument> DeleteOneAsync(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return await _collection.FindOneAndDeleteAsync(filterExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a single document from the collection based on its unique identifier (ID).
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the document to delete.</param>
|
||||
public void DeleteById(string id)
|
||||
{
|
||||
var objectId = new ObjectId(id);
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc._Id, id);
|
||||
_collection.FindOneAndDelete(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes a single document from the collection based on its unique identifier (ID).
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier of the document to delete.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task DeleteByIdAsync(string id)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var objectId = new ObjectId(id);
|
||||
var filter = Builders<TDocument>.Filter.Eq(doc => doc._Id, id);
|
||||
_collection.FindOneAndDeleteAsync(filter);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes multiple documents from the collection based on the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the documents to delete.</param>
|
||||
public void DeleteMany(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
_collection.DeleteMany(filterExpression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously deletes multiple documents from the collection based on the provided filter expression.
|
||||
/// </summary>
|
||||
/// <param name="filterExpression">A lambda expression that defines the filter criteria for the documents to delete.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task DeleteManyAsync(Expression<Func<TDocument, bool>> filterExpression)
|
||||
{
|
||||
return Task.Run(() => _collection.DeleteManyAsync(filterExpression));
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Core.Blueprint.Redis/Adapters/CacheSettings.cs
Normal file
23
Core.Blueprint.Redis/Adapters/CacheSettings.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Blueprint.Redis
|
||||
{
|
||||
public interface ICacheSettings
|
||||
{
|
||||
int DefaultCacheDurationInMinutes { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents the settings for Redis caching.
|
||||
/// </summary>
|
||||
public class CacheSettings: ICacheSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the default cache duration in minutes.
|
||||
/// </summary>
|
||||
public int DefaultCacheDurationInMinutes { get; set; }
|
||||
}
|
||||
}
|
||||
43
Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs
Normal file
43
Core.Blueprint.Redis/Configuration/RegisterBlueprint.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Core.Blueprint.Redis.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for registering Redis-related services in the DI container.
|
||||
/// </summary>
|
||||
public static class RegisterBlueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds Redis caching services to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to register the services into.</param>
|
||||
/// <param name="configuration">The application configuration object.</param>
|
||||
/// <returns>The updated service collection.</returns>
|
||||
public static IServiceCollection AddRedis(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
// Retrieve the Redis connection string from the configuration.
|
||||
// Get Redis configuration section
|
||||
var redisConnectionString = configuration.GetSection("ConnectionStrings:Redis").Value;
|
||||
if (string.IsNullOrEmpty(redisConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("Redis connection is not configured.");
|
||||
}
|
||||
|
||||
// Register RedisCacheProvider
|
||||
services.AddSingleton<IRedisCacheProvider>(provider =>
|
||||
new RedisCacheProvider(redisConnectionString, provider.GetRequiredService<ILogger<RedisCacheProvider>>()));
|
||||
|
||||
// Get CacheSettings and register with the ICacheSettings interface
|
||||
var cacheSettings = configuration.GetSection("CacheSettings").Get<CacheSettings>();
|
||||
if (cacheSettings == null)
|
||||
{
|
||||
throw new InvalidOperationException("Redis CacheSettings section is not configured.");
|
||||
}
|
||||
services.AddSingleton<ICacheSettings>(cacheSettings);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Core.Blueprint.Redis/Contracts/IRedisCacheProvider.cs
Normal file
48
Core.Blueprint.Redis/Contracts/IRedisCacheProvider.cs
Normal file
@ -0,0 +1,48 @@
|
||||
namespace Core.Blueprint.Redis
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for managing Redis cache operations.
|
||||
/// </summary>
|
||||
public interface IRedisCacheProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves a cache item by its key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the cached item.</typeparam>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <returns>The cached item, or default if not found.</returns>
|
||||
ValueTask<TEntity> GetAsync<TEntity>(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a cache item with the specified key and value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the item to cache.</typeparam>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <param name="value">The item to cache.</param>
|
||||
/// <param name="expiry">The optional expiration time for the cache item.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask SetAsync<TEntity>(string key, TEntity value, TimeSpan? expiry = null);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a cache item by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask RemoveAsync(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a cache item exists for the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <returns>True if the cache item exists; otherwise, false.</returns>
|
||||
ValueTask<bool> ExistsAsync(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the expiration time of a cache item if it exists.
|
||||
/// </summary>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <param name="expiry">The new expiration time for the cache item.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
ValueTask RefreshAsync(string key, TimeSpan? expiry = null);
|
||||
}
|
||||
}
|
||||
18
Core.Blueprint.Redis/Core.Blueprint.Redis.csproj
Normal file
18
Core.Blueprint.Redis/Core.Blueprint.Redis.csproj
Normal file
@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Azure.StackExchangeRedis" Version="3.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.22" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
63
Core.Blueprint.Redis/Helpers/RedisCacheKeyHelper.cs
Normal file
63
Core.Blueprint.Redis/Helpers/RedisCacheKeyHelper.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Blueprint.Redis.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for generating consistent and normalized cache keys.
|
||||
/// </summary>
|
||||
public static class CacheKeyHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a cache key based on the instance, method name, and parameters.
|
||||
/// </summary>
|
||||
/// <param name="instance">The instance of the class.</param>
|
||||
/// <param name="methodName">The method name related to the cache key.</param>
|
||||
/// <param name="parameters">The parameters used to generate the key.</param>
|
||||
/// <returns>A normalized cache key string.</returns>
|
||||
public static string GenerateCacheKey(object instance, string methodName, params object[] parameters)
|
||||
{
|
||||
var className = instance.GetType().Name;
|
||||
var keyBuilder = new StringBuilder($"{className}.{methodName}");
|
||||
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
string normalizedParam = NormalizeParameter(param);
|
||||
keyBuilder.Append($".{normalizedParam}");
|
||||
}
|
||||
|
||||
return keyBuilder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a parameter value for use in a cache key.
|
||||
/// </summary>
|
||||
/// <param name="param">The parameter to normalize.</param>
|
||||
/// <returns>A normalized string representation of the parameter.</returns>
|
||||
private static string NormalizeParameter(object param)
|
||||
{
|
||||
if (param == null)
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
|
||||
string paramString;
|
||||
|
||||
if (param is DateTime dateTime)
|
||||
{
|
||||
paramString = dateTime.ToString("yyyyMMdd");
|
||||
}
|
||||
else
|
||||
{
|
||||
paramString = param.ToString();
|
||||
}
|
||||
|
||||
// Replace special characters with an underscore.
|
||||
return Regex.Replace(paramString, @"[^a-zA-Z0-9]", "_");
|
||||
}
|
||||
}
|
||||
}
|
||||
171
Core.Blueprint.Redis/RedisCacheProvider.cs
Normal file
171
Core.Blueprint.Redis/RedisCacheProvider.cs
Normal file
@ -0,0 +1,171 @@
|
||||
using Azure.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StackExchange.Redis;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Core.Blueprint.Redis
|
||||
{
|
||||
/// <summary>
|
||||
/// Redis cache provider for managing cache operations.
|
||||
/// </summary>
|
||||
public sealed class RedisCacheProvider : IRedisCacheProvider
|
||||
{
|
||||
private IDatabase _cacheDatabase = null!;
|
||||
private readonly ILogger<RedisCacheProvider> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RedisCacheProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The Redis connection string.</param>
|
||||
/// <param name="logger">The logger instance for logging operations.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown when connection string is null or empty.</exception>
|
||||
public RedisCacheProvider(string connectionString, ILogger<RedisCacheProvider> logger)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(connectionString))
|
||||
throw new ArgumentNullException(nameof(connectionString), "Redis connection string cannot be null or empty.");
|
||||
|
||||
_logger = logger;
|
||||
_cacheDatabase = InitializeRedisAsync(connectionString).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes and establishes a connection to Redis using the provided connection string.
|
||||
/// </summary>
|
||||
/// <param name="connectionString">The Redis connection string.</param>
|
||||
/// <returns>An <see cref="IDatabase"/> instance representing the Redis cache database.</returns>
|
||||
/// <exception cref="Exception">Thrown when the connection to Redis fails.</exce
|
||||
async Task<IDatabase> InitializeRedisAsync(string connectionString)
|
||||
{
|
||||
try
|
||||
{
|
||||
var configurationOptions = await ConfigurationOptions.Parse($"{connectionString}")
|
||||
.ConfigureForAzureWithTokenCredentialAsync(new DefaultAzureCredential());
|
||||
|
||||
configurationOptions.AbortOnConnectFail = false;
|
||||
var connectionMultiplexer = await ConnectionMultiplexer.ConnectAsync(configurationOptions);
|
||||
|
||||
_logger.LogInformation("Successfully connected to Redis.");
|
||||
|
||||
return connectionMultiplexer.GetDatabase();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error establishing Redis connection.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a cache item by its key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the cached item.</typeparam>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <returns>The cached item of type <typeparamref name="T"/>, or default if not found.</returns>
|
||||
public async ValueTask<TEntity> GetAsync<TEntity>(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = await _cacheDatabase.StringGetAsync(key);
|
||||
if (value.IsNullOrEmpty)
|
||||
{
|
||||
_logger.LogInformation($"Cache miss for key: {key}");
|
||||
return default;
|
||||
}
|
||||
|
||||
_logger.LogInformation($"Cache hit for key: {key}");
|
||||
return JsonSerializer.Deserialize<TEntity>(value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error getting cache item with key {key}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets a cache item with the specified key and value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the item to cache.</typeparam>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <param name="value">The item to cache.</param>
|
||||
/// <param name="expiry">The optional expiration time for the cache item.</param>
|
||||
public async ValueTask SetAsync<TEntity>(string key, TEntity value, TimeSpan? expiry = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
await _cacheDatabase.StringSetAsync(key, json, expiry);
|
||||
_logger.LogInformation($"Cache item set with key: {key}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error setting cache item with key {key}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a cache item by its key.
|
||||
/// </summary>
|
||||
/// <param name="key">The cache key.</param>
|
||||
public async ValueTask RemoveAsync(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _cacheDatabase.KeyDeleteAsync(key);
|
||||
_logger.LogInformation($"Cache item removed with key: {key}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error removing cache item with key {key}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a cache item exists for the specified key.
|
||||
/// </summary>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <returns>True if the cache item exists; otherwise, false.</returns>
|
||||
public async ValueTask<bool> ExistsAsync(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var exists = await _cacheDatabase.KeyExistsAsync(key);
|
||||
_logger.LogInformation($"Cache item exists check for key: {key} - {exists}");
|
||||
return exists;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error checking existence of cache item with key {key}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the expiration time of a cache item if it exists.
|
||||
/// </summary>
|
||||
/// <param name="key">The cache key.</param>
|
||||
/// <param name="expiry">The new expiration time for the cache item.</param>
|
||||
public async ValueTask RefreshAsync(string key, TimeSpan? expiry = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var value = await _cacheDatabase.StringGetAsync(key);
|
||||
if (!value.IsNullOrEmpty)
|
||||
{
|
||||
await _cacheDatabase.StringSetAsync(key, value, expiry);
|
||||
_logger.LogInformation($"Cache item refreshed with key: {key}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning($"Cache item with key: {key} does not exist, cannot refresh");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error refreshing cache item with key {key}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Core.Blueprint.SQLServer/Adapters/BaseSQLAdapter.cs
Normal file
65
Core.Blueprint.SQLServer/Adapters/BaseSQLAdapter.cs
Normal file
@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.SQLServer.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the base class for SQL Server entities, providing common properties for auditing and state management.
|
||||
/// </summary>
|
||||
public abstract class BaseSQLAdapter : IBaseSQLAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier for the entity.
|
||||
/// </summary>
|
||||
[Key]
|
||||
[JsonPropertyName("id")]
|
||||
public int Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for the entity.
|
||||
/// </summary>
|
||||
[JsonPropertyName("guid")]
|
||||
public string Guid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp when the entity was created.
|
||||
/// Default value is the current UTC time at the moment of instantiation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("createdAt")]
|
||||
public DateTime? CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the user or system that created the entity.
|
||||
/// </summary>
|
||||
[JsonPropertyName("createdBy")]
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp when the entity was last updated.
|
||||
/// Null if the entity has not been updated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("updatedAt")]
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the user or system that last updated the entity.
|
||||
/// Null if the entity has not been updated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("updatedBy")]
|
||||
public string? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the entity, indicating whether it is active, inactive, or in another state.
|
||||
/// Default value is <see cref="StatusEnum.Active"/>.
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public StatusEnum Status { get; set; }
|
||||
|
||||
protected BaseSQLAdapter()
|
||||
{
|
||||
Guid = System.Guid.NewGuid().ToString();
|
||||
CreatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Core.Blueprint.SQLServer/Adapters/StatusEnum.cs
Normal file
29
Core.Blueprint.SQLServer/Adapters/StatusEnum.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.SQLServer.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the possible statuses for entities in the system.
|
||||
/// Used to track the state of an entity, such as whether it is active, inactive, or deleted.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum StatusEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that the entity is currently active and operational.
|
||||
/// </summary>
|
||||
Active = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the entity is currently inactive but still exists in the system.
|
||||
/// Typically used for temporary deactivation or soft-offline states.
|
||||
/// </summary>
|
||||
Inactive = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that the entity has been deleted and is no longer accessible.
|
||||
/// Often used in soft-delete scenarios where the entity is retained for archival or audit purposes.
|
||||
/// </summary>
|
||||
Deleted = 2
|
||||
}
|
||||
}
|
||||
33
Core.Blueprint.SQLServer/Configuration/RegisterBlueprint.cs
Normal file
33
Core.Blueprint.SQLServer/Configuration/RegisterBlueprint.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using Azure.Identity;
|
||||
using Core.Blueprint.DAL.SQLServer;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Core.Blueprint.SQLServer.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for configuring SQL Server.
|
||||
/// </summary>
|
||||
public static class RegisterBlueprint
|
||||
{
|
||||
/// <summary>
|
||||
/// Configures SQL Server services, including the database context and generic repository, for dependency injection.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to which the SQL Server services will be added.</param>
|
||||
/// <param name="configuration">The application configuration object for accessing settings such as connection strings.</param>
|
||||
/// <returns>An updated <see cref="IServiceCollection"/> with SQL Server services registered.</returns>
|
||||
public static IServiceCollection AddSQLServer(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
var chainedCredentials = new ChainedTokenCredential(
|
||||
new ManagedIdentityCredential(),
|
||||
new SharedTokenCacheCredential(),
|
||||
new VisualStudioCredential(),
|
||||
new VisualStudioCodeCredential()
|
||||
);
|
||||
|
||||
services.AddScoped(typeof(IEntityRepository<,>), typeof(EntityRepository<,>));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Core.Blueprint.SQLServer/Contracts/IBaseSQLAdapter.cs
Normal file
57
Core.Blueprint.SQLServer/Contracts/IBaseSQLAdapter.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using Core.Blueprint.SQLServer.Entities;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Core.Blueprint.SQLServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the interface for SQL Server entities, providing common properties for auditing and state management.
|
||||
/// </summary>
|
||||
public interface IBaseSQLAdapter
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier for the entity.
|
||||
/// </summary>
|
||||
[Key]
|
||||
[JsonPropertyName("id")]
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the GUID for the entity.
|
||||
/// </summary>
|
||||
[JsonPropertyName("guid")]
|
||||
string Guid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp when the entity was created.
|
||||
/// </summary>
|
||||
[JsonPropertyName("createdAt")]
|
||||
DateTime? CreatedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the user or system that created the entity.
|
||||
/// </summary>
|
||||
[JsonPropertyName("createdBy")]
|
||||
string? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timestamp when the entity was last updated.
|
||||
/// </summary>
|
||||
[JsonPropertyName("updatedAt")]
|
||||
DateTime? UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the user or system that last updated the entity.
|
||||
/// </summary>
|
||||
[JsonPropertyName("updatedBy")]
|
||||
string? UpdatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status of the entity, indicating whether it is active, inactive, or in another state.
|
||||
/// </summary>
|
||||
[JsonPropertyName("status")]
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
StatusEnum Status { get; set; }
|
||||
}
|
||||
}
|
||||
108
Core.Blueprint.SQLServer/Contracts/IEntityRepository.cs
Normal file
108
Core.Blueprint.SQLServer/Contracts/IEntityRepository.cs
Normal file
@ -0,0 +1,108 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Core.Blueprint.DAL.SQLServer
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the contract for a generic repository to manage entities in a SQL Server database.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The type of the entity managed by the repository. Must be a class.</typeparam>
|
||||
/// <typeparam name="TContext">The type of the database context used by the repository. Must inherit from <see cref="DbContext"/>.</typeparam>
|
||||
public interface IEntityRepository<TEntity, TContext>
|
||||
where TEntity : class
|
||||
where TContext : DbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves all entities of type <typeparamref name="T"/> from the database.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation, with a collection of entities as the result.</returns>
|
||||
Task<IEnumerable<TEntity>> GetAllAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all entities of type <typeparamref name="T"/> from the database that match a specified condition.
|
||||
/// </summary>
|
||||
/// <param name="predicate">An expression to filter the entities.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with a collection of matching entities as the result.</returns>
|
||||
Task<IEnumerable<TEntity>> GetByConditionAsync(Expression<Func<TEntity, bool>> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a single entity of type <typeparamref name="T"/> by its identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entity to retrieve.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with the entity as the result, or null if not found.</returns>
|
||||
Task<TEntity?> GetByIdAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the first entity of type <typeparamref name="T"/> that matches a specified condition, or null if no match is found.
|
||||
/// </summary>
|
||||
/// <param name="predicate">An expression to filter the entities.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with the matching entity as the result, or null if none match.</returns>
|
||||
Task<TEntity?> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new entity of type <typeparamref name="T"/> to the database.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to add.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddAsync(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple entities of type <typeparamref name="T"/> to the database.
|
||||
/// </summary>
|
||||
/// <param name="entities">The collection of entities to add.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task AddRangeAsync(IEnumerable<TEntity> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing entity of type <typeparamref name="T"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to update.</param>
|
||||
/// <returns>The updated entity.</returns>
|
||||
TEntity Update(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Updates multiple entities of type <typeparamref name="T"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="entities">The collection of entities to update.</param>
|
||||
void UpdateRange(IEnumerable<TEntity> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an entity of type <typeparamref name="T"/> from the database.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to delete.</param>
|
||||
void Delete(TEntity entity);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes multiple entities of type <typeparamref name="T"/> from the database.
|
||||
/// </summary>
|
||||
/// <param name="entities">The collection of entities to delete.</param>
|
||||
void DeleteRange(IEnumerable<TEntity> entities);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether any entities of type <typeparamref name="T"/> exist in the database that match a specified condition.
|
||||
/// </summary>
|
||||
/// <param name="predicate">An expression to filter the entities.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with a boolean result indicating whether any match exists.</returns>
|
||||
Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate);
|
||||
|
||||
/// <summary>
|
||||
/// Executes a raw SQL query and maps the result to entities of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <param name="sql">The raw SQL query to execute.</param>
|
||||
/// <param name="parameters">Optional parameters for the SQL query.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with a collection of entities as the result.</returns>
|
||||
Task<IEnumerable<TEntity>> ExecuteRawSqlAsync(string sql, params object[] parameters);
|
||||
|
||||
/// <summary>
|
||||
/// Counts the total number of entities of type <typeparamref name="T"/> in the database.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation, with the count as the result.</returns>
|
||||
Task<int> CountAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Saves all pending changes to the database.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
Task SaveAsync();
|
||||
}
|
||||
}
|
||||
16
Core.Blueprint.SQLServer/Core.Blueprint.SQLServer.csproj
Normal file
16
Core.Blueprint.SQLServer/Core.Blueprint.SQLServer.csproj
Normal file
@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.13.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
182
Core.Blueprint.SQLServer/Repositories/EntityRepository.cs
Normal file
182
Core.Blueprint.SQLServer/Repositories/EntityRepository.cs
Normal file
@ -0,0 +1,182 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Core.Blueprint.DAL.SQLServer
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="EntityRepository{TEntity, TContext}"/> class provides a comprehensive generic repository
|
||||
/// for managing entities using Entity Framework Core with SQL Server as the underlying database.
|
||||
/// Designed as a package for consumption by external applications.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The entity type managed by the repository. Must be a class.</typeparam>
|
||||
/// <typeparam name="TContext">The database context type. Must inherit from <see cref="DbContext"/>.</typeparam>
|
||||
public class EntityRepository<TEntity, TContext> : IEntityRepository<TEntity, TContext>
|
||||
where TEntity : class
|
||||
where TContext : DbContext
|
||||
{
|
||||
private readonly TContext _context;
|
||||
private readonly DbSet<TEntity> _dbSet;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="EntityRepository{TEntity, TContext}"/> class with a specified database context.
|
||||
/// </summary>
|
||||
/// <param name="context">The <see cref="TContext"/> for database operations.</param>
|
||||
public EntityRepository(TContext context)
|
||||
{
|
||||
_context = context;
|
||||
_dbSet = _context.Set<TEntity>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all entities of type <typeparamref name="TEntity"/> from the database.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation, with a list of entities as the result.</returns>
|
||||
public async Task<IEnumerable<TEntity>> GetAllAsync()
|
||||
{
|
||||
return await _dbSet.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all entities of type <typeparamref name="TEntity"/> from the database that match a specified filter.
|
||||
/// </summary>
|
||||
/// <param name="predicate">An expression to filter entities.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with a list of filtered entities as the result.</returns>
|
||||
public async Task<IEnumerable<TEntity>> GetByConditionAsync(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
return await _dbSet.Where(predicate).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a single entity of type <typeparamref name="TEntity"/> by its identifier.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier of the entity.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with the entity as the result, or null if not found.</returns>
|
||||
public async Task<TEntity?> GetByIdAsync(int id)
|
||||
{
|
||||
var existingEntity = await _dbSet.FindAsync(id);
|
||||
|
||||
if (existingEntity != null)
|
||||
{
|
||||
_context.Entry(existingEntity).State = EntityState.Detached;
|
||||
}
|
||||
|
||||
return existingEntity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new entity to the database.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to add.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task AddAsync(TEntity entity)
|
||||
{
|
||||
await _dbSet.AddAsync(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple entities to the database.
|
||||
/// </summary>
|
||||
/// <param name="entities">The collection of entities to add.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task AddRangeAsync(IEnumerable<TEntity> entities)
|
||||
{
|
||||
await _dbSet.AddRangeAsync(entities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates an existing entity in the database.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to update.</param>
|
||||
/// <returns>The updated entity.</returns>
|
||||
public TEntity Update(TEntity entity)
|
||||
{
|
||||
_dbSet.Attach(entity);
|
||||
_context.Entry(entity).State = EntityState.Modified;
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates multiple entities in the database.
|
||||
/// </summary>
|
||||
/// <param name="entities">The collection of entities to update.</param>
|
||||
public void UpdateRange(IEnumerable<TEntity> entities)
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
_dbSet.Attach(entity);
|
||||
_context.Entry(entity).State = EntityState.Modified;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes an entity from the database.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity to delete.</param>
|
||||
public void Delete(TEntity entity)
|
||||
{
|
||||
if (_context.Entry(entity).State == EntityState.Detached)
|
||||
{
|
||||
_dbSet.Attach(entity);
|
||||
}
|
||||
_dbSet.Remove(entity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes multiple entities from the database.
|
||||
/// </summary>
|
||||
/// <param name="entities">The collection of entities to delete.</param>
|
||||
public void DeleteRange(IEnumerable<TEntity> entities)
|
||||
{
|
||||
_dbSet.RemoveRange(entities);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the first entity matching the specified condition or null if no match is found.
|
||||
/// </summary>
|
||||
/// <param name="predicate">An expression to filter entities.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with the matched entity as the result.</returns>
|
||||
public async Task<TEntity?> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
return await _dbSet.FirstOrDefaultAsync(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if any entities exist that match the specified condition.
|
||||
/// </summary>
|
||||
/// <param name="predicate">An expression to filter entities.</param>
|
||||
/// <returns>A task representing the asynchronous operation, with a boolean result indicating existence.</returns>
|
||||
public async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> predicate)
|
||||
{
|
||||
return await _dbSet.AnyAsync(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves all pending changes to the database.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task SaveAsync()
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a raw SQL query and maps the result to the specified entity type.
|
||||
/// </summary>
|
||||
/// <param name="sql">The raw SQL query.</param>
|
||||
/// <param name="parameters">Optional parameters for the query.</param>
|
||||
/// <returns>An <see cref="IEnumerable{TEntity}"/> representing the result set.</returns>
|
||||
public async Task<IEnumerable<TEntity>> ExecuteRawSqlAsync(string sql, params object[] parameters)
|
||||
{
|
||||
return await _dbSet.FromSqlRaw(sql, parameters).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Counts the total number of entities in the database.
|
||||
/// </summary>
|
||||
/// <returns>A task representing the asynchronous operation, with the count as the result.</returns>
|
||||
public async Task<int> CountAsync()
|
||||
{
|
||||
return await _dbSet.CountAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Core.Blueprint.Storage/Adapters/BlobAddDto.cs
Normal file
8
Core.Blueprint.Storage/Adapters/BlobAddDto.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Core.Blueprint.Storage
|
||||
{
|
||||
public class BlobAddDto
|
||||
{
|
||||
public string? FileName { get; set; }
|
||||
public byte[] FileContent { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
12
Core.Blueprint.Storage/Adapters/BlobDownloadAdapter.cs
Normal file
12
Core.Blueprint.Storage/Adapters/BlobDownloadAdapter.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Blueprint.Storage.Adapters
|
||||
{
|
||||
class BlobDownloadAdapter
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
namespace Core.Blueprint.Storage.Adapters
|
||||
{
|
||||
public class BlobDownloadUriAdapter
|
||||
{
|
||||
public Uri Uri { get; set; } = null!;
|
||||
public string Name { get; set; } = null!;
|
||||
public string? Status { get; set; }
|
||||
}
|
||||
}
|
||||
13
Core.Blueprint.Storage/Adapters/BlobFileAdapter.cs
Normal file
13
Core.Blueprint.Storage/Adapters/BlobFileAdapter.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Core.Blueprint.Storage
|
||||
{
|
||||
public class BlobFileAdapter
|
||||
{
|
||||
public string? Uri { get; set; }
|
||||
public string Name { get; set; } = null!;
|
||||
public string? DateUpload { get; set; }
|
||||
public string? ContentType { get; set; }
|
||||
public long? Size { get; set; }
|
||||
public string? Status { get; set; }
|
||||
public string? ShortDate { get; set; }
|
||||
}
|
||||
}
|
||||
15
Core.Blueprint.Storage/Adapters/BlobStorageAdapter.cs
Normal file
15
Core.Blueprint.Storage/Adapters/BlobStorageAdapter.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Blueprint.Storage
|
||||
{
|
||||
public class BlobStorageAdapter
|
||||
{
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public string DownloadUrl { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
16
Core.Blueprint.Storage/Adapters/BlobStorageFolder.cs
Normal file
16
Core.Blueprint.Storage/Adapters/BlobStorageFolder.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Core.Blueprint.Storage.Adapters
|
||||
{
|
||||
public record BlobStorageFolder
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public List<BlobStorageFolder> SubFolders { get; set; } = [];
|
||||
public List<BlobStorageFilesAdapter> Files { get; set; } = [];
|
||||
}
|
||||
public record BlobStorageFilesAdapter(string Content, string Name, string ContentType, string DownloadUrl);
|
||||
}
|
||||
66
Core.Blueprint.Storage/Adapters/Trie/TrieNode.cs
Normal file
66
Core.Blueprint.Storage/Adapters/Trie/TrieNode.cs
Normal file
@ -0,0 +1,66 @@
|
||||
|
||||
namespace Core.Blueprint.Storage
|
||||
{
|
||||
public class TrieNode
|
||||
{
|
||||
public Dictionary<char, TrieNode> Children { get; private set; }
|
||||
public bool IsEndOfWord { get; set; }
|
||||
|
||||
public TrieNode()
|
||||
{
|
||||
Children = [];
|
||||
IsEndOfWord = false;
|
||||
}
|
||||
}
|
||||
public class Trie
|
||||
{
|
||||
private readonly TrieNode _root;
|
||||
|
||||
public Trie()
|
||||
{
|
||||
_root = new TrieNode();
|
||||
}
|
||||
|
||||
public void Insert(string word)
|
||||
{
|
||||
var node = _root;
|
||||
foreach (var ch in word)
|
||||
{
|
||||
if (!node.Children.ContainsKey(ch))
|
||||
{
|
||||
node.Children[ch] = new TrieNode();
|
||||
}
|
||||
node = node.Children[ch];
|
||||
}
|
||||
node.IsEndOfWord = true;
|
||||
}
|
||||
|
||||
public List<string> SearchByPrefix(string? prefix)
|
||||
{
|
||||
var results = new List<string>();
|
||||
var node = _root;
|
||||
foreach (var ch in prefix)
|
||||
{
|
||||
if (!node.Children.ContainsKey(ch))
|
||||
{
|
||||
return results;
|
||||
}
|
||||
node = node.Children[ch];
|
||||
}
|
||||
SearchByPrefixHelper(node, prefix, results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private void SearchByPrefixHelper(TrieNode node, string currentPrefix, List<string> results)
|
||||
{
|
||||
if (node.IsEndOfWord)
|
||||
{
|
||||
results.Add(currentPrefix);
|
||||
}
|
||||
foreach (var kvp in node.Children)
|
||||
{
|
||||
SearchByPrefixHelper(kvp.Value, currentPrefix + kvp.Key, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Core.Blueprint.Storage/Configuration/RegisterBlueprint.cs
Normal file
38
Core.Blueprint.Storage/Configuration/RegisterBlueprint.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using Azure.Identity;
|
||||
using Core.Blueprint.Storage.Contracts;
|
||||
using Core.Blueprint.Storage.Provider;
|
||||
using Microsoft.Extensions.Azure;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Core.Blueprint.Storage.Configuration
|
||||
{
|
||||
public static class RegisterBlueprint
|
||||
{
|
||||
public static IServiceCollection AddBlobStorage(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
|
||||
var blobConnection = configuration.GetConnectionString("BlobStorage");
|
||||
|
||||
if (blobConnection == null || string.IsNullOrWhiteSpace(blobConnection))
|
||||
{
|
||||
throw new ArgumentException("The BlobStorage configuration section is missing or empty.");
|
||||
}
|
||||
|
||||
var chainedCredentials = new ChainedTokenCredential(
|
||||
new ManagedIdentityCredential(),
|
||||
new SharedTokenCacheCredential(),
|
||||
new VisualStudioCredential(),
|
||||
new VisualStudioCodeCredential()
|
||||
);
|
||||
services.AddAzureClients(cfg =>
|
||||
{
|
||||
cfg.AddBlobServiceClient(new Uri(blobConnection)).WithCredential(chainedCredentials);
|
||||
});
|
||||
|
||||
services.AddScoped<IBlobStorageProvider, BlobStorageProvider>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
181
Core.Blueprint.Storage/Contracts/IBlobStorageProvider.cs
Normal file
181
Core.Blueprint.Storage/Contracts/IBlobStorageProvider.cs
Normal file
@ -0,0 +1,181 @@
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
using Azure.Storage.Sas;
|
||||
using Core.Blueprint.Storage.Adapters;
|
||||
|
||||
namespace Core.Blueprint.Storage.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a contract for managing blobs and containers in Azure Blob Storage.
|
||||
/// </summary>
|
||||
public interface IBlobStorageProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the blob container if it does not exist.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Response{T}"/> containing the container information.</returns>
|
||||
Task<Response<BlobContainerInfo>> CreateIfNotExistsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the blob container if it exists.
|
||||
/// </summary>
|
||||
Task DeleteIfExistsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets properties of the blob container.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Response{T}"/> containing container properties.</returns>
|
||||
Task<Response<BlobContainerProperties>> GetPropertiesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Sets metadata for the blob container.
|
||||
/// </summary>
|
||||
/// <param name="metadata">The metadata to set for the container.</param>
|
||||
Task SetMetadataAsync(IDictionary<string, string> metadata);
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a blob to the container.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The name of the blob.</param>
|
||||
/// <param name="content">The content to upload.</param>
|
||||
/// <returns>A <see cref="Response{T}"/> containing blob content information.</returns>
|
||||
Task<Response<BlobContentInfo>> UploadBlobAsync(string blobName, Stream content);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a blob from the container.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The name of the blob.</param>
|
||||
/// <returns>A <see cref="Response{T}"/> containing blob download information.</returns>
|
||||
/// <exception cref="FileNotFoundException">Thrown if the blob does not exist.</exception>
|
||||
Task<Response<BlobDownloadInfo>> DownloadBlobAsync(string blobName);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a blob from the container.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The name of the blob.</param>
|
||||
Task<bool> DeleteBlobAsync(string blobName);
|
||||
|
||||
/// <summary>
|
||||
/// Lists all blobs in the container with an optional prefix.
|
||||
/// </summary>
|
||||
/// <param name="prefix">The prefix to filter blobs.</param>
|
||||
/// <returns>A collection of <see cref="BlobItem"/>.</returns>
|
||||
Task<IEnumerable<BlobItem>> ListBlobItemAsync(string? prefix = null);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the account information for the associated Blob Service Client.
|
||||
/// </summary>
|
||||
/// <param name="cancellation">
|
||||
/// A <see cref="CancellationToken"/> that can be used to cancel the operation.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation. The task result contains the
|
||||
/// <see cref="AccountInfo"/> object, which provides details about the account, such as the SKU
|
||||
/// and account kind.
|
||||
Task<AccountInfo> GetAccountInfoAsync(CancellationToken cancellation);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a blob client for a specific blob.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The name of the blob.</param>
|
||||
/// <returns>A <see cref="BlobClient"/> for the blob.</returns>
|
||||
BlobClient GetBlobClient(string blobName);
|
||||
|
||||
/// <summary>
|
||||
/// Lists blobs hierarchically using a delimiter.
|
||||
/// </summary>
|
||||
/// <param name="prefix">The prefix to filter blobs.</param>
|
||||
/// <param name="delimiter">The delimiter to use for hierarchy.</param>
|
||||
/// <returns>A collection of <see cref="BlobHierarchyItem"/>.</returns>
|
||||
Task<IEnumerable<BlobHierarchyItem>> ListBlobsByHierarchyAsync(string? prefix = null, string delimiter = "/");
|
||||
|
||||
/// <summary>
|
||||
/// Generates a SAS token for the container with specified permissions.
|
||||
/// </summary>
|
||||
/// <param name="permissions">The permissions to assign to the SAS token.</param>
|
||||
/// <param name="expiresOn">The expiration time for the SAS token.</param>
|
||||
/// <returns>A <see cref="Uri"/> containing the SAS token.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if SAS URI generation is not supported.</exception>
|
||||
Uri GenerateContainerSasUri(BlobContainerSasPermissions permissions, DateTimeOffset expiresOn);
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a lease on the blob container.
|
||||
/// </summary>
|
||||
/// <param name="proposedId">The optional proposed lease ID.</param>
|
||||
/// <param name="duration">The optional lease duration.</param>
|
||||
/// <returns>A <see cref="Response{T}"/> containing lease information.</returns>
|
||||
Task<Response<BlobLease>> AcquireLeaseAsync(string? proposedId = null, TimeSpan? duration = null);
|
||||
|
||||
/// <summary>
|
||||
/// Releases a lease on the blob container.
|
||||
/// </summary>
|
||||
/// <param name="leaseId">The lease ID to release.</param>
|
||||
Task ReleaseLeaseAsync(string leaseId);
|
||||
|
||||
/// <summary>
|
||||
/// Sets access policies for the blob container.
|
||||
/// </summary>
|
||||
/// <param name="accessType">The type of public access to allow.</param>
|
||||
/// <param name="identifiers">The optional list of signed identifiers for access policy.</param>
|
||||
Task SetAccessPolicyAsync(PublicAccessType accessType, IEnumerable<BlobSignedIdentifier>? identifiers = null);
|
||||
|
||||
/// <summary>
|
||||
/// Lists blobs in the container with an optional prefix.
|
||||
/// </summary>
|
||||
/// <param name="prefix">The prefix to filter blobs.</param>
|
||||
/// <returns>A collection of <see cref="BlobFileAdapter"/>.</returns>
|
||||
Task<IEnumerable<BlobFileAdapter>> ListBlobsAsync(string? prefix = null);
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a blob to the container.
|
||||
/// </summary>
|
||||
/// <param name="newBlob">The blob to upload.</param>
|
||||
/// <returns>A <see cref="BlobFileAdapter"/> representing the uploaded blob.</returns>
|
||||
Task<BlobFileAdapter> UploadBlobAsync(BlobAddDto newBlob);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a blob from the container.
|
||||
/// </summary>
|
||||
/// <param name="fileName">The name of the blob to delete.</param>
|
||||
/// <returns>A <see cref="BlobFileAdapter"/> representing the deleted blob, or null if the blob was not found.</returns>
|
||||
Task<BlobFileAdapter?> DeleteBlobsAsync(string fileName);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a blob's content.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The name of the blob.</param>
|
||||
/// <returns>A <see cref="BlobDownloadInfo"/> representing the downloaded blob.</returns>
|
||||
/// <exception cref="FileNotFoundException">Thrown if the blob does not exist.</exception>
|
||||
Task<BlobDownloadInfo> DownloadBlobsAsync(string blobName);
|
||||
|
||||
/// <summary>
|
||||
/// Generates a secure download URI for a specified blob in the storage container.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The name of the blob for which the download URI is being generated.</param>
|
||||
/// <returns>
|
||||
/// An instance of <see cref="BlobDownloadUriAdapter"/> containing the generated URI, blob name, and status.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The generated URI includes a Shared Access Signature (SAS) token, which allows secure, time-limited access to the blob.
|
||||
/// The SAS token grants read-only access to the blob for a duration of 5 minutes starting from the current time.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="blobName"/> is null or empty.</exception>
|
||||
/// <exception cref="StorageException">Thrown if there is an issue communicating with the Azure Blob service.</exception>
|
||||
BlobDownloadUriAdapter GenerateBlobDownloadUri(string blobName);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the hierarchical folder structure.
|
||||
/// </summary>
|
||||
/// <param name="prefix">The prefix to start the hierarchy retrieval.</param>
|
||||
/// <returns>A list of <see cref="BlobStorageFolder"/> representing the folder structure.</returns>
|
||||
Task<List<BlobStorageFolder>> GetFolderHierarchyAsync(string prefix);
|
||||
|
||||
/// <summary>
|
||||
/// Lists neighboring folders based on a prefix.
|
||||
/// </summary>
|
||||
/// <param name="prefix">The prefix to search for neighboring folders.</param>
|
||||
/// <returns>A dictionary grouping folder names by their prefix.</returns>
|
||||
Task<Dictionary<string, List<string>>> ListNeighborFoldersAsync(string? prefix);
|
||||
}
|
||||
}
|
||||
17
Core.Blueprint.Storage/Core.Blueprint.Storage.csproj
Normal file
17
Core.Blueprint.Storage/Core.Blueprint.Storage.csproj
Normal file
@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.Identity" Version="1.13.1" />
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.9.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
372
Core.Blueprint.Storage/Provider/BlobStorageProvider.cs
Normal file
372
Core.Blueprint.Storage/Provider/BlobStorageProvider.cs
Normal file
@ -0,0 +1,372 @@
|
||||
using Azure;
|
||||
using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
using Azure.Storage.Blobs.Specialized;
|
||||
using Azure.Storage.Sas;
|
||||
using Core.Blueprint.Storage.Adapters;
|
||||
using Core.Blueprint.Storage.Contracts;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Core.Blueprint.Storage.Provider
|
||||
{
|
||||
public sealed class BlobStorageProvider : IBlobStorageProvider
|
||||
{
|
||||
private readonly BlobServiceClient _blobServiceClient;
|
||||
private readonly BlobContainerClient _blobContainerClient;
|
||||
private readonly string _containerName;
|
||||
private readonly Trie _trie = new Trie();
|
||||
|
||||
public BlobStorageProvider(BlobServiceClient blobServiceClient, IConfiguration configuration)
|
||||
{
|
||||
_blobServiceClient = blobServiceClient;
|
||||
_containerName = configuration.GetSection("BlobStorage:ContainerName").Value ?? "";
|
||||
|
||||
if (string.IsNullOrEmpty(_containerName))
|
||||
throw new ArgumentException("Blob container cannot be null or empty.");
|
||||
|
||||
_blobContainerClient = blobServiceClient.GetBlobContainerClient(_containerName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the blob container if it does not exist.
|
||||
/// </summary>
|
||||
public async Task<Response<BlobContainerInfo>> CreateIfNotExistsAsync()
|
||||
{
|
||||
return await _blobContainerClient.CreateIfNotExistsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes the blob container if it exists.
|
||||
/// </summary>
|
||||
public async Task DeleteIfExistsAsync()
|
||||
{
|
||||
await _blobContainerClient.DeleteIfExistsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets properties of the blob container.
|
||||
/// </summary>
|
||||
public async Task<Response<BlobContainerProperties>> GetPropertiesAsync()
|
||||
{
|
||||
return await _blobContainerClient.GetPropertiesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets metadata for the blob container.
|
||||
/// </summary>
|
||||
public async Task SetMetadataAsync(IDictionary<string, string> metadata)
|
||||
{
|
||||
await _blobContainerClient.SetMetadataAsync(metadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a blob to the container.
|
||||
/// </summary>
|
||||
public async Task<Response<BlobContentInfo>> UploadBlobAsync(string blobName, Stream content)
|
||||
{
|
||||
var blobClient = _blobContainerClient.GetBlobClient(blobName);
|
||||
return await blobClient.UploadAsync(content, overwrite: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a blob from the container.
|
||||
/// </summary>
|
||||
public async Task<Response<BlobDownloadInfo>> DownloadBlobAsync(string blobName)
|
||||
{
|
||||
var blobClient = _blobContainerClient.GetBlobClient(blobName);
|
||||
return await blobClient.DownloadAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a blob from the container.
|
||||
/// </summary>
|
||||
public async Task<bool> DeleteBlobAsync(string blobName)
|
||||
{
|
||||
var blobClient = _blobContainerClient.GetBlobClient(blobName);
|
||||
return await blobClient.DeleteIfExistsAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists all blobs in the container with an optional prefix.
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<BlobItem>> ListBlobItemAsync(string? prefix = null)
|
||||
{
|
||||
var blobs = new List<BlobItem>();
|
||||
|
||||
await foreach (var blobItem in _blobContainerClient.GetBlobsAsync(prefix: prefix))
|
||||
{
|
||||
blobs.Add(blobItem);
|
||||
}
|
||||
|
||||
return blobs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the account information for the associated Blob Service Client.
|
||||
/// </summary>
|
||||
/// <param name="cancellation">
|
||||
/// A <see cref="CancellationToken"/> that can be used to cancel the operation.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation. The task result contains the
|
||||
/// <see cref="AccountInfo"/> object, which provides details about the account, such as the SKU
|
||||
/// and account kind.
|
||||
public async Task<AccountInfo> GetAccountInfoAsync(CancellationToken cancellation)
|
||||
{
|
||||
return await _blobServiceClient.GetAccountInfoAsync(cancellation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a blob client for a specific blob.
|
||||
/// </summary>
|
||||
public BlobClient GetBlobClient(string blobName)
|
||||
{
|
||||
return _blobContainerClient.GetBlobClient(blobName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists blobs hierarchically using a delimiter.
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<BlobHierarchyItem>> ListBlobsByHierarchyAsync(string? prefix = null, string delimiter = "/")
|
||||
{
|
||||
var blobs = new List<BlobHierarchyItem>();
|
||||
|
||||
await foreach (var blobHierarchyItem in _blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: delimiter))
|
||||
{
|
||||
blobs.Add(blobHierarchyItem);
|
||||
}
|
||||
|
||||
return blobs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a SAS token for the container with specified permissions.
|
||||
/// </summary>
|
||||
public Uri GenerateContainerSasUri(BlobContainerSasPermissions permissions, DateTimeOffset expiresOn)
|
||||
{
|
||||
if (!_blobContainerClient.CanGenerateSasUri)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot generate SAS URI. Ensure the client is authorized with account key credentials.");
|
||||
}
|
||||
|
||||
var sasBuilder = new BlobSasBuilder
|
||||
{
|
||||
BlobContainerName = _blobContainerClient.Name,
|
||||
Resource = "c", // c for container
|
||||
ExpiresOn = expiresOn
|
||||
};
|
||||
|
||||
sasBuilder.SetPermissions(permissions);
|
||||
return _blobContainerClient.GenerateSasUri(sasBuilder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a lease on the blob container.
|
||||
/// </summary>
|
||||
public async Task<Response<BlobLease>> AcquireLeaseAsync(string? proposedId = null, TimeSpan? duration = null)
|
||||
{
|
||||
return await _blobContainerClient.GetBlobLeaseClient(proposedId).AcquireAsync(duration ?? TimeSpan.FromSeconds(60));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases a lease on the blob container.
|
||||
/// </summary>
|
||||
public async Task ReleaseLeaseAsync(string leaseId)
|
||||
{
|
||||
await _blobContainerClient.GetBlobLeaseClient(leaseId).ReleaseAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets access policies for the blob container.
|
||||
/// </summary>
|
||||
public async Task SetAccessPolicyAsync(PublicAccessType accessType, IEnumerable<BlobSignedIdentifier>? identifiers = null)
|
||||
{
|
||||
await _blobContainerClient.SetAccessPolicyAsync(accessType, identifiers);
|
||||
}
|
||||
/// <summary>
|
||||
/// Lists blobs in the container with an optional prefix.
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<BlobFileAdapter>> ListBlobsAsync(string? prefix = null)
|
||||
{
|
||||
var blobs = new List<BlobFileAdapter>();
|
||||
|
||||
await foreach (BlobItem blob in _blobContainerClient.GetBlobsAsync(prefix: prefix))
|
||||
{
|
||||
blobs.Add(new BlobFileAdapter
|
||||
{
|
||||
Name = blob.Name,
|
||||
Uri = $"{_blobContainerClient.Uri}/{blob.Name}",
|
||||
ContentType = blob.Properties.ContentType,
|
||||
Size = blob.Properties.ContentLength,
|
||||
DateUpload = blob.Properties.LastModified?.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
ShortDate = blob.Properties.LastModified?.UtcDateTime.ToString("MM-dd-yyyy"),
|
||||
Status = "Available"
|
||||
});
|
||||
}
|
||||
|
||||
return blobs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a blob to the container.
|
||||
/// </summary>
|
||||
public async Task<BlobFileAdapter> UploadBlobAsync(BlobAddDto newBlob)
|
||||
{
|
||||
var blobClient = _blobContainerClient.GetBlobClient(newBlob.FileName);
|
||||
|
||||
using var stream = new MemoryStream(newBlob.FileContent);
|
||||
await blobClient.UploadAsync(stream, overwrite: true);
|
||||
|
||||
var properties = await blobClient.GetPropertiesAsync();
|
||||
|
||||
return new BlobFileAdapter
|
||||
{
|
||||
Name = newBlob.FileName ?? "",
|
||||
Uri = blobClient.Uri.ToString(),
|
||||
ContentType = properties.Value.ContentType,
|
||||
Size = properties.Value.ContentLength,
|
||||
DateUpload = properties.Value.LastModified.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
ShortDate = properties.Value.LastModified.UtcDateTime.ToString("MM-dd-yyyy"),
|
||||
Status = "Uploaded"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a blob from the container.
|
||||
/// </summary>
|
||||
public async Task<BlobFileAdapter?> DeleteBlobsAsync(string fileName)
|
||||
{
|
||||
var blobClient = _blobContainerClient.GetBlobClient(fileName);
|
||||
|
||||
if (await blobClient.ExistsAsync())
|
||||
{
|
||||
var properties = await blobClient.GetPropertiesAsync();
|
||||
var _response = await blobClient.DeleteIfExistsAsync();
|
||||
|
||||
return new BlobFileAdapter
|
||||
{
|
||||
Name = fileName,
|
||||
Uri = blobClient.Uri.ToString(),
|
||||
ContentType = properties.Value.ContentType,
|
||||
Size = properties.Value.ContentLength,
|
||||
DateUpload = properties.Value.LastModified.UtcDateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
||||
ShortDate = properties.Value.LastModified.UtcDateTime.ToString("MM-dd-yyyy"),
|
||||
Status = _response ? "Deleted" : "Failed to delete"
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a blob's content.
|
||||
/// </summary>
|
||||
public async Task<BlobDownloadInfo> DownloadBlobsAsync(string blobName)
|
||||
{
|
||||
var blobClient = _blobContainerClient.GetBlobClient(blobName);
|
||||
|
||||
if (!await blobClient.ExistsAsync())
|
||||
{
|
||||
throw new FileNotFoundException($"Blob '{blobName}' does not exist in the container '{_containerName}'.");
|
||||
}
|
||||
|
||||
return await blobClient.DownloadAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a secure download URI for a specified blob in the storage container.
|
||||
/// </summary>
|
||||
/// <param name="blobName">The name of the blob for which the download URI is being generated.</param>
|
||||
/// <returns>
|
||||
/// An instance of <see cref="BlobDownloadUriAdapter"/> containing the generated URI, blob name, and status.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The generated URI includes a Shared Access Signature (SAS) token, which allows secure, time-limited access to the blob.
|
||||
/// The SAS token grants read-only access to the blob for a duration of 5 minutes starting from the current time.
|
||||
/// </remarks>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="blobName"/> is null or empty.</exception>
|
||||
/// <exception cref="StorageException">Thrown if there is an issue communicating with the Azure Blob service.</exception>
|
||||
public BlobDownloadUriAdapter GenerateBlobDownloadUri(string blobName)
|
||||
{
|
||||
var delegationKey = _blobServiceClient.GetUserDelegationKey(DateTimeOffset.UtcNow,
|
||||
DateTimeOffset.UtcNow.AddHours(2));
|
||||
|
||||
var blob = _blobContainerClient.GetBlobClient(blobName);
|
||||
|
||||
var sasBuilder = new BlobSasBuilder()
|
||||
{
|
||||
BlobContainerName = blob.BlobContainerName,
|
||||
BlobName = blob.Name,
|
||||
Resource = "b",
|
||||
StartsOn = DateTimeOffset.UtcNow,
|
||||
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5),
|
||||
};
|
||||
sasBuilder.SetPermissions(BlobAccountSasPermissions.Read);
|
||||
sasBuilder.Protocol = SasProtocol.Https;
|
||||
|
||||
var blobUriBuilder = new BlobUriBuilder(blob.Uri)
|
||||
{
|
||||
Sas = sasBuilder.ToSasQueryParameters(delegationKey, _blobServiceClient.AccountName)
|
||||
};
|
||||
|
||||
return new BlobDownloadUriAdapter
|
||||
{
|
||||
Uri = blobUriBuilder.ToUri(),
|
||||
Name = blob.Name,
|
||||
Status = "Available"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the hierarchical folder structure.
|
||||
/// </summary>
|
||||
public async Task<List<BlobStorageFolder>> GetFolderHierarchyAsync(string prefix)
|
||||
{
|
||||
var rootFolder = new BlobStorageFolder { Name = prefix };
|
||||
await PopulateFolderAsync(rootFolder, prefix);
|
||||
return new List<BlobStorageFolder> { rootFolder };
|
||||
}
|
||||
|
||||
private async Task PopulateFolderAsync(BlobStorageFolder folder, string? prefix)
|
||||
{
|
||||
await foreach (var blobHierarchy in _blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: "/"))
|
||||
{
|
||||
if (blobHierarchy.IsPrefix)
|
||||
{
|
||||
var subFolder = new BlobStorageFolder { Name = blobHierarchy.Prefix.TrimEnd('/') };
|
||||
folder.SubFolders.Add(subFolder);
|
||||
await PopulateFolderAsync(subFolder, blobHierarchy.Prefix);
|
||||
}
|
||||
else
|
||||
{
|
||||
folder.Files.Add(new BlobStorageFilesAdapter(Content: blobHierarchy.Prefix, //Fix
|
||||
Name: blobHierarchy.Blob.Name,
|
||||
ContentType: "",
|
||||
DownloadUrl: $"{_blobContainerClient.Uri}/{blobHierarchy.Blob.Name}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
public async Task<Dictionary<string, List<string>>> ListNeighborFoldersAsync(string? prefix)
|
||||
{
|
||||
await ListFoldersInTrieAsync(prefix);
|
||||
|
||||
var groupedFolders = _trie.SearchByPrefix(prefix)
|
||||
.OrderBy(folder => folder)
|
||||
.GroupBy(folder => folder.Substring(0, 1))
|
||||
.ToDictionary(group => group.Key, group => group.ToList());
|
||||
|
||||
return groupedFolders;
|
||||
}
|
||||
private async Task ListFoldersInTrieAsync(string? prefix)
|
||||
{
|
||||
await foreach (var blobHierarchy in _blobContainerClient.GetBlobsByHierarchyAsync(prefix: prefix, delimiter: "/"))
|
||||
{
|
||||
if (blobHierarchy.IsPrefix)
|
||||
{
|
||||
var folderName = blobHierarchy.Prefix.TrimEnd('/').Split('/').Last();
|
||||
_trie.Insert(folderName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user