6 Commits

Author SHA1 Message Date
Developer 02
50c19fea31 Refactor exception handling middleware
Updated DependencyInjection to use GlobalExceptionHandler.
Removed ExceptionHandlingMiddleware and added
GlobalExceptionHandlerMiddleware for unified exception
handling across the application.
2025-05-16 15:59:38 +02:00
Developer 02
f93b197d45 Refactor ExceptionHandlingMiddleware and update dependencies
Rewrote the `ExceptionHandlingMiddleware` class to improve structure and functionality, changing its namespace to `DigitalData.Core.Exceptions`. Updated the constructor to support a nullable logger and implemented null-conditional logging for unhandled exceptions. Added new package references in `DigitalData.Core.Exceptions.csproj` for `Microsoft.AspNetCore.Http.Abstractions` and `Microsoft.Extensions.Logging.Abstractions`. Introduced a new `DependencyInjection` class to register the middleware in the application's pipeline.
2025-05-16 15:55:27 +02:00
Developer 02
eae0d9f913 Refactor DIExtensions and add exception handling middleware
- Improved documentation in DIExtensions.cs for clarity.
- Added project reference to DigitalData.Core.Exceptions.
- Updated solution file to include DigitalData.Core.Exceptions.
- Introduced ExceptionHandlingMiddleware for global exception handling.
- Added BadRequestException and NotFoundException classes.
- Created DigitalData.Core.Exceptions project with .NET 7.0, 8.0, and 9.0 support.
2025-05-16 15:37:21 +02:00
Developer 02
55eb250d7e Deprecate controllers/services; simplify generics
Added `[Obsolete("Use MediatR")]` attributes to various controller and service classes to indicate deprecation in favor of MediatR. Simplified generic type constraints in `CRUDControllerBase` and related files by removing `IUnique<TId>`. Improved structure and documentation in `CSPMiddleware.cs`. Introduced new extension methods in `EntityExtensions.cs` for safer retrieval of 'Id' properties. Removed `IUnique.cs` interface and updated project dependencies in `DigitalData.Core.Application.csproj` for caching. Overall, these changes enhance code maintainability and clarity.
2025-05-16 14:54:31 +02:00
Developer 02
e0c1b856ad Remove ServiceResultExtensions class and Try method
The `ServiceResultExtensions` class has been removed from the
`DigitalData.Core.Abstractions` namespace. This class included a
static method `Try<T>` for handling nullable results, which has
now been eliminated. This change may indicate a refactoring
or a new approach to managing nullable results in the codebase.
2025-05-16 13:12:02 +02:00
Developer 02
3a1aeb7ac3 Refactor namespaces and enhance application structure
This commit reorganizes namespaces from `DigitalData.Core.Abstractions` and `DigitalData.Core.DTO` to `DigitalData.Core.Application.Interfaces` and `DigitalData.Core.Application.DTO`, improving maintainability and clarity.

Updated using directives across multiple files to reflect the new structure, ensuring functionality remains intact.

Project references in `DigitalData.Core.API.csproj` have been consolidated to include the new Application project.

Introduced new classes and interfaces such as `BaseDTO`, `CookieConsentSettings`, `DataResult`, `Notice`, and `Result` to enhance data transfer and service result handling.

Updated `IRepository`, `ICRUDRepository`, and `IEntityMapper` interfaces to facilitate CRUD operations and entity mapping.

Added extension methods in `Extensions.cs` to improve repository usability.

New interfaces for HTTP client services have been added, enhancing external API call handling.

Overall, these changes reflect a significant restructuring aimed at improving organization and preparing for future development.
2025-05-16 11:24:58 +02:00
59 changed files with 1240 additions and 321 deletions

View File

