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.
This commit is contained in:
parent
e0c1b856ad
commit
55eb250d7e
@ -1,4 +1,3 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
|
||||||
using DigitalData.Core.Application.Interfaces;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@ -6,10 +5,11 @@ 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,4 +1,3 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
|
||||||
using DigitalData.Core.Application.Interfaces;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using DigitalData.Core.Application.DTO;
|
using DigitalData.Core.Application.DTO;
|
||||||
@ -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,4 +1,3 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
|
||||||
using DigitalData.Core.Application.Interfaces;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using DigitalData.Core.Application.DTO;
|
using DigitalData.Core.Application.DTO;
|
||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
namespace DigitalData.Core.Abstractions
|
|
||||||
{
|
|
||||||
public interface IUnique<T>
|
|
||||||
{
|
|
||||||
public T Id { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DigitalData.Core.Abstractions;
|
|
||||||
using DigitalData.Core.Application.Interfaces;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.Application.Interfaces.Repository;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
|
|
||||||
@ -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,5 +1,4 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DigitalData.Core.Abstractions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using DigitalData.Core.Application.Interfaces.Repository;
|
using DigitalData.Core.Application.Interfaces.Repository;
|
||||||
using DigitalData.Core.Application.Interfaces;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
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;
|
namespace DigitalData.Core.Application.Interfaces
|
||||||
|
|
||||||
namespace DigitalData.Core.Application.Interfaces
|
|
||||||
{
|
{
|
||||||
/// <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.Application.Interfaces
|
|||||||
/// 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,10 +1,10 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
using DigitalData.Core.Application.DTO;
|
||||||
using DigitalData.Core.Application.DTO;
|
|
||||||
|
|
||||||
namespace DigitalData.Core.Application.Interfaces
|
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}"/>.
|
||||||
@ -21,6 +21,6 @@ namespace DigitalData.Core.Application.Interfaces
|
|||||||
/// </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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace DigitalData.Core.Application.Interfaces
|
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,13 +1,11 @@
|
|||||||
using DigitalData.Core.Abstractions;
|
namespace DigitalData.Core.Application.Interfaces.Repository
|
||||||
|
|
||||||
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.
|
||||||
|
|||||||
@ -3,62 +3,61 @@ 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 AutoMapper;
|
using AutoMapper;
|
||||||
using DigitalData.Core.Abstractions;
|
|
||||||
using DigitalData.Core.Application.DTO;
|
using DigitalData.Core.Application.DTO;
|
||||||
using DigitalData.Core.Application.Interfaces;
|
using DigitalData.Core.Application.Interfaces;
|
||||||
using DigitalData.Core.Application.Interfaces.Repository;
|
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>
|
/// <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;
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user