Compare commits
28 Commits
feat/Infra
...
87e4c1414e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87e4c1414e | ||
|
|
fd8e976e1e | ||
|
|
2c393701e4 | ||
|
|
1d50fd8e5b | ||
|
|
701c34a251 | ||
|
|
63f914d188 | ||
|
|
9ea2599553 | ||
|
|
b8995da5ea | ||
|
|
14013bc7b7 | ||
|
|
f586e9eb2f | ||
|
|
ce786a6d42 | ||
|
|
97695fb0b0 | ||
|
|
cb7b69a0a2 | ||
|
|
b38422256c | ||
|
|
9b29a49ad6 | ||
|
|
83ba492b37 | ||
|
|
50c19fea31 | ||
|
|
f93b197d45 | ||
|
|
eae0d9f913 | ||
|
|
55eb250d7e | ||
|
|
e0c1b856ad | ||
|
|
3a1aeb7ac3 | ||
|
|
246184165f | ||
|
|
9d36ced82f | ||
|
|
c81ff2c628 | ||
|
|
653665bb25 | ||
|
|
17edf605e7 | ||
|
|
b41373339e |
BIN
Assets/core_icon.png
Normal file
BIN
Assets/core_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1020 KiB |
BIN
Assets/core_legacy_icon.png
Normal file
BIN
Assets/core_legacy_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
@@ -1,15 +1,15 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.Abstractions.Application;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace DigitalData.Core.API
|
namespace DigitalData.Core.API
|
||||||
{
|
{
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public class BasicCRUDControllerBase<TCRUDService, TDto, TEntity, TId> : CRUDControllerBase<TCRUDService, TDto, TDto, TDto, TEntity, TId>
|
public class BasicCRUDControllerBase<TCRUDService, TDto, TEntity, TId> : CRUDControllerBase<TCRUDService, TDto, TDto, TDto, TEntity, TId>
|
||||||
where TCRUDService : ICRUDService<TDto, TDto, TEntity, TId>
|
where TCRUDService : ICRUDService<TDto, TDto, TEntity, TId>
|
||||||
where TDto : class, IUnique<TId>
|
where TDto : class
|
||||||
where TEntity : class, IUnique<TId>
|
where TEntity : class
|
||||||
{
|
{
|
||||||
public BasicCRUDControllerBase(ILogger logger, TCRUDService service) : base(logger, service)
|
public BasicCRUDControllerBase(ILogger logger, TCRUDService service) : base(logger, service)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.Abstractions.Application;
|
|
||||||
using DigitalData.Core.DTO;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using DigitalData.Core.Application.DTO;
|
||||||
|
|
||||||
namespace DigitalData.Core.API
|
namespace DigitalData.Core.API
|
||||||
{
|
{
|
||||||
@@ -15,12 +14,13 @@ namespace DigitalData.Core.API
|
|||||||
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public class CRUDControllerBase<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
|
public class CRUDControllerBase<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
|
||||||
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
|
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
|
||||||
where TCreateDto : class
|
where TCreateDto : class
|
||||||
where TReadDto : class
|
where TReadDto : class
|
||||||
where TUpdateDto : class, IUnique<TId>
|
where TUpdateDto : class
|
||||||
where TEntity : class, IUnique<TId>
|
where TEntity : class
|
||||||
{
|
{
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
protected readonly TCRUDService _service;
|
protected readonly TCRUDService _service;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.Abstractions.Application;
|
|
||||||
using DigitalData.Core.DTO;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using DigitalData.Core.Application.DTO;
|
||||||
|
|
||||||
namespace DigitalData.Core.API
|
namespace DigitalData.Core.API
|
||||||
{
|
{
|
||||||
@@ -16,12 +15,13 @@ namespace DigitalData.Core.API
|
|||||||
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public class CRUDControllerBaseWithErrorHandling<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
|
public class CRUDControllerBaseWithErrorHandling<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
|
||||||
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
|
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
|
||||||
where TCreateDto : class
|
where TCreateDto : class
|
||||||
where TReadDto : class
|
where TReadDto : class
|
||||||
where TUpdateDto : class, IUnique<TId>
|
where TUpdateDto : class
|
||||||
where TEntity : class, IUnique<TId>
|
where TEntity : class
|
||||||
{
|
{
|
||||||
protected readonly ILogger _logger;
|
protected readonly ILogger _logger;
|
||||||
protected readonly TCRUDService _service;
|
protected readonly TCRUDService _service;
|
||||||
|
|||||||
@@ -1,47 +1,46 @@
|
|||||||
namespace DigitalData.Core.API
|
namespace DigitalData.Core.API;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Middleware to add Content Security Policy (CSP) headers to the HTTP response.
|
||||||
|
/// </summary>
|
||||||
|
public class CSPMiddleware
|
||||||
{
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly string _policy;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Middleware to add Content Security Policy (CSP) headers to the HTTP response.
|
/// Initializes a new instance of the <see cref="CSPMiddleware"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CSPMiddleware
|
/// <param name="next">The next middleware in the request pipeline.</param>
|
||||||
|
/// <param name="policy">The CSP policy string with placeholders for nonces.</param>
|
||||||
|
public CSPMiddleware(RequestDelegate next, string policy)
|
||||||
{
|
{
|
||||||
private readonly RequestDelegate _next;
|
_next = next;
|
||||||
private readonly string _policy;
|
_policy = policy;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CSPMiddleware"/> class.
|
/// Invokes the middleware to add the CSP header to the response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="next">The next middleware in the request pipeline.</param>
|
/// <param name="context">The HTTP context.</param>
|
||||||
/// <param name="policy">The CSP policy string with placeholders for nonces.</param>
|
/// <returns>A task that represents the completion of request processing.</returns>
|
||||||
public CSPMiddleware(RequestDelegate next, string policy)
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
// Generate a nonce (number used once) for inline scripts and styles
|
||||||
|
var nonce = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
|
||||||
|
|
||||||
|
// Store the nonce in the context items for later use
|
||||||
|
context.Items["csp-nonce"] = nonce;
|
||||||
|
|
||||||
|
// Add the CSP header to the response
|
||||||
|
context.Response.OnStarting(() =>
|
||||||
{
|
{
|
||||||
_next = next;
|
context.Response.Headers.Append("Content-Security-Policy",
|
||||||
_policy = policy;
|
string.Format(_policy, nonce));
|
||||||
}
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
/// <summary>
|
// Call the next middleware in the pipeline
|
||||||
/// Invokes the middleware to add the CSP header to the response.
|
await _next(context);
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The HTTP context.</param>
|
|
||||||
/// <returns>A task that represents the completion of request processing.</returns>
|
|
||||||
public async Task Invoke(HttpContext context)
|
|
||||||
{
|
|
||||||
// Generate a nonce (number used once) for inline scripts and styles
|
|
||||||
var nonce = Convert.ToBase64String(Guid.NewGuid().ToByteArray());
|
|
||||||
|
|
||||||
// Store the nonce in the context items for later use
|
|
||||||
context.Items["csp-nonce"] = nonce;
|
|
||||||
|
|
||||||
// Add the CSP header to the response
|
|
||||||
context.Response.OnStarting(() =>
|
|
||||||
{
|
|
||||||
context.Response.Headers.Add("Content-Security-Policy",
|
|
||||||
string.Format(_policy, nonce));
|
|
||||||
return Task.CompletedTask;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Call the next middleware in the pipeline
|
|
||||||
await _next(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.DTO;
|
using DigitalData.Core.Application.DTO;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,50 @@
|
|||||||
using Microsoft.AspNetCore.Builder;
|
namespace DigitalData.Core.API;
|
||||||
using System.Configuration;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.API
|
/// <summary>
|
||||||
|
/// Provides extension methods for adding middleware to the application's request pipeline.
|
||||||
|
/// </summary>
|
||||||
|
public static class DIExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides extension methods for adding middleware to the application's request pipeline.
|
/// Adds the <see cref="CSPMiddleware"/> to the application's request pipeline to include
|
||||||
|
/// Content Security Policy (CSP) headers in the HTTP response.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DIExtensions
|
/// <param name="app">The application builder.</param>
|
||||||
{
|
/// <param name="policy">
|
||||||
/// <summary>
|
/// The CSP policy string with placeholders. The first format parameter {0} will be replaced
|
||||||
/// Adds the <see cref="CSPMiddleware"/> to the application's request pipeline to include
|
/// by the nonce value.
|
||||||
/// Content Security Policy (CSP) headers in the HTTP response.
|
/// </param>
|
||||||
/// </summary>
|
/// <returns>The application builder with the CSP middleware added.</returns>
|
||||||
/// <param name="app">The application builder.</param>
|
public static IApplicationBuilder UseCSPMiddleware(this IApplicationBuilder app, string policy)
|
||||||
/// <param name="policy">
|
=> app.UseMiddleware<CSPMiddleware>(policy);
|
||||||
/// The CSP policy string with placeholders. The first format parameter {0} will be replaced
|
|
||||||
/// by the nonce value.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>The application builder with the CSP middleware added.</returns>
|
|
||||||
public static IApplicationBuilder UseCSPMiddleware(this IApplicationBuilder app, string policy)
|
|
||||||
=> app.UseMiddleware<CSPMiddleware>(policy);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
|
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">The WebApplicationBuilder instance.</param>
|
/// <param name="builder">The WebApplicationBuilder instance.</param>
|
||||||
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
|
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
|
||||||
public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue<bool>("DiPMode");
|
public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue<bool>("DiPMode");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
|
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="app">The WebApplication instance.</param>
|
/// <param name="app">The WebApplication instance.</param>
|
||||||
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
|
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
|
||||||
public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue<bool>("DiPMode");
|
public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue<bool>("DiPMode");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
|
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">The WebApplicationBuilder instance.</param>
|
/// <param name="builder">The WebApplicationBuilder instance.</param>
|
||||||
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
|
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
|
||||||
public static bool IsDevOrDiP(this WebApplicationBuilder builder) => builder.Environment.IsDevelopment() || builder.IsDiP();
|
public static bool IsDevOrDiP(this WebApplicationBuilder builder) => builder.Environment.IsDevelopment() || builder.IsDiP();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
|
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="app">The WebApplication instance.</param>
|
/// <param name="app">The WebApplication instance.</param>
|
||||||
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
|
/// <returns>True if the environment is Development or DiP mode is enabled; otherwise, false.</returns>
|
||||||
public static bool IsDevOrDiP(this WebApplication app) => app.Environment.IsDevelopment() || app.IsDiP();
|
public static bool IsDevOrDiP(this WebApplication app) => app.Environment.IsDevelopment() || app.IsDiP();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Configures the services with options from the specified section of the appsettings.json file.
|
/// Configures the services with options from the specified section of the appsettings.json file.
|
||||||
@@ -66,4 +63,3 @@ namespace DigitalData.Core.API
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@@ -32,8 +32,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
|
||||||
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
|
<ProjectReference Include="..\DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using DigitalData.Core.Abstractions.Application;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.DTO;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using DigitalData.Core.Application.DTO;
|
||||||
|
|
||||||
namespace DigitalData.Core.API
|
namespace DigitalData.Core.API
|
||||||
{
|
{
|
||||||
@@ -12,6 +12,7 @@ namespace DigitalData.Core.API
|
|||||||
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public class ReadControllerBase<TReadService, TReadDto, TEntity, TId> : ControllerBase
|
public class ReadControllerBase<TReadService, TReadDto, TEntity, TId> : ControllerBase
|
||||||
where TReadService : IReadService<TReadDto, TEntity, TId>
|
where TReadService : IReadService<TReadDto, TEntity, TId>
|
||||||
where TReadDto : class
|
where TReadDto : class
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using DigitalData.Core.Abstractions.Application;
|
using DigitalData.Core.Application.DTO;
|
||||||
using DigitalData.Core.DTO;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace DigitalData.Core.API
|
namespace DigitalData.Core.API
|
||||||
@@ -13,6 +13,7 @@ namespace DigitalData.Core.API
|
|||||||
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public class ReadControllerBaseWithErrorHandling<TReadService, TReadDto, TEntity, TId> : ControllerBase
|
public class ReadControllerBaseWithErrorHandling<TReadService, TReadDto, TEntity, TId> : ControllerBase
|
||||||
where TReadService : IReadService<TReadDto, TEntity, TId>
|
where TReadService : IReadService<TReadDto, TEntity, TId>
|
||||||
where TReadDto : class
|
where TReadDto : class
|
||||||
|
|||||||
30
DigitalData.Core.Abstractions/ConfigurationExtension.cs
Normal file
30
DigitalData.Core.Abstractions/ConfigurationExtension.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Abstractions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for the <see cref="IConfiguration"/> interface, providing
|
||||||
|
/// additional functionality for retrieving configuration values with default behavior.
|
||||||
|
/// </summary>
|
||||||
|
public static class ConfigurationExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a configuration value for the specified key, or returns a default value
|
||||||
|
/// of type <typeparamref name="T"/> if the configuration is not found or the key is null.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the object to retrieve from the configuration.</typeparam>
|
||||||
|
/// <param name="configuration">The <see cref="IConfiguration"/> instance.</param>
|
||||||
|
/// <param name="key">The optional key to look for in the configuration. If null, the method
|
||||||
|
/// retrieves the root configuration.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// An instance of <typeparamref name="T"/> populated from the configuration values, or
|
||||||
|
/// a new instance of <typeparamref name="T"/> if no matching configuration is found.
|
||||||
|
/// </returns>
|
||||||
|
public static T GetOrDefault<T>(this IConfiguration configuration, string? key = null)
|
||||||
|
where T : new()
|
||||||
|
=> (key is null
|
||||||
|
? configuration.Get<T>()
|
||||||
|
: configuration.GetSection(key).Get<T>())
|
||||||
|
?? new T();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
DigitalData.Core.Abstractions/DeferredServiceProvider.cs
Normal file
40
DigitalData.Core.Abstractions/DeferredServiceProvider.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
namespace DigitalData.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A deferred implementation of <see cref="IServiceProvider"/> that allows the <see cref="IServiceProvider"/> instance
|
||||||
|
/// to be provided at a later time via a factory callback.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is particularly useful when service registration requires an <see cref="IServiceProvider"/> instance,
|
||||||
|
/// but the service provider is not yet available at registration time.
|
||||||
|
/// By using <see cref="DeferredServiceProvider"/>, a service can safely resolve dependencies once
|
||||||
|
/// the application's service provider has been built, ensuring a single consistent scope.
|
||||||
|
/// </remarks>
|
||||||
|
public class DeferredServiceProvider : IServiceProvider
|
||||||
|
{
|
||||||
|
private Lazy<IServiceProvider>? _serviceProvider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the factory that will be used to lazily initialize the <see cref="IServiceProvider"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
public Func<IServiceProvider> Factory
|
||||||
|
{
|
||||||
|
set => _serviceProvider = new(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the requested service object from the deferred <see cref="IServiceProvider"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serviceType">The type of service object to retrieve.</param>
|
||||||
|
/// <returns>The requested service object, or <c>null</c> if the service is not available.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Thrown if the service provider factory has not been set before calling <see cref="GetService"/>.
|
||||||
|
/// </exception>
|
||||||
|
public object? GetService(Type serviceType)
|
||||||
|
{
|
||||||
|
if (_serviceProvider is null)
|
||||||
|
throw new InvalidOperationException("The service provider has not been initialized. Make sure 'Factory' is set before calling 'GetService'.");
|
||||||
|
|
||||||
|
return _serviceProvider.Value.GetService(serviceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||||
<PackAsTool>False</PackAsTool>
|
<PackAsTool>False</PackAsTool>
|
||||||
<PackageIcon>core_icon.png</PackageIcon>
|
<PackageIcon>core_icon.png</PackageIcon>
|
||||||
<Version>3.4.3</Version>
|
<Version>3.6.0</Version>
|
||||||
<AssemblyVersion>3.4.3</AssemblyVersion>
|
<AssemblyVersion>3.6.0</AssemblyVersion>
|
||||||
<FileVersion>3.4.3</FileVersion>
|
<FileVersion>3.6.0</FileVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -37,8 +37,4 @@
|
|||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace DigitalData.Core.Abstractions
|
|
||||||
{
|
|
||||||
public interface IUnique<T>
|
|
||||||
{
|
|
||||||
public T Id { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
31
DigitalData.Core.Abstractions/ServiceProviderExtensions.cs
Normal file
31
DigitalData.Core.Abstractions/ServiceProviderExtensions.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Abstractions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="IServiceProvider"/> to retrieve configuration options.
|
||||||
|
/// </summary>
|
||||||
|
public static class ServiceProviderExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves an instance of <typeparamref name="TOptions"/> from the <see cref="IServiceProvider"/>.
|
||||||
|
/// If the options are not registered, returns null.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TOptions">The type of the options to retrieve.</typeparam>
|
||||||
|
/// <param name="service">The service provider instance.</param>
|
||||||
|
/// <returns>An instance of <typeparamref name="TOptions"/> or null if not found.</returns>
|
||||||
|
public static TOptions? GetOptions<TOptions>(this IServiceProvider service) where TOptions : class
|
||||||
|
=> service.GetService<IOptions<TOptions>>()?.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves an instance of <typeparamref name="TOptions"/> from the <see cref="IServiceProvider"/>.
|
||||||
|
/// Throws an exception if the options are not registered.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TOptions">The type of the options to retrieve.</typeparam>
|
||||||
|
/// <param name="service">The service provider instance.</param>
|
||||||
|
/// <returns>An instance of <typeparamref name="TOptions"/>.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when the options are not registered.</exception>
|
||||||
|
public static TOptions GetRequiredOptions<TOptions>(this IServiceProvider service) where TOptions : class
|
||||||
|
=> service.GetRequiredService<IOptions<TOptions>>().Value;
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
namespace DigitalData.Core.Abstractions
|
|
||||||
{
|
|
||||||
public static class ServiceResultExtensions
|
|
||||||
{
|
|
||||||
public static bool Try<T>(this T? nullableResult, out T result)
|
|
||||||
{
|
|
||||||
#pragma warning disable CS8601 // Possible null reference assignment.
|
|
||||||
result = nullableResult;
|
|
||||||
#pragma warning restore CS8601 // Possible null reference assignment.
|
|
||||||
return nullableResult is not null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DigitalData.Core.Abstractions;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.Abstractions.Application;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Application
|
namespace DigitalData.Core.Application
|
||||||
{
|
{
|
||||||
@@ -18,9 +17,10 @@ namespace DigitalData.Core.Application
|
|||||||
/// reducing the need for multiple DTOs and simplifying the data mapping process. It leverages AutoMapper for object mapping
|
/// reducing the need for multiple DTOs and simplifying the data mapping process. It leverages AutoMapper for object mapping
|
||||||
/// and a culture-specific translation service for any necessary text translations, ensuring a versatile and internationalized approach to CRUD operations.
|
/// and a culture-specific translation service for any necessary text translations, ensuring a versatile and internationalized approach to CRUD operations.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public class BasicCRUDService<TCRUDRepository, TDto, TEntity, TId> :
|
public class BasicCRUDService<TCRUDRepository, TDto, TEntity, TId> :
|
||||||
CRUDService<TCRUDRepository, TDto, TDto, TEntity, TId>, IBasicCRUDService<TDto, TEntity, TId>
|
CRUDService<TCRUDRepository, TDto, TDto, TEntity, TId>, IBasicCRUDService<TDto, TEntity, TId>
|
||||||
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId>
|
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TDto : class where TEntity : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the BasicCRUDService with the specified repository, translation service, and AutoMapper configuration.
|
/// Initializes a new instance of the BasicCRUDService with the specified repository, translation service, and AutoMapper configuration.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
using DigitalData.Core.Abstractions.Application;
|
using AutoMapper;
|
||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
|
||||||
using AutoMapper;
|
|
||||||
using DigitalData.Core.DTO;
|
|
||||||
using DigitalData.Core.Abstractions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
|
using DigitalData.Core.Application.Interfaces;
|
||||||
|
using DigitalData.Core.Application.DTO;
|
||||||
|
|
||||||
namespace DigitalData.Core.Application
|
namespace DigitalData.Core.Application
|
||||||
{
|
{
|
||||||
@@ -14,8 +13,10 @@ namespace DigitalData.Core.Application
|
|||||||
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
|
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
|
||||||
/// <typeparam name="TEntity">The entity type.</typeparam>
|
/// <typeparam name="TEntity">The entity type.</typeparam>
|
||||||
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
||||||
|
///
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TEntity, TId> : ReadService<TCRUDRepository, TReadDto, TEntity, TId>, ICRUDService<TCreateDto, TReadDto, TEntity, TId>
|
public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TEntity, TId> : ReadService<TCRUDRepository, TReadDto, TEntity, TId>, ICRUDService<TCreateDto, TReadDto, TEntity, TId>
|
||||||
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
|
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TEntity : class
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,7 +37,7 @@ namespace DigitalData.Core.Application
|
|||||||
{
|
{
|
||||||
var entity = _mapper.Map<TEntity>(createDto);
|
var entity = _mapper.Map<TEntity>(createDto);
|
||||||
var createdEntity = await _repository.CreateAsync(entity);
|
var createdEntity = await _repository.CreateAsync(entity);
|
||||||
return createdEntity is null ? Result.Fail<TId>() : Result.Success(createdEntity.Id);
|
return createdEntity is null ? Result.Fail<TId>() : Result.Success(createdEntity.GetIdOrDefault<TId>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,12 +45,12 @@ namespace DigitalData.Core.Application
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="updateDto">The DTO to update an entity from.</param>
|
/// <param name="updateDto">The DTO to update an entity from.</param>
|
||||||
/// <returns>A service message indicating success or failure.</returns>
|
/// <returns>A service message indicating success or failure.</returns>
|
||||||
public virtual async Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>
|
public virtual async Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto)
|
||||||
{
|
{
|
||||||
var currentEntitiy = await _repository.ReadByIdAsync(updateDto.Id);
|
var currentEntitiy = await _repository.ReadByIdAsync(updateDto.GetIdOrDefault<TId>());
|
||||||
|
|
||||||
if (currentEntitiy is null)
|
if (currentEntitiy is null)
|
||||||
return Result.Fail().Notice(LogLevel.Warning, Flag.NotFound, $"{updateDto.Id} is not found in update process of {GetType()} entity.");
|
return Result.Fail().Notice(LogLevel.Warning, Flag.NotFound, $"{updateDto.GetIdOrDefault<TId>()} is not found in update process of {GetType()} entity.");
|
||||||
|
|
||||||
var entity = _mapper.Map(updateDto, currentEntitiy);
|
var entity = _mapper.Map(updateDto, currentEntitiy);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Application;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|||||||
17
DigitalData.Core.Application/DTO/BaseDto.cs
Normal file
17
DigitalData.Core.Application/DTO/BaseDto.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a base Data Transfer Object (DTO) with an identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TId">The type of the identifier.</typeparam>
|
||||||
|
/// <param name="Id">The identifier of the DTO.</param>
|
||||||
|
public record BaseDTO<TId>(TId Id) where TId : notnull
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the hash code for this instance, based on the identifier.
|
||||||
|
/// This override ensures that the hash code is derived consistently from the identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A hash code for the current object, derived from the identifier.</returns>
|
||||||
|
public override int GetHashCode() => Id.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
74
DigitalData.Core.Application/DTO/CookieConsentSettings.cs
Normal file
74
DigitalData.Core.Application/DTO/CookieConsentSettings.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents settings related to user cookie consent dialogs. Designed to be serialized into JSON format for use with JavaScript frontend libraries,
|
||||||
|
/// such as the bootstrap-cookie-consent-settings at the GitHub repository: https://github.com/shaack/bootstrap-cookie-consent-settings
|
||||||
|
/// </summary>
|
||||||
|
public class CookieConsentSettings
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// URL to the privacy policy page.
|
||||||
|
/// </summary>
|
||||||
|
public string? PrivacyPolicyUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL to the legal notice page.
|
||||||
|
/// </summary>
|
||||||
|
public string? LegalNoticeUrl { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// URL to the content of the dialog box.
|
||||||
|
/// </summary>
|
||||||
|
public string? ContentURL { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSS class for the 'Agree' button.
|
||||||
|
/// </summary>
|
||||||
|
public string? ButtonAgreeClass { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSS class for the 'Don't Agree' button.
|
||||||
|
/// </summary>
|
||||||
|
public string? ButtonDontAgreeClass { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CSS class for the 'Save' button.
|
||||||
|
/// </summary>
|
||||||
|
public string? ButtonSaveClass { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Language in which the modal is displayed.
|
||||||
|
/// </summary>
|
||||||
|
public string? Lang { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default language for the modal if the user's browser language is not supported.
|
||||||
|
/// </summary>
|
||||||
|
public string? DefaultLang { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the cookie used to store the consent status.
|
||||||
|
/// </summary>
|
||||||
|
public string? CookieName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of days the cookie will be stored.
|
||||||
|
/// </summary>
|
||||||
|
public int CookieStorageDays { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Identifier for the modal dialog element.
|
||||||
|
/// </summary>
|
||||||
|
public string? ModalId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether to also store the settings in the browser's localStorage.
|
||||||
|
/// </summary>
|
||||||
|
public bool AlsoUseLocalStorage { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of categories for cookie consent.
|
||||||
|
/// </summary>
|
||||||
|
public List<string>? Categories { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
33
DigitalData.Core.Application/DTO/DIExtensions.cs
Normal file
33
DigitalData.Core.Application/DTO/DIExtensions.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Configuration;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for dependency injection.
|
||||||
|
/// </summary>
|
||||||
|
public static class DIExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the <see cref="CookieConsentSettings"/> to the service collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The service collection to add the settings to.</param>
|
||||||
|
/// <returns>The updated service collection.</returns>
|
||||||
|
/// <exception cref="ConfigurationErrorsException">
|
||||||
|
/// Thrown if the 'CookieConsentSettings' section is missing or improperly configured in appsettings.json.
|
||||||
|
/// </exception>
|
||||||
|
public static IServiceCollection AddCookieConsentSettings(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddSingleton(sp =>
|
||||||
|
{
|
||||||
|
var configuration = sp.GetRequiredService<IConfiguration>();
|
||||||
|
var settings = configuration.GetSection("CookieConsentSettings").Get<CookieConsentSettings>();
|
||||||
|
return settings is null
|
||||||
|
? throw new ConfigurationErrorsException("The 'CookieConsentSettings' section is missing or improperly configured in appsettings.json.")
|
||||||
|
: settings;
|
||||||
|
});
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
367
DigitalData.Core.Application/DTO/DTOExtensions.cs
Normal file
367
DigitalData.Core.Application/DTO/DTOExtensions.cs
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for data transfer objects (DTOs).
|
||||||
|
/// </summary>
|
||||||
|
public static class DTOExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a single message to the result, if not null.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the message to.</param>
|
||||||
|
/// <param name="message">The message to add.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Message<T>(this T result, string? message) where T : Result
|
||||||
|
{
|
||||||
|
if(message is not null)
|
||||||
|
result.Messages.Add(message);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IEnumerable<T> FilterNull<T>(this IEnumerable<T?> list)
|
||||||
|
{
|
||||||
|
foreach (var item in list)
|
||||||
|
if(item is not null)
|
||||||
|
yield return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds multiple messages to the result, after removing nulls.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the messages to.</param>
|
||||||
|
/// <param name="messages">The messages to add.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Message<T>(this T result, params string?[] messages) where T : Result
|
||||||
|
{
|
||||||
|
result.Messages.AddRange(messages.FilterNull());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a collection of messages to the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the messages to.</param>
|
||||||
|
/// <param name="messages">The collection of messages to add.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Message<T>(this T result, IEnumerable<string?> messages) where T : Result
|
||||||
|
{
|
||||||
|
result.Messages.AddRange(messages.FilterNull());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a notice to the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the notice to.</param>
|
||||||
|
/// <param name="notice">The notice to add.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Notice<T>(this T result, Notice notice) where T : Result
|
||||||
|
{
|
||||||
|
result.Notices.Add(notice);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a collection of notices to the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the notices to.</param>
|
||||||
|
/// <param name="notices">The collection of notices to add.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Notice<T>(this T result, IEnumerable<Notice> notices) where T : Result
|
||||||
|
{
|
||||||
|
result.Notices.AddRange(notices);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds notices with a specific log level and flags to the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the notices to.</param>
|
||||||
|
/// <param name="level">The log level of the notices.</param>
|
||||||
|
/// <param name="flags">The flags associated with the notices.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Notice<T>(this T result, LogLevel level, params Enum[] flags) where T : Result
|
||||||
|
{
|
||||||
|
var notices = flags.Select(flag => new Notice()
|
||||||
|
{
|
||||||
|
Flag = flag,
|
||||||
|
Level = level
|
||||||
|
});
|
||||||
|
result.Notices.AddRange(notices);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a notice with a specific log level, flag, and messages to the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the notice to.</param>
|
||||||
|
/// <param name="level">The log level of the notice.</param>
|
||||||
|
/// <param name="flag">The flag associated with the notice.</param>
|
||||||
|
/// <param name="messages">The messages to add to the notice.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Notice<T>(this T result, LogLevel level, Enum flag, params string?[] messages) where T : Result
|
||||||
|
{
|
||||||
|
result.Notices.Add(new Notice()
|
||||||
|
{
|
||||||
|
Flag = flag,
|
||||||
|
Level = level,
|
||||||
|
Messages = messages.FilterNull().ToList()
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a notice with a specific log level and messages to the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the result.</typeparam>
|
||||||
|
/// <param name="result">The result to add the notice to.</param>
|
||||||
|
/// <param name="level">The log level of the notice.</param>
|
||||||
|
/// <param name="messages">The messages to add to the notice.</param>
|
||||||
|
/// <returns>The updated result.</returns>
|
||||||
|
public static T Notice<T>(this T result, LogLevel level, params string[] messages) where T : Result
|
||||||
|
{
|
||||||
|
result.Notices.Add(new Notice()
|
||||||
|
{
|
||||||
|
Flag = null,
|
||||||
|
Level = level,
|
||||||
|
Messages = messages.FilterNull().ToList()
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any notice has the specified flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="notices">The collection of notices to check.</param>
|
||||||
|
/// <param name="flag">The flag to check for.</param>
|
||||||
|
/// <returns>True if any notice has the specified flag; otherwise, false.</returns>
|
||||||
|
public static bool HasFlag(this IEnumerable<Notice> notices, Enum flag) => notices.Any(n => n.Flag?.ToString() == flag.ToString());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any notice has any of the specified flags.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="notices">The collection of notices to check.</param>
|
||||||
|
/// <param name="flags">The flags to check for.</param>
|
||||||
|
/// <returns>True if any notice has any of the specified flags; otherwise, false.</returns>
|
||||||
|
public static bool HasAnyFlag(this IEnumerable<Notice> notices, params Enum[] flags) => flags.Any(f => notices.HasFlag(f));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a function based on the success or failure of the task result,
|
||||||
|
/// without using result data.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="tResult">The task returning a result to evaluate.</param>
|
||||||
|
/// <param name="Success">The function to execute if the result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static I? Then<I>(this Result result, Func<I> Success)
|
||||||
|
{
|
||||||
|
return result.IsSuccess ? Success() : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a function based on the success or failure of the task result,
|
||||||
|
/// using the data in the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data in the result.</typeparam>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="tResult">The task returning a data result to evaluate.</param>
|
||||||
|
/// <param name="Success">The function to execute if the data result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the data result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static async Task<I?> ThenAsync<I>(this Result result, Func<Task<I>> SuccessAsync)
|
||||||
|
{
|
||||||
|
return result.IsSuccess ? await SuccessAsync() : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a function based on the success or failure of the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="result">The result to evaluate.</param>
|
||||||
|
/// <param name="Success">The function to execute if the result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static I Then<I>(this Result result, Func<I> Success, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
return result.IsSuccess ? Success() : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes a function based on the success or failure of the result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="result">The result to evaluate.</param>
|
||||||
|
/// <param name="SuccessAsync">The asynchronous function to execute if the result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static async Task<I> ThenAsync<I>(this Result result, Func<Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
return result.IsSuccess ? await SuccessAsync() : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Executes a function based on the success or failure of the data result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data in the result.</typeparam>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="result">The data result to evaluate.</param>
|
||||||
|
/// <param name="Success">The function to execute if the data result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the data result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static I Then<T, I>(this DataResult<T> result, Func<T, I> Success, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
return result.IsSuccess ? Success(result.Data) : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes a function based on the success or failure of the data result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data in the result.</typeparam>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="result">The data result to evaluate.</param>
|
||||||
|
/// <param name="SuccessAsync">The asynchronous function to execute if the data result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the data result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static async Task<I> ThenAsync<T, I>(this DataResult<T> result, Func<T, Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
return result.IsSuccess ? await SuccessAsync(result.Data) : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes a function based on the success or failure of a task returning a result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="tResult">The task returning a result to evaluate.</param>
|
||||||
|
/// <param name="Success">The function to execute if the result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static async Task<I> ThenAsync<I>(this Task<Result> tResult, Func<I> Success, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
Result result = await tResult;
|
||||||
|
return result.IsSuccess ? Success() : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes a function based on the success or failure of a task returning a result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="tResult">The task returning a result to evaluate.</param>
|
||||||
|
/// <param name="SuccessAsync">The asynchronous function to execute if the result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static async Task<I> ThenAsync<I>(this Task<Result> tResult, Func<Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
Result result = await tResult;
|
||||||
|
return result.IsSuccess ? await SuccessAsync() : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes a function based on the success or failure of a task returning a data result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data in the result.</typeparam>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="tResult">The task returning a data result to evaluate.</param>
|
||||||
|
/// <param name="Success">The function to execute if the data result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the data result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static async Task<I> ThenAsync<T, I>(this Task<DataResult<T>> tResult, Func<T, I> Success, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
DataResult<T> result = await tResult;
|
||||||
|
return result.IsSuccess ? Success(result.Data) : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously executes a function based on the success or failure of a task returning a data result.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data in the result.</typeparam>
|
||||||
|
/// <typeparam name="I">The type of the return value.</typeparam>
|
||||||
|
/// <param name="tResult">The task returning a data result to evaluate.</param>
|
||||||
|
/// <param name="SuccessAsync">The asynchronous function to execute if the data result is successful.</param>
|
||||||
|
/// <param name="Fail">The function to execute if the data result is a failure.</param>
|
||||||
|
/// <returns>The result of the executed function.</returns>
|
||||||
|
public static async Task<I> ThenAsync<T, I>(this Task<DataResult<T>> tResult, Func<T, Task<I>> SuccessAsync, Func<List<string>, List<Notice>, I> Fail)
|
||||||
|
{
|
||||||
|
DataResult<T> result = await tResult;
|
||||||
|
return result.IsSuccess ? await SuccessAsync(result.Data) : Fail(result.Messages, result.Notices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Joins the values into a single string with optional start, separator, and end strings.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the values.</typeparam>
|
||||||
|
/// <param name="values">The values to join.</param>
|
||||||
|
/// <param name="start">The starting string.</param>
|
||||||
|
/// <param name="separator">The separator string.</param>
|
||||||
|
/// <param name="end">The ending string.</param>
|
||||||
|
/// <returns>The joined string.</returns>
|
||||||
|
public static string Join<T>(this IEnumerable<T> values, string start = "", string seperator = ". ", string end = ".")
|
||||||
|
=> new StringBuilder(start).Append(string.Join(seperator, values)).Append(end).ToString();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the notices using the specified logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger to use.</param>
|
||||||
|
/// <param name="notices">The collection of notices to log.</param>
|
||||||
|
/// <param name="start">The starting string for each notice.</param>
|
||||||
|
/// <param name="separator">The separator string for messages in each notice.</param>
|
||||||
|
/// <param name="end">The ending string for each notice.</param>
|
||||||
|
public static void LogNotice(this ILogger logger, IEnumerable<Notice> notices, string start = ": ", string seperator = ". ", string end = ".\n")
|
||||||
|
{
|
||||||
|
foreach(LogLevel level in Enum.GetValues(typeof(LogLevel)))
|
||||||
|
{
|
||||||
|
var logNotices = notices.Where(n => n.Level == level);
|
||||||
|
|
||||||
|
if (!logNotices.Any())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach(Notice notice in logNotices)
|
||||||
|
{
|
||||||
|
if (notice.Flag is not null)
|
||||||
|
sb.Append(notice.Flag);
|
||||||
|
|
||||||
|
if (notice.Messages.Any())
|
||||||
|
sb.Append(start).Append(string.Join(seperator, notice.Messages)).AppendLine(end);
|
||||||
|
else sb.Append(end);
|
||||||
|
}
|
||||||
|
logger.Log(level, sb.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the notices from a result using the specified logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">The logger to use.</param>
|
||||||
|
/// <param name="result">The result containing the notices to log.</param>
|
||||||
|
/// <param name="start">The starting string for each notice.</param>
|
||||||
|
/// <param name="separator">The separator string for messages in each notice.</param>
|
||||||
|
/// <param name="end">The ending string for each notice.</param>
|
||||||
|
public static void LogNotice(this ILogger logger, Result result, string start = ": ", string seperator = ". ", string end = ".\n")
|
||||||
|
=> logger.LogNotice(notices: result.Notices, start: start, seperator: seperator, end: end);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the data result is right (true).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bResult">The data result to evaluate.</param>
|
||||||
|
/// <returns>True if the data result is true; otherwise, false.</returns>
|
||||||
|
public static bool IsRight(this DataResult<bool> bResult) => bResult.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if the data result is wrong (false).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bResult">The data result to evaluate.</param>
|
||||||
|
/// <returns>True if the data result is false; otherwise, false.</returns>
|
||||||
|
public static bool IsWrong(this DataResult<bool> bResult) => !bResult.Data;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
DigitalData.Core.Application/DTO/DataResult.cs
Normal file
26
DigitalData.Core.Application/DTO/DataResult.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a result of an operation that includes data, inheriting from <see cref="Result"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data included in the result.</typeparam>
|
||||||
|
public class DataResult<T> : Result
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the data included in the result. This property is required.
|
||||||
|
/// It will be ignored during JSON serialization if the value is null.
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public required T Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the current <see cref="DataResult{T}"/> to a failed <see cref="DataResult{I}"/>,
|
||||||
|
/// preserving the messages and notices.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="I">The type of the data in the new failed result.</typeparam>
|
||||||
|
/// <returns>A failed <see cref="DataResult{I}"/> with the current messages and notices.</returns>
|
||||||
|
public DataResult<I> ToFail<I>() => Fail<I>().Message(Messages).Notice(Notices);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
DigitalData.Core.Application/DTO/Flag.cs
Normal file
50
DigitalData.Core.Application/DTO/Flag.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines flags that indicate specific types of status or conditions in a service operation.
|
||||||
|
/// These flags help in categorizing and identifying specific circumstances or issues that may arise during execution.
|
||||||
|
/// </summary>
|
||||||
|
public enum Flag
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates a security breach or vulnerability has been detected during the service operation.
|
||||||
|
/// </summary>
|
||||||
|
SecurityBreach,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates a potential issue with data integrity during the service operation.
|
||||||
|
/// This flag is used when data may have been altered, corrupted, or is otherwise unreliable,
|
||||||
|
/// which could impact the accuracy or trustworthiness of the operation's results.
|
||||||
|
/// </summary>
|
||||||
|
DataIntegrityIssue,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that either a security breach, a data integrity issue, or both have been detected during the service operation.
|
||||||
|
/// This flag is used when it is not sure whether the problem is security or data integrity. In this case, data integrity should be checked first.
|
||||||
|
/// </summary>
|
||||||
|
SecurityBreachOrDataIntegrity,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates a possible security breach during the service operation.
|
||||||
|
/// </summary>
|
||||||
|
PossibleSecurityBreach,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates a possible issue with data integrity during the service operation.
|
||||||
|
/// This flag is used when there is a suspicion of data alteration, corruption, or unreliability.
|
||||||
|
/// </summary>
|
||||||
|
PossibleDataIntegrityIssue,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that either a possible security breach, a possible data integrity issue, or both have been detected during the service operation.
|
||||||
|
/// This flag is used when it is uncertain whether the issue is related to security, data integrity, or both.
|
||||||
|
/// </summary>
|
||||||
|
PossibleSecurityBreachOrDataIntegrity,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the requested resource or operation could not be found.
|
||||||
|
/// This flag is used when the specified item or condition does not exist or is unavailable.
|
||||||
|
/// </summary>
|
||||||
|
NotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
25
DigitalData.Core.Application/DTO/Notice.cs
Normal file
25
DigitalData.Core.Application/DTO/Notice.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a notice for logging purposes, containing a flag, log level, and associated messages.
|
||||||
|
/// </summary>
|
||||||
|
public class Notice
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets an optional flag associated with the notice.
|
||||||
|
/// </summary>
|
||||||
|
public Enum? Flag { get; init; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the log level for the notice.
|
||||||
|
/// </summary>
|
||||||
|
public LogLevel Level { get; init; } = LogLevel.None;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of messages associated with the notice.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Messages { get; init; } = new();
|
||||||
|
}
|
||||||
|
}
|
||||||
97
DigitalData.Core.Application/DTO/Result.cs
Normal file
97
DigitalData.Core.Application/DTO/Result.cs
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Application.DTO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the result of an operation, containing information about its success or failure,
|
||||||
|
/// messages for the client, and notices for logging.
|
||||||
|
/// </summary>
|
||||||
|
public class Result
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether the operation was successful.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSuccess { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether the operation failed.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFailed => !IsSuccess;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of messages intended for the client.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Messages { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a list of notices intended for logging purposes. This property is ignored during JSON serialization.
|
||||||
|
/// </summary>
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<Notice> Notices = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a <see cref="DataResult{T}"/> with the specified data.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data.</typeparam>
|
||||||
|
/// <param name="data">The data to include in the result.</param>
|
||||||
|
/// <returns>A new <see cref="DataResult{T}"/> instance.</returns>
|
||||||
|
public DataResult<T> Data<T>(T data) => new()
|
||||||
|
{
|
||||||
|
IsSuccess = IsSuccess,
|
||||||
|
Messages = Messages,
|
||||||
|
Notices = Notices,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any notice has the specified flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flag">The flag to check.</param>
|
||||||
|
/// <returns>True if any notice has the specified flag; otherwise, false.</returns>
|
||||||
|
public bool HasFlag(Enum flag) => Notices.Any(n => n.Flag?.ToString() == flag.ToString());
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any notice has any of the specified flags.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="flags">The flags to check.</param>
|
||||||
|
/// <returns>True if any notice has any of the specified flags; otherwise, false.</returns>
|
||||||
|
public bool HasAnyFlag(params Enum[] flags) => flags.Any(HasFlag);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new successful <see cref="Result"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new successful <see cref="Result"/>.</returns>
|
||||||
|
public static Result Success() => new() { IsSuccess = true };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new failed <see cref="Result"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A new failed <see cref="Result"/>.</returns>
|
||||||
|
public static Result Fail() => new() { IsSuccess = false };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new successful <see cref="DataResult{T}"/> with the specified data.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data.</typeparam>
|
||||||
|
/// <param name="data">The data to include in the result.</param>
|
||||||
|
/// <returns>A new successful <see cref="DataResult{T}"/> with the specified data.</returns>
|
||||||
|
public static DataResult<T> Success<T>(T data) => new()
|
||||||
|
{
|
||||||
|
IsSuccess = true,
|
||||||
|
Data = data
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new failed <see cref="DataResult{T}"/> with no data.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the data.</typeparam>
|
||||||
|
/// <returns>A new failed <see cref="DataResult{T}"/> with no data.</returns>
|
||||||
|
#pragma warning disable CS8601 // Possible null reference assignment.
|
||||||
|
public static DataResult<T> Fail<T>() => new()
|
||||||
|
{
|
||||||
|
IsSuccess = false,
|
||||||
|
Data = default
|
||||||
|
};
|
||||||
|
#pragma warning restore CS8601 // Possible null reference assignment.
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
|
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||||
@@ -37,20 +36,22 @@
|
|||||||
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||||
</ItemGroup>
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||||
</ItemGroup>
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||||
</ItemGroup>
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using DigitalData.Core.Abstractions.Application;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.DirectoryServices;
|
using System.DirectoryServices;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using System.DirectoryServices.AccountManagement;
|
using System.DirectoryServices.AccountManagement;
|
||||||
using DigitalData.Core.DTO;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using DigitalData.Core.Application.Interfaces;
|
||||||
|
using DigitalData.Core.Application.DTO;
|
||||||
|
|
||||||
namespace DigitalData.Core.Application
|
namespace DigitalData.Core.Application
|
||||||
{
|
{
|
||||||
|
|||||||
94
DigitalData.Core.Application/EntityExtensions.cs
Normal file
94
DigitalData.Core.Application/EntityExtensions.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
namespace DigitalData.Core.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides extension methods for retrieving the value of an 'Id' property from objects.
|
||||||
|
/// </summary>
|
||||||
|
public static class EntityExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the value of the 'Id' property from the specified object.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
|
||||||
|
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The value of the 'Id' property if it exists and is of type <typeparamref name="TId"/>; otherwise, <c>default</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static TId? GetIdOrDefault<TId>(this object? obj)
|
||||||
|
{
|
||||||
|
var prop = obj?.GetType().GetProperty("Id");
|
||||||
|
return prop is not null && prop.GetValue(obj) is TId id ? id : default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of the 'Id' property from the specified object, or throws an exception if not found or of the wrong type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
|
||||||
|
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
|
||||||
|
/// <returns>The value of the 'Id' property.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Thrown if the object does not have a readable 'Id' property of type <typeparamref name="TId"/>.
|
||||||
|
/// </exception>
|
||||||
|
public static TId? GetId<TId>(this object? obj)
|
||||||
|
=> obj.GetIdOrDefault<TId>()
|
||||||
|
?? throw new InvalidOperationException($"The object of type '{obj?.GetType().FullName ?? "null"}' does not have a readable 'Id' property of type '{typeof(TId).FullName}'.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to retrieve the value of the 'Id' property from the specified object.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TId">The expected type of the 'Id' property.</typeparam>
|
||||||
|
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
|
||||||
|
/// <param name="id">When this method returns, contains the value of the 'Id' property if found; otherwise, the default value for the type.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if the 'Id' property was found and is of type <typeparamref name="TId"/>; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static bool TryGetId<TId>(object? obj, out TId id)
|
||||||
|
{
|
||||||
|
#pragma warning disable CS8601
|
||||||
|
id = obj.GetIdOrDefault<TId>();
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
return id is not null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the value of the 'Id' property from the specified object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// The value of the 'Id' property if it exists; otherwise, <c>null</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static object? GetIdOrDefault(this object? obj)
|
||||||
|
{
|
||||||
|
var prop = obj?.GetType().GetProperty("Id");
|
||||||
|
return prop?.GetValue(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the value of the 'Id' property from the specified object, or throws an exception if not found.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
|
||||||
|
/// <returns>The value of the 'Id' property.</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">
|
||||||
|
/// Thrown if the object does not have a readable 'Id' property.
|
||||||
|
/// </exception>
|
||||||
|
public static object GetId(this object? obj)
|
||||||
|
=> obj.GetIdOrDefault()
|
||||||
|
?? throw new InvalidOperationException($"The object of type '{obj?.GetType().FullName ?? "null"}' does not have a readable 'Id' property.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tries to retrieve the value of the 'Id' property from the specified object.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object from which to retrieve the 'Id' property.</param>
|
||||||
|
/// <param name="id">
|
||||||
|
/// When this method returns, contains the value of the 'Id' property if found; otherwise, <c>null</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// <c>true</c> if the 'Id' property was found; otherwise, <c>false</c>.
|
||||||
|
/// </returns>
|
||||||
|
public static bool TryGetId(object? obj, out object id)
|
||||||
|
{
|
||||||
|
#pragma warning disable CS8601
|
||||||
|
id = obj.GetIdOrDefault();
|
||||||
|
#pragma warning restore CS8601
|
||||||
|
return id is not null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
namespace DigitalData.Core.Application.Interfaces
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Application
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements a simplified CRUD service interface that uses a single Data Transfer Object (DTO) type for all CRUD operations,
|
/// Implements a simplified CRUD service interface that uses a single Data Transfer Object (DTO) type for all CRUD operations,
|
||||||
@@ -15,8 +13,9 @@ namespace DigitalData.Core.Abstractions.Application
|
|||||||
/// This interface is useful for entities that do not require different DTOs for different operations,
|
/// This interface is useful for entities that do not require different DTOs for different operations,
|
||||||
/// allowing for a more concise and maintainable codebase when implementing services for such entities.
|
/// allowing for a more concise and maintainable codebase when implementing services for such entities.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public interface IBasicCRUDService<TDto, TEntity, TId> : ICRUDService<TDto, TDto, TEntity, TId>
|
public interface IBasicCRUDService<TDto, TEntity, TId> : ICRUDService<TDto, TDto, TEntity, TId>
|
||||||
where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId>
|
where TDto : class where TEntity : class
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
using DigitalData.Core.DTO;
|
using DigitalData.Core.Application.DTO;
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Application
|
namespace DigitalData.Core.Application.Interfaces
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public interface ICRUDService<TCreateDto, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
|
public interface ICRUDService<TCreateDto, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
|
||||||
where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
|
where TCreateDto : class where TReadDto : class where TEntity : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Asynchronously creates a new entity based on the provided <paramref name="createDto"/> and returns the identifier of the created entity wrapped in a <see cref="DataResult{TId}"/>.
|
/// Asynchronously creates a new entity based on the provided <paramref name="createDto"/> and returns the identifier of the created entity wrapped in a <see cref="DataResult{TId}"/>.
|
||||||
@@ -20,6 +21,6 @@ namespace DigitalData.Core.Abstractions.Application
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="updateDto">The updateDTO with updated values for the entity.</param>
|
/// <param name="updateDto">The updateDTO with updated values for the entity.</param>
|
||||||
/// <returns>An Result indicating the outcome of the update operation, with an appropriate message.</returns>
|
/// <returns>An Result indicating the outcome of the update operation, with an appropriate message.</returns>
|
||||||
Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>;
|
Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using DigitalData.Core.DTO;
|
using DigitalData.Core.Application.DTO;
|
||||||
using System.DirectoryServices;
|
using System.DirectoryServices;
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Application
|
namespace DigitalData.Core.Application.Interfaces
|
||||||
{
|
{
|
||||||
public interface IDirectorySearchService
|
public interface IDirectorySearchService
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace DigitalData.Core.Application
|
namespace DigitalData.Core.Application.Interfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the operations for JWT service handling claims of type <typeparamref name="TClaimValue"/>.
|
/// Defines the operations for JWT service handling claims of type <typeparamref name="TClaimValue"/>.
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
using DigitalData.Core.DTO;
|
using DigitalData.Core.Application.DTO;
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Application
|
namespace DigitalData.Core.Application.Interfaces
|
||||||
{
|
{
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
public interface IReadService<TReadDto, TEntity, TId>
|
public interface IReadService<TReadDto, TEntity, TId>
|
||||||
where TReadDto : class where TEntity : class
|
where TReadDto : class where TEntity : class
|
||||||
{
|
{
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Infrastructure;
|
namespace DigitalData.Core.Application.Interfaces.Repository;
|
||||||
|
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
namespace DigitalData.Core.Abstractions.Infrastructure
|
namespace DigitalData.Core.Application.Interfaces.Repository
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines the contract for CRUD operations on a repository for entities of type TEntity.
|
/// Defines the contract for CRUD operations on a repository for entities of type TEntity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TEntity">The type of the entity this repository works with.</typeparam>
|
/// <typeparam name="TEntity">The type of the entity this repository works with.</typeparam>
|
||||||
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
||||||
public interface ICRUDRepository<TEntity, TId> where TEntity : class, IUnique<TId>
|
public interface ICRUDRepository<TEntity, TId> where TEntity : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a new entity to the repository.
|
/// Adds a new entity to the repository.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace DigitalData.Core.Abstractions.Infrastructure
|
namespace DigitalData.Core.Application.Interfaces.Repository
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Defines methods for mapping between entities and Data Transfer Objects (DTOs).
|
/// Defines methods for mapping between entities and Data Transfer Objects (DTOs).
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Infrastructure;
|
namespace DigitalData.Core.Application.Interfaces.Repository;
|
||||||
|
|
||||||
public interface IRepository<TEntity>
|
public interface IRepository<TEntity>
|
||||||
{
|
{
|
||||||
@@ -1,63 +1,63 @@
|
|||||||
using Microsoft.IdentityModel.Tokens;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
namespace DigitalData.Core.Application
|
namespace DigitalData.Core.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implements the <see cref="IJWTService{TClaimValue}"/> interface to manage JWT operations for claims of type <typeparamref name="TClaimValue"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class JWTService<TClaimValue> : IJWTService<TClaimValue>
|
||||||
{
|
{
|
||||||
|
private readonly Func<TClaimValue, SecurityTokenDescriptor> _factory;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Implements the <see cref="IJWTService{TClaimValue}"/> interface to manage JWT operations for claims of type <typeparamref name="TClaimValue"/>.
|
/// Initializes a new instance of the <see cref="JWTService{TClaimValue}"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JWTService<TClaimValue> : IJWTService<TClaimValue>
|
/// <param name="tokenDescriptorFactory">A factory function to produce <see cref="SecurityTokenDescriptor"/> based on the claim value.</param>
|
||||||
|
public JWTService(Func<TClaimValue, SecurityTokenDescriptor> tokenDescriptorFactory)
|
||||||
{
|
{
|
||||||
private readonly Func<TClaimValue, SecurityTokenDescriptor> _factory;
|
_factory = tokenDescriptorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="JWTService{TClaimValue}"/> class.
|
/// Generates a symmetric security key with the specified byte size.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="tokenDescriptorFactory">A factory function to produce <see cref="SecurityTokenDescriptor"/> based on the claim value.</param>
|
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param>
|
||||||
public JWTService(Func<TClaimValue, SecurityTokenDescriptor> tokenDescriptorFactory)
|
/// <returns>A new instance of <see cref="SymmetricSecurityKey"/>.</returns>
|
||||||
{
|
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
|
||||||
_factory = tokenDescriptorFactory;
|
{
|
||||||
}
|
using var rng = RandomNumberGenerator.Create();
|
||||||
|
var randomBytes = new byte[byteSize];
|
||||||
|
rng.GetBytes(randomBytes);
|
||||||
|
var securityKey = new SymmetricSecurityKey(randomBytes);
|
||||||
|
|
||||||
/// <summary>
|
return securityKey;
|
||||||
/// Generates a symmetric security key with the specified byte size.
|
}
|
||||||
/// </summary>
|
|
||||||
/// <param name="byteSize">The size of the security key in bytes. Default is 32 bytes.</param>
|
|
||||||
/// <returns>A new instance of <see cref="SymmetricSecurityKey"/>.</returns>
|
|
||||||
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
|
|
||||||
{
|
|
||||||
using var rng = RandomNumberGenerator.Create();
|
|
||||||
var randomBytes = new byte[byteSize];
|
|
||||||
rng.GetBytes(randomBytes);
|
|
||||||
var securityKey = new SymmetricSecurityKey(randomBytes);
|
|
||||||
|
|
||||||
return securityKey;
|
/// <summary>
|
||||||
}
|
/// Generates a JWT for the specified claim value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="claimValue">The claim value to encode in the JWT.</param>
|
||||||
|
/// <returns>A JWT as a string.</returns>
|
||||||
|
public string GenerateToken(TClaimValue claimValue)
|
||||||
|
{
|
||||||
|
var tokenDescriptor = _factory(claimValue);
|
||||||
|
|
||||||
/// <summary>
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
/// Generates a JWT for the specified claim value.
|
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||||
/// </summary>
|
return tokenHandler.WriteToken(token);
|
||||||
/// <param name="claimValue">The claim value to encode in the JWT.</param>
|
}
|
||||||
/// <returns>A JWT as a string.</returns>
|
|
||||||
public string GenerateToken(TClaimValue claimValue)
|
|
||||||
{
|
|
||||||
var tokenDescriptor = _factory(claimValue);
|
|
||||||
|
|
||||||
var tokenHandler = new JwtSecurityTokenHandler();
|
/// <summary>
|
||||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
/// Reads and validates a security token from a string representation.
|
||||||
return tokenHandler.WriteToken(token);
|
/// </summary>
|
||||||
}
|
/// <param name="token">The JWT to read.</param>
|
||||||
|
/// <returns>A <see cref="JwtSecurityToken"/> if the token is valid; otherwise, null.</returns>
|
||||||
/// <summary>
|
public JwtSecurityToken? ReadSecurityToken(string token)
|
||||||
/// Reads and validates a security token from a string representation.
|
{
|
||||||
/// </summary>
|
var tokenHandler = new JwtSecurityTokenHandler();
|
||||||
/// <param name="token">The JWT to read.</param>
|
return tokenHandler.CanReadToken(token) ? tokenHandler.ReadToken(token) as JwtSecurityToken : null;
|
||||||
/// <returns>A <see cref="JwtSecurityToken"/> if the token is valid; otherwise, null.</returns>
|
|
||||||
public JwtSecurityToken? ReadSecurityToken(string token)
|
|
||||||
{
|
|
||||||
var tokenHandler = new JwtSecurityTokenHandler();
|
|
||||||
return tokenHandler.CanReadToken(token) ? tokenHandler.ReadToken(token) as JwtSecurityToken : null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
namespace DigitalData.Core.Application
|
namespace DigitalData.Core.Application;
|
||||||
|
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
|
public static class Key
|
||||||
{
|
{
|
||||||
public static class Key
|
public static readonly string EntityDoesNotExist = "EntityDoesNotExist";
|
||||||
{
|
public static readonly string ReadFailed = "ReadFailed";
|
||||||
public static readonly string EntityDoesNotExist = "EntityDoesNotExist";
|
public static readonly string UpdateFailed = "UpdateFailed";
|
||||||
public static readonly string ReadFailed = "ReadFailed";
|
public static readonly string DeletionFailed = "DeletionFailed";
|
||||||
public static readonly string UpdateFailed = "UpdateFailed";
|
public static readonly string DirSearcherDisconnected = "DirSearcherDisconnected";
|
||||||
public static readonly string DeletionFailed = "DeletionFailed";
|
|
||||||
public static readonly string DirSearcherDisconnected = "DirSearcherDisconnected";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,79 +1,78 @@
|
|||||||
using DigitalData.Core.Abstractions.Application;
|
using AutoMapper;
|
||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
using DigitalData.Core.Application.DTO;
|
||||||
using AutoMapper;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.DTO;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
using DigitalData.Core.Abstractions;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Application
|
namespace DigitalData.Core.Application;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides generic Read (Read and Delete) operations for a specified type of entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
|
||||||
|
/// <typeparam name="TEntity">The entity type.</typeparam>
|
||||||
|
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
||||||
|
[Obsolete("Use MediatR")]
|
||||||
|
public class ReadService<TCRUDRepository, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
|
||||||
|
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TReadDto : class where TEntity : class
|
||||||
{
|
{
|
||||||
|
protected readonly TCRUDRepository _repository;
|
||||||
|
protected readonly IMapper _mapper;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides generic Read (Read and Delete) operations for a specified type of entity.
|
/// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
|
/// <param name="repository">The CRUD repository for accessing the database.</param>
|
||||||
/// <typeparam name="TEntity">The entity type.</typeparam>
|
/// <param name="mapper">The AutoMapper instance for mapping between DTOs and entity objects.</param>
|
||||||
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
|
public ReadService(TCRUDRepository repository, IMapper mapper)
|
||||||
public class ReadService<TCRUDRepository, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
|
|
||||||
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TReadDto : class where TEntity : class, IUnique<TId>
|
|
||||||
{
|
{
|
||||||
protected readonly TCRUDRepository _repository;
|
_repository = repository;
|
||||||
protected readonly IMapper _mapper;
|
_mapper = mapper;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="repository">The CRUD repository for accessing the database.</param>
|
|
||||||
/// <param name="mapper">The AutoMapper instance for mapping between DTOs and entity objects.</param>
|
|
||||||
public ReadService(TCRUDRepository repository, IMapper mapper)
|
|
||||||
{
|
|
||||||
_repository = repository;
|
|
||||||
_mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously reads an entity by its identifier and maps it to a read DTO.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The identifier of the entity to read.</param>
|
|
||||||
/// <returns>A service result indicating success or failure, including the read DTO if successful.</returns>
|
|
||||||
public virtual async Task<DataResult<TReadDto>> ReadByIdAsync(TId id)
|
|
||||||
{
|
|
||||||
var entity = await _repository.ReadByIdAsync(id);
|
|
||||||
return entity is null
|
|
||||||
? Result.Fail<TReadDto>()
|
|
||||||
: Result.Success(_mapper.Map<TReadDto>(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously reads all entities and maps them to read DTOs.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>A service result including a collection of read DTOs.</returns>
|
|
||||||
public virtual async Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync()
|
|
||||||
{
|
|
||||||
var entities = await _repository.ReadAllAsync();
|
|
||||||
var readDto = _mapper.Map<IEnumerable<TReadDto>>(entities);
|
|
||||||
return Result.Success(readDto);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously deletes an entity by its identifier.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The identifier of the entity to delete.</param>
|
|
||||||
/// <returns>A service message indicating success or failure.</returns>
|
|
||||||
public virtual async Task<Result> DeleteAsyncById(TId id)
|
|
||||||
{
|
|
||||||
TEntity? entity = await _repository.ReadByIdAsync(id);
|
|
||||||
|
|
||||||
if (entity is null)
|
|
||||||
return Result.Fail();
|
|
||||||
|
|
||||||
bool isDeleted = await _repository.DeleteAsync(entity);
|
|
||||||
return isDeleted ? Result.Success() : Result.Fail();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Asynchronously checks if an entity with the specified identifier exists.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="id">The identifier of the entity to check.</param>
|
|
||||||
/// <returns>A Task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
|
|
||||||
public virtual async Task<bool> HasEntity(TId id) => await _repository.CountAsync(id) > 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously reads an entity by its identifier and maps it to a read DTO.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The identifier of the entity to read.</param>
|
||||||
|
/// <returns>A service result indicating success or failure, including the read DTO if successful.</returns>
|
||||||
|
public virtual async Task<DataResult<TReadDto>> ReadByIdAsync(TId id)
|
||||||
|
{
|
||||||
|
var entity = await _repository.ReadByIdAsync(id);
|
||||||
|
return entity is null
|
||||||
|
? Result.Fail<TReadDto>()
|
||||||
|
: Result.Success(_mapper.Map<TReadDto>(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously reads all entities and maps them to read DTOs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A service result including a collection of read DTOs.</returns>
|
||||||
|
public virtual async Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync()
|
||||||
|
{
|
||||||
|
var entities = await _repository.ReadAllAsync();
|
||||||
|
var readDto = _mapper.Map<IEnumerable<TReadDto>>(entities);
|
||||||
|
return Result.Success(readDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously deletes an entity by its identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The identifier of the entity to delete.</param>
|
||||||
|
/// <returns>A service message indicating success or failure.</returns>
|
||||||
|
public virtual async Task<Result> DeleteAsyncById(TId id)
|
||||||
|
{
|
||||||
|
TEntity? entity = await _repository.ReadByIdAsync(id);
|
||||||
|
|
||||||
|
if (entity is null)
|
||||||
|
return Result.Fail();
|
||||||
|
|
||||||
|
bool isDeleted = await _repository.DeleteAsync(entity);
|
||||||
|
return isDeleted ? Result.Success() : Result.Fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Asynchronously checks if an entity with the specified identifier exists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The identifier of the entity to check.</param>
|
||||||
|
/// <returns>A Task that represents the asynchronous operation. The task result contains a boolean value indicating whether the entity exists.</returns>
|
||||||
|
public virtual async Task<bool> HasEntity(TId id) => await _repository.CountAsync(id) > 0;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Client;
|
using DigitalData.Core.Client.Interface;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Client;
|
using DigitalData.Core.Client.Interface;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Client;
|
using DigitalData.Core.Client.Interface;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
|
|
||||||
namespace DigitalData.Core.Abstractions.Client
|
namespace DigitalData.Core.Client.Interface
|
||||||
{
|
{
|
||||||
public interface IBaseHttpClientService
|
public interface IBaseHttpClientService
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace DigitalData.Core.Abstractions.Client
|
namespace DigitalData.Core.Client.Interface
|
||||||
{
|
{
|
||||||
public interface IHttpClientOptions
|
public interface IHttpClientOptions
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace DigitalData.Core.Abstractions.Client
|
namespace DigitalData.Core.Client.Interface
|
||||||
{
|
{
|
||||||
public interface IHttpClientService<TClientOptions> : IBaseHttpClientService where TClientOptions : IHttpClientOptions
|
public interface IHttpClientService<TClientOptions> : IBaseHttpClientService where TClientOptions : IHttpClientOptions
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Client;
|
using DigitalData.Core.Client.Interface;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace DigitalData.Core.Client
|
namespace DigitalData.Core.Client
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Exceptions.Middleware;
|
||||||
|
|
||||||
|
public static class DependencyInjection
|
||||||
|
{
|
||||||
|
public static IServiceCollection ConfigureGlobalExceptionHandler(this IServiceCollection services, Action<GlobalExceptionHandlerOptions>? options = null, bool addDefaultHandlers = true)
|
||||||
|
{
|
||||||
|
options ??= opt => { };
|
||||||
|
|
||||||
|
if (addDefaultHandlers)
|
||||||
|
{
|
||||||
|
options += opt => opt.Add(HttpExceptionHandler.Default, setAsDefault: true);
|
||||||
|
options += opt => opt.Add(HttpExceptionHandler.DefaultBadRequest);
|
||||||
|
options += opt => opt.Add(HttpExceptionHandler.DefaultNotFound);
|
||||||
|
}
|
||||||
|
return services.Configure(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IApplicationBuilder UseGlobalExceptionHandler(this IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseMiddleware<GlobalExceptionHandlerMiddleware>();
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<!-- NuGet Package Metadata -->
|
||||||
|
<PackageId>DigitalData.Core.Exceptions.Middleware</PackageId>
|
||||||
|
<Authors>Digital Data GmbH</Authors>
|
||||||
|
<Company>Digital Data GmbH</Company>
|
||||||
|
<Product>DigitalData.Core.Exceptions.Middleware</Product>
|
||||||
|
<Description>Provides middleware components for standardized exception handling and error response formatting in ASP.NET Core applications.</Description>
|
||||||
|
<PackageTags>digital data core exceptions middleware</PackageTags>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<Copyright>Copyright 2025</Copyright>
|
||||||
|
<PackageProjectUrl></PackageProjectUrl>
|
||||||
|
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||||
|
<PackAsTool>False</PackAsTool>
|
||||||
|
<PackageIcon>core_icon.png</PackageIcon>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.0</FileVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\Assets\core_icon.png">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Exceptions.Middleware;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Middleware for handling exceptions globally in the application.
|
||||||
|
/// Captures exceptions thrown during the request pipeline execution,
|
||||||
|
/// logs them, and returns an appropriate HTTP response with a JSON error message.
|
||||||
|
/// </summary>
|
||||||
|
public class GlobalExceptionHandlerMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
private readonly ILogger<GlobalExceptionHandlerMiddleware>? _logger;
|
||||||
|
|
||||||
|
private readonly GlobalExceptionHandlerOptions? _options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="GlobalExceptionHandlerMiddleware"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="next">The next middleware in the request pipeline.</param>
|
||||||
|
/// <param name="logger">The logger instance for logging exceptions.</param>
|
||||||
|
public GlobalExceptionHandlerMiddleware(RequestDelegate next, ILogger<GlobalExceptionHandlerMiddleware>? logger = null, IOptions<GlobalExceptionHandlerOptions>? options = null)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
_options = options?.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes the middleware to handle the HTTP request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="context">The HTTP context of the current request.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context); // Continue down the pipeline
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if(ex.GetType() == typeof(Exception))
|
||||||
|
_options?.DefaultHandler?.HandleExceptionAsync.Invoke(context, ex, _logger);
|
||||||
|
|
||||||
|
if (_options?.Handlers.TryGetValue(ex.GetType(), out var handler) ?? false)
|
||||||
|
handler?.HandleExceptionAsync.Invoke(context, ex, _logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace DigitalData.Core.Exceptions.Middleware;
|
||||||
|
|
||||||
|
public class GlobalExceptionHandlerOptions
|
||||||
|
{
|
||||||
|
internal readonly Dictionary<Type, HttpExceptionHandler> Handlers = new();
|
||||||
|
|
||||||
|
internal HttpExceptionHandler? DefaultHandler { get; private set; }
|
||||||
|
|
||||||
|
public GlobalExceptionHandlerOptions Add(HttpExceptionHandler handler, bool setAsDefault = false)
|
||||||
|
{
|
||||||
|
if (setAsDefault)
|
||||||
|
DefaultHandler = handler;
|
||||||
|
else
|
||||||
|
Handlers[handler.ExceptionType] = handler;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace DigitalData.Core.Exceptions.Middleware;
|
||||||
|
|
||||||
|
public record HttpExceptionHandler(Type ExceptionType, Func<HttpContext, Exception, ILogger?, Task> HandleExceptionAsync)
|
||||||
|
{
|
||||||
|
#region Alternative generator methods
|
||||||
|
public static HttpExceptionHandler Create<TException>(Func<HttpContext, Exception, ILogger?, Task> HandleExceptionAsync) where TException : Exception
|
||||||
|
=> new HttpExceptionHandler(typeof(TException), HandleExceptionAsync);
|
||||||
|
|
||||||
|
public static HttpExceptionHandler Create<TException>(HttpStatusCode statusCode, Func<Exception, string> messageFactory) where TException : Exception
|
||||||
|
=> Create<TException>(
|
||||||
|
async (context, ex, logger) =>
|
||||||
|
{
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
context.Response.StatusCode = (int)statusCode;
|
||||||
|
var message = messageFactory(ex);
|
||||||
|
await context.Response.WriteAsync(JsonSerializer.Serialize(new { message }));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Default handlers
|
||||||
|
public static readonly Func<Exception, string> DefaultMessageFactory = ex => ex.Message;
|
||||||
|
|
||||||
|
public static HttpExceptionHandler DefaultBadRequest => Create<BadRequestException>(HttpStatusCode.BadRequest, DefaultMessageFactory);
|
||||||
|
|
||||||
|
public static HttpExceptionHandler DefaultNotFound => Create<NotFoundException>(HttpStatusCode.NotFound, DefaultMessageFactory);
|
||||||
|
|
||||||
|
public static HttpExceptionHandler Default => Create<Exception>(
|
||||||
|
async (context, ex, logger) =>
|
||||||
|
{
|
||||||
|
context.Response.ContentType = "application/json";
|
||||||
|
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
|
||||||
|
var message = "An unexpected error occurred.";
|
||||||
|
await context.Response.WriteAsync(JsonSerializer.Serialize(new { message }));
|
||||||
|
});
|
||||||
|
#endregion
|
||||||
|
};
|
||||||
22
DigitalData.Core.Exceptions/BadRequestException.cs
Normal file
22
DigitalData.Core.Exceptions/BadRequestException.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace DigitalData.Core.Exceptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an exception that is thrown when a bad request is encountered.
|
||||||
|
/// </summary>
|
||||||
|
public class BadRequestException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BadRequestException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public BadRequestException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="BadRequestException"/> class with a specified error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
public BadRequestException(string? message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<!-- NuGet Package Metadata -->
|
||||||
|
<PackageId>DigitalData.Core.Exceptions</PackageId>
|
||||||
|
<Authors>Digital Data GmbH</Authors>
|
||||||
|
<Company>Digital Data GmbH</Company>
|
||||||
|
<Product>DigitalData.Core.Exceptions</Product>
|
||||||
|
<Description>This package contains exceptions for the DigitalData.Core library</Description>
|
||||||
|
<PackageTags>digital data core exceptions</PackageTags>
|
||||||
|
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
|
||||||
|
<Copyright>Copyright 2025</Copyright>
|
||||||
|
<PackageProjectUrl></PackageProjectUrl>
|
||||||
|
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
|
||||||
|
<PackAsTool>False</PackAsTool>
|
||||||
|
<PackageIcon>core_icon.png</PackageIcon>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<AssemblyVersion>1.0.0</AssemblyVersion>
|
||||||
|
<FileVersion>1.0.0</FileVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="..\Assets\core_icon.png">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
22
DigitalData.Core.Exceptions/NotFoundException.cs
Normal file
22
DigitalData.Core.Exceptions/NotFoundException.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace DigitalData.Core.Exceptions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an exception that is thrown when a requested resource is not found.
|
||||||
|
/// </summary>
|
||||||
|
public class NotFoundException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="NotFoundException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public NotFoundException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="NotFoundException"/> class with a specified error message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
public NotFoundException(string? message) : base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
|
|
||||||
namespace DigitalData.Core.Infrastructure.AutoMapper;
|
namespace DigitalData.Core.Infrastructure.AutoMapper;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
using DigitalData.Core.Abstractions;
|
||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
using DigitalData.Core.Application;
|
||||||
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DigitalData.Core.Infrastructure
|
namespace DigitalData.Core.Infrastructure
|
||||||
@@ -15,7 +16,7 @@ namespace DigitalData.Core.Infrastructure
|
|||||||
/// It leverages the EF Core's DbContext and DbSet to perform these operations.
|
/// It leverages the EF Core's DbContext and DbSet to perform these operations.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class CRUDRepository<TEntity, TId, TDbContext> : ICRUDRepository<TEntity, TId>
|
public class CRUDRepository<TEntity, TId, TDbContext> : ICRUDRepository<TEntity, TId>
|
||||||
where TEntity : class, IUnique<TId>
|
where TEntity : class
|
||||||
where TDbContext : DbContext
|
where TDbContext : DbContext
|
||||||
{
|
{
|
||||||
protected readonly TDbContext _dbContext;
|
protected readonly TDbContext _dbContext;
|
||||||
@@ -107,6 +108,6 @@ namespace DigitalData.Core.Infrastructure
|
|||||||
/// If there are multiple entities with the same identifier, they will all be counted.
|
/// If there are multiple entities with the same identifier, they will all be counted.
|
||||||
/// The default implementation assumes that the identifier is unique for each entity.
|
/// The default implementation assumes that the identifier is unique for each entity.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public virtual async Task<int> CountAsync(TId id) => await _dbSet.Where(e => e.Id!.Equals(id)).CountAsync();
|
public virtual async Task<int> CountAsync(TId id) => await _dbSet.Where(e => e.GetId().Equals(id)).CountAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
|
||||||
|
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace DigitalData.Core.Infrastructure;
|
namespace DigitalData.Core.Infrastructure;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using DigitalData.Core.Abstractions.Client;
|
using DigitalData.Core.Client;
|
||||||
using DigitalData.Core.Client;
|
using DigitalData.Core.Client.Interface;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace DigitalData.Core.Tests.Client
|
namespace DigitalData.Core.Tests.Client
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using DigitalData.Core.Abstractions.Infrastructure;
|
|
||||||
using DigitalData.Core.Infrastructure.AutoMapper;
|
using DigitalData.Core.Infrastructure.AutoMapper;
|
||||||
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
|
|
||||||
public class DbRepositoryTests
|
public class DbRepositoryTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Infrastruc
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{41795B74-A757-4E93-B907-83BFF04EEE5C}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{41795B74-A757-4E93-B907-83BFF04EEE5C}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Exceptions", "DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj", "{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Exceptions.Middleware", "DigitalData.Core.Exceptions.Middleware\DigitalData.Core.Exceptions.Middleware.csproj", "{2336AE61-A21D-437E-A11B-367D008A64B2}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Exceptions", "Exceptions", "{8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{0A27EA70-74F4-48FB-881C-D741F2FCD456}"
|
||||||
|
ProjectSection(SolutionItems) = preProject
|
||||||
|
Assets\core_icon.png = Assets\core_icon.png
|
||||||
|
Assets\core_legacy_icon.png = Assets\core_legacy_icon.png
|
||||||
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -92,14 +104,22 @@ Global
|
|||||||
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{9BC2DEC5-E89D-48CC-9A51-4D94496EE4A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C9266749-9504-4EA9-938F-F083357B60B7}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||||
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C9266749-9504-4EA9-938F-F083357B60B7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.Build.0 = Release|Any CPU
|
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{CE00E1F7-2771-4D9C-88FB-E564894E539E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2336AE61-A21D-437E-A11B-367D008A64B2}.Debug|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2336AE61-A21D-437E-A11B-367D008A64B2}.Debug|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2336AE61-A21D-437E-A11B-367D008A64B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2336AE61-A21D-437E-A11B-367D008A64B2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -121,6 +141,9 @@ Global
|
|||||||
{C9266749-9504-4EA9-938F-F083357B60B7} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
|
{C9266749-9504-4EA9-938F-F083357B60B7} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
|
||||||
{CE00E1F7-2771-4D9C-88FB-E564894E539E} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
|
{CE00E1F7-2771-4D9C-88FB-E564894E539E} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
|
||||||
{41795B74-A757-4E93-B907-83BFF04EEE5C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
{41795B74-A757-4E93-B907-83BFF04EEE5C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
|
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4} = {8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7}
|
||||||
|
{2336AE61-A21D-437E-A11B-367D008A64B2} = {8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7}
|
||||||
|
{8C3AF25D-81D9-4651-90CA-BF0BD2A03EA7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}
|
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}
|
||||||
|
|||||||
Reference in New Issue
Block a user