@@ -1,15 +1,15 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace DigitalData.Core.API
{
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class BasicCRUDControllerBase<TCRUDService, TDto, TEntity, TId> : CRUDControllerBase<TCRUDService, TDto, TDto, TDto, TEntity, TId>
where TCRUDService : ICRUDService<TDto, TDto, TEntity, TId>
where TDto : class, IUnique<TId>
where TEntity : class, IUnique<TId>
where TDto : class
where TEntity : class
{
public BasicCRUDControllerBase(ILogger logger, TCRUDService service) : base(logger, service)
{

View File

@@ -1,7 +1,6 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using DigitalData.Core.Application.Interfaces;
using Microsoft.AspNetCore.Mvc;
using DigitalData.Core.Application.DTO;
namespace DigitalData.Core.API
{
@@ -15,12 +14,13 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class CRUDControllerBase<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCreateDto : class
where TReadDto : class
where TUpdateDto : class, IUnique<TId>
where TEntity : class, IUnique<TId>
where TUpdateDto : class
where TEntity : class
{
protected readonly ILogger _logger;
protected readonly TCRUDService _service;

View File

@@ -1,7 +1,6 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using DigitalData.Core.Application.Interfaces;
using Microsoft.AspNetCore.Mvc;
using DigitalData.Core.Application.DTO;
namespace DigitalData.Core.API
{
@@ -16,12 +15,13 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class CRUDControllerBaseWithErrorHandling<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCreateDto : class
where TReadDto : class
where TUpdateDto : class, IUnique<TId>
where TEntity : class, IUnique<TId>
where TUpdateDto : class
where TEntity : class
{
protected readonly ILogger _logger;
protected readonly TCRUDService _service;

View File

@@ -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>
/// Middleware to add Content Security Policy (CSP) headers to the HTTP response.
/// Initializes a new instance of the <see cref="CSPMiddleware"/> class.
/// </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;
private readonly string _policy;
_next = next;
_policy = policy;
}
/// <summary>
/// Initializes a new instance of the <see cref="CSPMiddleware"/> class.
/// </summary>
/// <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)
/// <summary>
/// Invokes the middleware to add the CSP header to the response.
/// </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(() =>
{
_next = next;
_policy = policy;
}
context.Response.Headers.Append("Content-Security-Policy",
string.Format(_policy, nonce));
return Task.CompletedTask;
});
/// <summary>
/// Invokes the middleware to add the CSP header to the response.
/// </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);
}
// Call the next middleware in the pipeline
await _next(context);
}
}

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.DTO;
using DigitalData.Core.Application.DTO;
using Microsoft.AspNetCore.Mvc;
using System.Text;

View File

@@ -1,53 +1,50 @@
using Microsoft.AspNetCore.Builder;
using System.Configuration;
namespace DigitalData.Core.API;
namespace DigitalData.Core.API
/// <summary>
/// Provides extension methods for adding middleware to the application's request pipeline.
/// </summary>
public static class DIExtensions
{
/// <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>
public static class DIExtensions
{
/// <summary>
/// Adds the <see cref="CSPMiddleware"/> to the application's request pipeline to include
/// Content Security Policy (CSP) headers in the HTTP response.
/// </summary>
/// <param name="app">The application builder.</param>
/// <param name="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);
/// <param name="app">The application builder.</param>
/// <param name="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>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplicationBuilder builder) => builder.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <returns>True if DiP mode is enabled; otherwise, false.</returns>
public static bool IsDiP(this WebApplication app) => app.Configuration.GetValue<bool>("DiPMode");
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <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();
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplicationBuilder.
/// </summary>
/// <param name="builder">The WebApplicationBuilder instance.</param>
/// <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();
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <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();
/// <summary>
/// Checks if the environment is Development or DiP (Development in Production) mode is enabled for the WebApplication.
/// </summary>
/// <param name="app">The WebApplication instance.</param>
/// <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();
/// <summary>
/// Configures the services with options from the specified section of the appsettings.json file.
@@ -65,5 +62,4 @@ namespace DigitalData.Core.API
builder.Services.Configure<T>(section);
return builder;
}
}
}
}

View File

@@ -32,8 +32,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
<ProjectReference Include="..\DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,6 +1,6 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using DigitalData.Core.Application.Interfaces;
using Microsoft.AspNetCore.Mvc;
using DigitalData.Core.Application.DTO;
namespace DigitalData.Core.API
{
@@ -12,6 +12,7 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class ReadControllerBase<TReadService, TReadDto, TEntity, TId> : ControllerBase
where TReadService : IReadService<TReadDto, TEntity, TId>
where TReadDto : class

View File

@@ -1,5 +1,5 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using DigitalData.Core.Application.DTO;
using DigitalData.Core.Application.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace DigitalData.Core.API
@@ -13,6 +13,7 @@ namespace DigitalData.Core.API
/// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController]
[Route("api/[controller]")]
[Obsolete("Use MediatR")]
public class ReadControllerBaseWithErrorHandling<TReadService, TReadDto, TEntity, TId> : ControllerBase
where TReadService : IReadService<TReadDto, TEntity, TId>
where TReadDto : class

View File

@@ -37,8 +37,4 @@
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,7 +0,0 @@
namespace DigitalData.Core.Abstractions
{
public interface IUnique<T>
{
public T Id { get; }
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,7 +1,6 @@
using AutoMapper;
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Application.Interfaces;
using DigitalData.Core.Application.Interfaces.Repository;
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
/// and a culture-specific translation service for any necessary text translations, ensuring a versatile and internationalized approach to CRUD operations.
/// </remarks>
[Obsolete("Use MediatR")]
public class BasicCRUDService<TCRUDRepository, 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>
/// Initializes a new instance of the BasicCRUDService with the specified repository, translation service, and AutoMapper configuration.

View File

@@ -1,9 +1,8 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstractions.Infrastructure;
using AutoMapper;
using DigitalData.Core.DTO;
using DigitalData.Core.Abstractions;
using AutoMapper;
using Microsoft.Extensions.Logging;
using DigitalData.Core.Application.Interfaces.Repository;
using DigitalData.Core.Application.Interfaces;
using DigitalData.Core.Application.DTO;
namespace DigitalData.Core.Application
{
@@ -14,8 +13,10 @@ namespace DigitalData.Core.Application
/// <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 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>
@@ -36,7 +37,7 @@ namespace DigitalData.Core.Application
{
var entity = _mapper.Map<TEntity>(createDto);
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>
@@ -44,12 +45,12 @@ namespace DigitalData.Core.Application
/// </summary>
/// <param name="updateDto">The DTO to update an entity from.</param>
/// <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)
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);

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Application.Interfaces;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

View 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();
}
}

View 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; }
}
}

View 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;
}
}
}

View 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;
}
}

View 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);
}
}

View 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
}
}

View 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();
}
}

View 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.
}
}

View File

@@ -27,7 +27,6 @@
</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.Localization.Abstractions" Version="7.0.16" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
@@ -37,20 +36,22 @@
<PackageReference Include="System.Security.Cryptography.Cng" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
<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'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<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'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.5" />
</ItemGroup>
</Project>

View File

@@ -1,10 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.CodeAnalysis;
using System.DirectoryServices;
using Microsoft.Extensions.Caching.Memory;
using System.DirectoryServices.AccountManagement;
using DigitalData.Core.DTO;
using Microsoft.Extensions.Options;
using DigitalData.Core.Application.Interfaces;
using DigitalData.Core.Application.DTO;
namespace DigitalData.Core.Application
{

View 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;
}
}

View File

@@ -1,6 +1,4 @@
using DigitalData.Core.Abstractions.Infrastructure;
namespace DigitalData.Core.Abstractions.Application
namespace DigitalData.Core.Application.Interfaces
{
/// <summary>
/// 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,
/// allowing for a more concise and maintainable codebase when implementing services for such entities.
/// </remarks>
[Obsolete("Use MediatR")]
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
{
}
}

View File

@@ -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>
where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
where TCreateDto : class where TReadDto : class where TEntity : class
{
/// <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}"/>.
@@ -20,6 +21,6 @@ namespace DigitalData.Core.Abstractions.Application
/// </summary>
/// <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>
Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>;
Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto);
}
}

View File

@@ -1,7 +1,7 @@
using DigitalData.Core.DTO;
using DigitalData.Core.Application.DTO;
using System.DirectoryServices;
namespace DigitalData.Core.Abstractions.Application
namespace DigitalData.Core.Application.Interfaces
{
public interface IDirectorySearchService
{

View File

@@ -2,7 +2,7 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
namespace DigitalData.Core.Application
namespace DigitalData.Core.Application.Interfaces
{
/// <summary>
/// Defines the operations for JWT service handling claims of type <typeparamref name="TClaimValue"/>.

View File

@@ -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>
where TReadDto : class where TEntity : class
{

View File

@@ -1,6 +1,6 @@
using System.Linq.Expressions;
namespace DigitalData.Core.Abstractions.Infrastructure;
namespace DigitalData.Core.Application.Interfaces.Repository;
public static class Extensions
{

View File

@@ -1,11 +1,11 @@
namespace DigitalData.Core.Abstractions.Infrastructure
namespace DigitalData.Core.Application.Interfaces.Repository
{
/// <summary>
/// Defines the contract for CRUD operations on a repository for entities of type TEntity.
/// </summary>
/// <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>
public interface ICRUDRepository<TEntity, TId> where TEntity : class, IUnique<TId>
public interface ICRUDRepository<TEntity, TId> where TEntity : class
{
/// <summary>
/// Adds a new entity to the repository.

View File

@@ -1,4 +1,4 @@
namespace DigitalData.Core.Abstractions.Infrastructure
namespace DigitalData.Core.Application.Interfaces.Repository
{
/// <summary>
/// Defines methods for mapping between entities and Data Transfer Objects (DTOs).

View File

@@ -1,6 +1,6 @@
using System.Linq.Expressions;
namespace DigitalData.Core.Abstractions.Infrastructure;
namespace DigitalData.Core.Application.Interfaces.Repository;
public interface IRepository<TEntity>
{

View File

@@ -1,63 +1,63 @@
using Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Application.Interfaces;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
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>
/// 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>
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>
/// Initializes a new instance of the <see cref="JWTService{TClaimValue}"/> class.
/// </summary>
/// <param name="tokenDescriptorFactory">A factory function to produce <see cref="SecurityTokenDescriptor"/> based on the claim value.</param>
public JWTService(Func<TClaimValue, SecurityTokenDescriptor> tokenDescriptorFactory)
{
_factory = tokenDescriptorFactory;
}
/// <summary>
/// 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);
/// <summary>
/// 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;
}
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>
/// 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);
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
/// <summary>
/// Reads and validates a security token from a string representation.
/// </summary>
/// <param name="token">The JWT to read.</param>
/// <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;
}
/// <summary>
/// Reads and validates a security token from a string representation.
/// </summary>
/// <param name="token">The JWT to read.</param>
/// <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;
}
}

View File

@@ -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 UpdateFailed = "UpdateFailed";
public static readonly string DeletionFailed = "DeletionFailed";
public static readonly string DirSearcherDisconnected = "DirSearcherDisconnected";
}
public static readonly string EntityDoesNotExist = "EntityDoesNotExist";
public static readonly string ReadFailed = "ReadFailed";
public static readonly string UpdateFailed = "UpdateFailed";
public static readonly string DeletionFailed = "DeletionFailed";
public static readonly string DirSearcherDisconnected = "DirSearcherDisconnected";
}

View File

@@ -1,79 +1,78 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.Abstractions.Infrastructure;
using AutoMapper;
using DigitalData.Core.DTO;
using DigitalData.Core.Abstractions;
using AutoMapper;
using DigitalData.Core.Application.DTO;
using DigitalData.Core.Application.Interfaces;
using DigitalData.Core.Application.Interfaces.Repository;
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>
/// 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>
/// <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>
public class ReadService<TCRUDRepository, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TReadDto : class where TEntity : class, IUnique<TId>
/// <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)
{
protected readonly TCRUDRepository _repository;
protected readonly IMapper _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;
_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;
}

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using Microsoft.Extensions.Options;
using System.Net;

View File

@@ -1,6 +1,6 @@
using System.Net;
namespace DigitalData.Core.Abstractions.Client
namespace DigitalData.Core.Client.Interface
{
public interface IBaseHttpClientService
{

View File

@@ -1,4 +1,4 @@
namespace DigitalData.Core.Abstractions.Client
namespace DigitalData.Core.Client.Interface
{
public interface IHttpClientOptions
{

View File

@@ -1,4 +1,4 @@
namespace DigitalData.Core.Abstractions.Client
namespace DigitalData.Core.Client.Interface
{
public interface IHttpClientService<TClientOptions> : IBaseHttpClientService where TClientOptions : IHttpClientOptions
{

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Client;
using DigitalData.Core.Client.Interface;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Client

View 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)
{
}
}

View File

@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Builder;
namespace DigitalData.Core.Exceptions;
public static class DependencyInjection
{
public static IApplicationBuilder UseGlobalExceptionHandler(this IApplicationBuilder app)
{
app.UseMiddleware<GlobalExceptionHandlerMiddleware>();
return app;
}
}

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.5" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,83 @@
namespace DigitalData.Core.Exceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Text.Json;
/// <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;
/// <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)
{
_next = next;
_logger = logger;
}
/// <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)
{
await HandleExceptionAsync(context, ex, _logger);
}
}
/// <summary>
/// Handles exceptions by logging them and writing an appropriate JSON response.
/// </summary>
/// <param name="context">The HTTP context of the current request.</param>
/// <param name="exception">The exception that occurred.</param>
/// <param name="logger">The logger instance for logging the exception.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger? logger = null)
{
context.Response.ContentType = "application/json";
string message;
switch (exception)
{
case BadRequestException badRequestEx:
context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
message = badRequestEx.Message;
break;
case NotFoundException notFoundEx:
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
message = notFoundEx.Message;
break;
default:
logger?.LogError(exception, "Unhandled exception occurred.");
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
message = "An unexpected error occurred.";
break;
}
await context.Response.WriteAsync(JsonSerializer.Serialize(new
{
message
}));
}
}

View 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)
{
}
}

View File

@@ -1,5 +1,5 @@
using AutoMapper;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Application.Interfaces.Repository;
namespace DigitalData.Core.Infrastructure.AutoMapper;

View File

@@ -1,5 +1,5 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Application.Interfaces.Repository;
using Microsoft.EntityFrameworkCore;
namespace DigitalData.Core.Infrastructure

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Application.Interfaces.Repository;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Application.Interfaces.Repository;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

View File

@@ -41,6 +41,7 @@
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.Application\DigitalData.Core.Application.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,4 +1,4 @@
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Application.Interfaces.Repository;
using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.Core.Infrastructure;

View File

@@ -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;
namespace DigitalData.Core.Tests.Client

View File

@@ -6,8 +6,8 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System.Reflection;
using DigitalData.Core.Abstractions.Infrastructure;
using DigitalData.Core.Infrastructure.AutoMapper;
using DigitalData.Core.Application.Interfaces.Repository;
public class DbRepositoryTests
{

View File

@@ -39,6 +39,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Infrastruc
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{41795B74-A757-4E93-B907-83BFF04EEE5C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.Exceptions", "DigitalData.Core.Exceptions\DigitalData.Core.Exceptions.csproj", "{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -100,6 +102,10 @@ Global
{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.Build.0 = Release|Any CPU
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4}.Debug|Any CPU.Build.0 = Debug|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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -121,6 +127,7 @@ Global
{C9266749-9504-4EA9-938F-F083357B60B7} = {72CBAFBA-55CC-49C9-A484-F8F4550054CB}
{CE00E1F7-2771-4D9C-88FB-E564894E539E} = {41795B74-A757-4E93-B907-83BFF04EEE5C}
{41795B74-A757-4E93-B907-83BFF04EEE5C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{BAEF6CC9-4FE2-4E3F-9D32-911C9E8CCFB4} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {8E2C3187-F848-493A-9E79-56D20DDCAC94}