Erweiterung der DTOs und Implementierung der Lokalisierungsdienste

- Neue DTO-Extension-Methoden hinzugefügt, um die Verarbeitung und Zuweisung von Nachrichten und Benachrichtigungen in Ergebnisobjekten zu vereinfachen.
- Lokalisierungsunterstützung in der API-Schicht implementiert, einschließlich Cookie-basierter Lokalisierung und Konfiguration unterstützter Kulturen.
- Die Integration von StringLocalizer in die API-Schicht wurde durchgeführt, um eine nahtlose Mehrsprachigkeit zu ermöglichen.
- Fehlerbehandlung für fehlende Konfigurationseinstellungen verbessert.

Die Änderungen verbessern die Flexibilität und Wartbarkeit des Codes und unterstützen eine effizientere Internationalisierung der Anwendung.
This commit is contained in:
Developer 02 2024-04-30 17:01:26 +02:00
parent f6d8721c27
commit 4b71836fea
36 changed files with 303 additions and 1109 deletions

View File

@ -1,4 +1,5 @@
using DigitalData.Core.Contracts.Application; using DigitalData.Core.Contracts.Application;
using DigitalData.Core.DTO;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Text; using System.Text;
@ -50,10 +51,8 @@ namespace DigitalData.Core.API
/// Returns an ObjectResult representing an internal server error (status code 500) with optional exception and message details. /// Returns an ObjectResult representing an internal server error (status code 500) with optional exception and message details.
/// </summary> /// </summary>
/// <param name="controllerBase">The ControllerBase instance representing the controller.</param> /// <param name="controllerBase">The ControllerBase instance representing the controller.</param>
/// <param name="ex">Optional. The exception that occurred, if any.</param> /// <param name="result">Optional. A custom error resul to include in the response.</param>
/// <param name="message">Optional. A custom error message to include in the response.</param>
/// /// <param name="messageKey">Optional. A custom error message key to include in the response.</param>
/// <returns>An ObjectResult representing an internal server error (status code 500).</returns> /// <returns>An ObjectResult representing an internal server error (status code 500).</returns>
public static ObjectResult InnerServiceError(this ControllerBase controllerBase, IServiceMessage? serviceMessage = null) => controllerBase.StatusCode(500, serviceMessage); public static ObjectResult InnerServiceError(this ControllerBase controllerBase, Result? result = null) => controllerBase.StatusCode(500, result);
} }
} }

View File

@ -13,6 +13,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Contracts\DigitalData.Core.Contracts.csproj" /> <ProjectReference Include="..\DigitalData.Core.Contracts\DigitalData.Core.Contracts.csproj" />
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -20,10 +20,10 @@ namespace DigitalData.Core.API
public static IServiceCollection AddCookieBasedLocalizer(this IServiceCollection services, string resourcesPath) public static IServiceCollection AddCookieBasedLocalizer(this IServiceCollection services, string resourcesPath)
{ {
// Adds localization services with the specified resources path. // Adds localization services with the specified resources path.
services.AddLocalization(options => options.ResourcesPath = resourcesPath); services.AddLocalization(options => options.ResourcesPath = resourcesPath)
// Adds MVC services with view localization and data annotations localization. // Adds MVC services with view localization and data annotations localization.
services.AddMvc().AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix) .AddMvc().AddViewLocalization(Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat.Suffix)
.AddDataAnnotationsLocalization(); .AddDataAnnotationsLocalization();
return services; return services;

View File

@ -4,6 +4,7 @@ using AutoMapper;
using System.Reflection; using System.Reflection;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using DigitalData.Core.DTO;
namespace DigitalData.Core.Application namespace DigitalData.Core.Application
{ {
@ -15,26 +16,29 @@ namespace DigitalData.Core.Application
/// <typeparam name="TUpdateDto">The DTO type for update operations.</typeparam> /// <typeparam name="TUpdateDto">The DTO type for update 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>
public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ServiceBase, ICRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId>, IServiceBase public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ICRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : class where TEntity : class where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : class where TEntity : class
{ {
protected readonly TCRUDRepository _repository; protected readonly TCRUDRepository _repository;
protected readonly IMapper _mapper; protected readonly IMapper _mapper;
protected readonly PropertyInfo? _keyPropertyInfo; protected readonly PropertyInfo? _keyPropertyInfo;
protected readonly IStringLocalizer _localizer;
/// <summary> /// <summary>
/// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper. /// Initializes a new instance of the CRUDService class with the specified repository, translation service, and mapper.
/// </summary> /// </summary>
/// <param name="repository">The CRUD repository for accessing the database.</param> /// <param name="repository">The CRUD repository for accessing the database.</param>
/// <param name="translationService">The service for translating messages based on culture.</param> /// <param name="localizer">The localizer for translating messages based on culture.</param>
/// <param name="mapper">The AutoMapper instance for mapping between DTOs and entity objects.</param> /// <param name="mapper">The AutoMapper instance for mapping between DTOs and entity objects.</param>
public CRUDService(TCRUDRepository repository, IStringLocalizer defaultLocalizer, IMapper mapper) : base(defaultLocalizer) public CRUDService(TCRUDRepository repository, IStringLocalizer localizer, IMapper mapper)
{ {
_repository = repository; _repository = repository;
_mapper = mapper; _mapper = mapper;
_keyPropertyInfo = typeof(TEntity).GetProperties() _keyPropertyInfo = typeof(TEntity).GetProperties()
.FirstOrDefault(prop => Attribute.IsDefined(prop, typeof(KeyAttribute))); .FirstOrDefault(prop => Attribute.IsDefined(prop, typeof(KeyAttribute)));
_localizer = localizer;
} }
/// <summary> /// <summary>
@ -42,14 +46,11 @@ namespace DigitalData.Core.Application
/// </summary> /// </summary>
/// <param name="createDto">The DTO to create an entity from.</param> /// <param name="createDto">The DTO to create an entity from.</param>
/// <returns>A service result indicating success or failure, including the entity DTO.</returns> /// <returns>A service result indicating success or failure, including the entity DTO.</returns>
public virtual async Task<IServiceResult<TId>> CreateAsync(TCreateDto createDto) public virtual async Task<DataResult<TId>> CreateAsync(TCreateDto createDto)
{ {
var entity = _mapper.MapOrThrow<TEntity>(createDto); var entity = _mapper.MapOrThrow<TEntity>(createDto);
var createdEntity = await _repository.CreateAsync(entity); var createdEntity = await _repository.CreateAsync(entity);
if (createdEntity is null) return createdEntity is null ? Result.Fail<TId>() : Result.Success(KeyValueOf(createdEntity));
return Failed<TId>();
else
return Successful(KeyValueOf(createdEntity));
} }
/// <summary> /// <summary>
@ -57,27 +58,23 @@ namespace DigitalData.Core.Application
/// </summary> /// </summary>
/// <param name="id">The identifier of the entity to read.</param> /// <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> /// <returns>A service result indicating success or failure, including the read DTO if successful.</returns>
public virtual async Task<IServiceResult<TReadDto>> ReadByIdAsync(TId id) public virtual async Task<DataResult<TReadDto>> ReadByIdAsync(TId id)
{ {
var entity = await _repository.ReadByIdAsync(id); var entity = await _repository.ReadByIdAsync(id);
if (entity is null) return entity is null
{ ? Result.Fail<TReadDto>().Message(_localizer[Key.EntityDoesNotExist])
var translatedMessage = MessageKey.EntityDoesNotExist.LocalizedBy(_localizer); : Result.Success(_mapper.MapOrThrow<TReadDto>(entity));
return Failed<TReadDto>();
}
else
return Successful(_mapper.MapOrThrow<TReadDto>(entity));
} }
/// <summary> /// <summary>
/// Asynchronously reads all entities and maps them to read DTOs. /// Asynchronously reads all entities and maps them to read DTOs.
/// </summary> /// </summary>
/// <returns>A service result including a collection of read DTOs.</returns> /// <returns>A service result including a collection of read DTOs.</returns>
public virtual async Task<IServiceResult<IEnumerable<TReadDto>>> ReadAllAsync() public virtual async Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync()
{ {
var entities = await _repository.ReadAllAsync(); var entities = await _repository.ReadAllAsync();
var readDto = _mapper.MapOrThrow<IEnumerable<TReadDto>>(entities); var readDto = _mapper.MapOrThrow<IEnumerable<TReadDto>>(entities);
return Successful(readDto); return Result.Success(readDto);
} }
/// <summary> /// <summary>
@ -85,17 +82,11 @@ 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<IServiceMessage> UpdateAsync(TUpdateDto updateDto) public virtual async Task<Result> UpdateAsync(TUpdateDto updateDto)
{ {
var entity = _mapper.MapOrThrow<TEntity>(updateDto); var entity = _mapper.MapOrThrow<TEntity>(updateDto);
bool isUpdated = await _repository.UpdateAsync(entity); bool isUpdated = await _repository.UpdateAsync(entity);
if (isUpdated) return isUpdated ? Result.Success() : Result.Fail().Message(_localizer[Key.UpdateFailed]);
return Successful();
else
{
var translatedMessage = MessageKey.UpdateFailed.LocalizedBy(_localizer);
return Failed();
}
} }
/// <summary> /// <summary>
@ -103,26 +94,15 @@ namespace DigitalData.Core.Application
/// </summary> /// </summary>
/// <param name="id">The identifier of the entity to delete.</param> /// <param name="id">The identifier of the entity to delete.</param>
/// <returns>A service message indicating success or failure.</returns> /// <returns>A service message indicating success or failure.</returns>
public virtual async Task<IServiceMessage> DeleteAsyncById(TId id) public virtual async Task<Result> DeleteAsyncById(TId id)
{ {
TEntity? entity = await _repository.ReadByIdAsync(id); TEntity? entity = await _repository.ReadByIdAsync(id);
if (entity is null) if (entity is null)
{ return Result.Fail().Message(_localizer[Key.DeletionFailed], _localizer[Key.EntityDoesNotExist]);
var deletionFailedMessage = MessageKey.DeletionFailed.LocalizedBy(_localizer);
var entityDoesNotExistMessage = MessageKey.EntityDoesNotExist.LocalizedBy(_localizer);
return new ServiceMessage(isSuccess: false, deletionFailedMessage, entityDoesNotExistMessage);
}
bool isDeleted = await _repository.DeleteAsync(entity); bool isDeleted = await _repository.DeleteAsync(entity);
return isDeleted ? Result.Success() : Result.Fail().Message(_localizer[Key.DeletionFailed]);
if (isDeleted)
return Successful();
else
{
var deletionFailedMessage = MessageKey.DeletionFailed.LocalizedBy(_localizer);
return Failed(deletionFailedMessage);
}
} }
/// <summary> /// <summary>

View File

@ -1,5 +1,4 @@
using AutoMapper; using AutoMapper;
using DigitalData.Core.Application.DTO;
using DigitalData.Core.Contracts.Application; using DigitalData.Core.Contracts.Application;
using DigitalData.Core.Contracts.Infrastructure; using DigitalData.Core.Contracts.Infrastructure;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
@ -66,30 +65,9 @@ namespace DigitalData.Core.Application
return service; return service;
} }
public static IServiceCollection AddResponseService(this IServiceCollection service)
{
service.AddScoped<IResponseService, ResponseService>();
return service;
}
public static IServiceCollection AddJWTService<TClaimValue>(this IServiceCollection services, Func<TClaimValue, SecurityTokenDescriptor> tokenDescriptorFactory) public static IServiceCollection AddJWTService<TClaimValue>(this IServiceCollection services, Func<TClaimValue, SecurityTokenDescriptor> tokenDescriptorFactory)
{ {
return services.AddScoped<IJWTService<TClaimValue>, JWTService<TClaimValue>>(provider => new (tokenDescriptorFactory)); return services.AddScoped<IJWTService<TClaimValue>, JWTService<TClaimValue>>(provider => new (tokenDescriptorFactory));
} }
public static IServiceCollection AddCookieConsentSettings(this IServiceCollection services)
{
services.AddSingleton<CookieConsentSettings>(sp =>
{
var configuration = sp.GetRequiredService<IConfiguration>();
var settings = configuration.GetSection("CookieConsentSettings").Get<CookieConsentSettings>();
if (settings is null)
{
throw new ConfigurationErrorsException("The 'CookieConsentSettings' section is missing or improperly configured in appsettings.json.");
}
return settings;
});
return services;
}
} }
} }

View File

@ -21,4 +21,8 @@
<ProjectReference Include="..\DigitalData.Core.Contracts\DigitalData.Core.Contracts.csproj" /> <ProjectReference Include="..\DigitalData.Core.Contracts\DigitalData.Core.Contracts.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="NewFolder\" />
</ItemGroup>
</Project> </Project>

View File

@ -6,11 +6,12 @@ using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using System.DirectoryServices.AccountManagement; using System.DirectoryServices.AccountManagement;
using Microsoft.Extensions.Localization; using Microsoft.Extensions.Localization;
using DigitalData.Core.DTO;
namespace DigitalData.Core.Application namespace DigitalData.Core.Application
{ {
[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")] [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "<Pending>")]
public class DirectorySearchService : ServiceBase, IDirectorySearchService public class DirectorySearchService : IDirectorySearchService
{ {
private readonly IMemoryCache _memoryCache; private readonly IMemoryCache _memoryCache;
public string ServerName { get; } public string ServerName { get; }
@ -18,8 +19,9 @@ namespace DigitalData.Core.Application
public string SearchRootPath { get; } public string SearchRootPath { get; }
private readonly DateTimeOffset _userCacheExpiration; private readonly DateTimeOffset _userCacheExpiration;
public Dictionary<string, string> CustomSearchFilters { get; } public Dictionary<string, string> CustomSearchFilters { get; }
protected readonly IStringLocalizer _localizer;
public DirectorySearchService(IConfiguration configuration, ILogger<DirectorySearchService> logger, IMemoryCache memoryCache, IStringLocalizer defaultLocalizer) : base(defaultLocalizer) public DirectorySearchService(IConfiguration configuration, ILogger<DirectorySearchService> logger, IMemoryCache memoryCache, IStringLocalizer localizer)
{ {
_memoryCache = memoryCache; _memoryCache = memoryCache;
@ -37,6 +39,8 @@ namespace DigitalData.Core.Application
_userCacheExpiration = default; _userCacheExpiration = default;
else else
_userCacheExpiration = DateTimeOffset.Now.Date.AddDays(dayCounts); _userCacheExpiration = DateTimeOffset.Now.Date.AddDays(dayCounts);
_localizer = localizer;
} }
public bool ValidateCredentials(string dirEntryUsername, string dirEntryPassword) public bool ValidateCredentials(string dirEntryUsername, string dirEntryPassword)
@ -45,7 +49,7 @@ namespace DigitalData.Core.Application
return context.ValidateCredentials(dirEntryUsername, dirEntryPassword); return context.ValidateCredentials(dirEntryUsername, dirEntryPassword);
} }
public IServiceResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties) public DataResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties)
{ {
List<ResultPropertyCollection> list = new(); List<ResultPropertyCollection> list = new();
@ -71,17 +75,17 @@ namespace DigitalData.Core.Application
list.Add(rpc); list.Add(rpc);
} }
return Successful<IEnumerable<ResultPropertyCollection>>(list); return Result.Success<IEnumerable<ResultPropertyCollection>>(list);
} }
public IServiceResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties) public DataResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties)
{ {
List<ResultPropertyCollection> list = new(); List<ResultPropertyCollection> list = new();
_memoryCache.TryGetValue(username, out DirectoryEntry? searchRoot); _memoryCache.TryGetValue(username, out DirectoryEntry? searchRoot);
if (searchRoot is null) if (searchRoot is null)
return Failed<IEnumerable<ResultPropertyCollection>>(MessageKey.DirSearcherDisconnected.ToString()); return Result.Fail<IEnumerable<ResultPropertyCollection>>().Message(_localizer[Key.DirSearcherDisconnected]);
return FindAll(searchRoot, filter, searchScope, sizeLimit, properties); return FindAll(searchRoot, filter, searchScope, sizeLimit, properties);
} }

View File

@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DigitalData.Core.Application
{
/// <summary>
/// Provides extension methods for IEnumerable<T>.
/// </summary>
public static class EnumerableExtensions
{
/// <summary>
/// Concatenates the members of a collection, using the specified separator between each member.
/// </summary>
/// <typeparam name="T">The type of the elements of the enumerable.</typeparam>
/// <param name="enumerable">The IEnumerable<T> whose elements to concatenate.</param>
/// <param name="separator">The string to use as a separator. Separator is included in the returned string only between elements of the collection.</param>
/// <returns>A string that consists of the elements in the collection delimited by the separator string. If the collection is empty, the method returns String.Empty.</returns>
public static string Join<T>(this IEnumerable<T> enumerable, string separator = ". ") => string.Join(separator, enumerable);
}
}

View File

@ -0,0 +1,11 @@
namespace DigitalData.Core.Application
{
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";
}
}

View File

@ -1,41 +0,0 @@
using DigitalData.Core.Contracts.Application;
using Microsoft.Extensions.Logging;
namespace DigitalData.Core.Application
{
/// <summary>
/// Provides extension methods for ILogger to handle logging of message collections and service messages more effectively.
/// </summary>
public static class LoggerExtensions
{
/// <summary>
/// Logs a list of messages at the specified log level.
/// </summary>
/// <param name="logger">The extended ILogger instance.</param>
/// <param name="logLevel">The severity level of the log entry.</param>
/// <param name="messages">The collection of messages to log.</param>
/// <param name="separator">The separator used to join messages. Default is newline.</param>
/// <param name="args">Additional arguments used for formatting the log message.</param>
public static void LogMessageList(this ILogger logger, LogLevel logLevel, IEnumerable<string> messages, string separator = "\n", params object?[] args)
{
if (messages.Any())
logger.Log(logLevel: logLevel, message: string.Join(separator, messages), args);
}
/// <summary>
/// Logs all messages from a service message instance, categorized by message type to appropriate log levels.
/// </summary>
/// <param name="logger">The extended ILogger instance.</param>
/// <param name="serviceMessage">The service message instance containing categorized messages.</param>
/// <param name="separator">The separator used to join messages within each category. Default is newline.</param>
public static void LogServiceMessage(this ILogger logger, IServiceMessage serviceMessage, string separator = "\n")
{
logger.LogMessageList(LogLevel.Trace, serviceMessage.TraceMessages, separator);
logger.LogMessageList(LogLevel.Debug, serviceMessage.DebugMessages, separator);
logger.LogMessageList(LogLevel.Information, serviceMessage.InformationMessages, separator);
logger.LogMessageList(LogLevel.Warning, serviceMessage.WarningMessages, separator);
logger.LogMessageList(LogLevel.Error, serviceMessage.ErrorMessages, separator);
logger.LogMessageList(LogLevel.Critical, serviceMessage.CriticalMessages, separator);
}
}
}

View File

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DigitalData.Core.Application
{
public enum MessageKey
{
EntityDoesNotExist,
ReadFailed,
UpdateFailed,
DeletionFailed,
DirSearcherDisconnected
}
}

View File

@ -1,211 +0,0 @@
using DigitalData.Core.Contracts.Application;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
public class ResponseService : IResponseService
{
protected readonly IStringLocalizer _localizer;
public ResponseService(IStringLocalizer defaultLocalizer)
{
_localizer = defaultLocalizer;
}
#region WITHOUT_MESSAGE
/// <summary>
/// Creates a service message indicating success or failure without additional messages.
/// </summary>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <returns>A service message reflecting the operation outcome.</returns>
public IServiceMessage CreateMessage(bool isSuccess = false) => new ServiceMessage()
{
IsSuccess = isSuccess
};
/// <summary>
/// Creates a service result containing the provided data and a success flag, without additional messages.
/// </summary>
/// <typeparam name="T">The type of the data included in the result.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <returns>A service result with the specified data and outcome.</returns>
public IServiceResult<T> CreateResult<T>(T? data = default, bool isSuccess = false) => new ServiceResult<T>()
{
IsSuccess = isSuccess,
Data = data
};
/// <summary>
/// Creates a service message indicating a successful operation without additional messages.
/// </summary>
/// <returns>A service message indicating a successful operation.</returns>
public IServiceMessage Successful() => CreateMessage(true);
/// <summary>
/// Creates a service message indicating a failed operation without additional messages.
/// </summary>
/// <returns>A service message indicating a failed operation.</returns>
public IServiceMessage Failed() => CreateMessage(false);
/// <summary>
/// Creates a successful service result with the specified data, without additional messages.
/// </summary>
/// <typeparam name="T">The type of data included in the result.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <returns>A successful service result containing the specified data.</returns>
public IServiceResult<T> Successful<T>(T data) => CreateResult(data, true);
/// <summary>
/// Creates a failed service result with optional data, without additional messages.
/// </summary>
/// <typeparam name="T">The type of data the service result can contain.</typeparam>
/// <param name="data">Optional data to include in the result.</param>
/// <returns>A failed service result, which may or may not contain the specified data.</returns>
public IServiceResult<T> Failed<T>(T? data = default) => CreateResult(data, false);
#endregion
#region WITH_STRING_MESSAGE
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a service message with the specified success flag and messages.
/// </summary>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">An array of messages associated with the operation.</param>
/// <returns>A new instance of <see cref="ServiceMessage"/> reflecting the operation outcome.</returns>
public virtual IServiceMessage CreateMessage(bool isSuccess, params string[] messages) => new ServiceMessage(isSuccess, messages);
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a service result containing the provided data, success flag, and messages.
/// </summary>
/// <typeparam name="T">The type of the data included in the result.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">An array of messages associated with the operation.</param>
/// <returns>A new instance of <see cref="ServiceResult{T}"/> with the specified data and outcome.</returns>
public virtual IServiceResult<T> CreateResult<T>(T? data = default, bool isSuccess = true, params string[] messages) => new ServiceResult<T>(data, isSuccess, messages);
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a successful service message.
/// </summary>
/// <param name="messages">An array of success messages.</param>
/// <returns>A successful service message.</returns>
public virtual IServiceMessage Successful(params string[] messages) => CreateMessage(true, messages);
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a failed service message.
/// </summary>
/// <param name="messages">An array of failure messages.</param>
/// <returns>A failed service message.</returns>
public virtual IServiceMessage Failed(params string[] messages) => CreateMessage(false, messages);
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a successful service result with the provided data.
/// </summary>
/// <typeparam name="T">The type of data included in the result.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <param name="messages">An array of success messages.</param>
/// <returns>A successful service result containing the specified data.</returns>
public virtual IServiceResult<T> Successful<T>(T data, params string[] messages) => CreateResult(data, true, messages);
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a failed service result, optionally including data.
/// </summary>
/// <typeparam name="T">The type of data the service result can contain.</typeparam>
/// <param name="data">Optional data to include in the failed result.</param>
/// <param name="messages">An array of failure messages.</param>
/// <returns>A failed service result, which may or may not contain the specified data.</returns>
public virtual IServiceResult<T> Failed<T>(T? data = default, params string[] messages) => CreateResult(data, false, messages);
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a failed service result using only failure messages, without explicitly including data.
/// </summary>
/// <remarks>
/// This method provides a convenient way to create a failed service result when the failure does not pertain to any specific data,
/// or when the inclusion of data is not necessary to convey the failure reason. The data part of the result will be set to its default value.
/// </remarks>
/// <typeparam name="T">The type of data the service result can contain. The result will contain the default value for this type.</typeparam>
/// <param name="messages">An array of failure messages that provide details about the reasons for the operation's failure.</param>
/// <returns>A failed service result. The data part of the result will be set to the default value for the specified type,
/// and it will include the provided failure messages.</returns>
public virtual IServiceResult<T> Failed<T>(params string[] messages) => Failed<T>(default, messages);
#endregion
#region WITH_ENUM_MESSAGE
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a service message with the specified success flag and enumeration messages.
/// </summary>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">An array of enumeration values associated with the operation.</param>
/// <returns>A new instance of <see cref="ServiceMessage"/> reflecting the operation outcome with enumeration messages.</returns>
public IServiceMessage CreateMessage(bool isSuccess, params Enum[] messages) => CreateMessage(isSuccess, messages.Select(m => m.ToString()).ToArray());
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a service result containing the provided data, success flag, and enumeration messages.
/// </summary>
/// <typeparam name="T">The type of the data included in the result.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">An array of enumeration values associated with the operation.</param>
/// <returns>A new instance of <see cref="ServiceResult{T}"/> with the specified data and outcome using enumeration messages.</returns>
public IServiceResult<T> CreateResult<T>(T? data, bool isSuccess, params Enum[] messages) => CreateResult(data, isSuccess, messages.Select(m => m.ToString()).ToArray());
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a successful service message using enumeration messages.
/// </summary>
/// <param name="messages">An array of success enumeration values.</param>
/// <returns>A successful service message.</returns>
public IServiceMessage Successful(params Enum[] messages) => CreateMessage(true, messages.Select(m => m.ToString()).ToArray());
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a failed service message using enumeration messages.
/// </summary>
/// <param name="messages">An array of failure enumeration values.</param>
/// <returns>A failed service message.</returns>
public IServiceMessage Failed(params Enum[] messages) => CreateMessage(false, messages.Select(m => m.ToString()).ToArray());
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a successful service result with the provided data using enumeration messages.
/// </summary>
/// <typeparam name="T">The type of data included in the result.</typeparam>
/// <param name="data">The data to include in the result.</param>
/// <param name="messages">An array of success enumeration values.</param>
/// <returns>A successful service result containing the specified data.</returns>
public IServiceResult<T> Successful<T>(T data, params Enum[] messages) => CreateResult(data, true, messages.Select(m => m.ToString()).ToArray());
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a failed service result, optionally including data, using enumeration messages.
/// </summary>
/// <typeparam name="T">The type of data the service result can contain.</typeparam>
/// <param name="data">Optional data to include in the failed result.</param>
/// <param name="messages">An array of failure enumeration values.</param>
/// <returns>A failed service result, which may or may not contain the specified data.</returns>
public IServiceResult<T> Failed<T>(T? data = default, params Enum[] messages) => CreateResult(data, false, messages.Select(m => m.ToString()).ToArray());
[Obsolete("Deprecated: Use ClientMessages instead.")]
/// <summary>
/// Creates a failed service result using only failure messages, without explicitly including data.
/// </summary>
/// <typeparam name="T">The type of data the service result can contain. The result will contain the default value for this type.</typeparam>
/// <param name="messages">An array of failure enumeration values that provide details about the reasons for the operation's failure.</param>
/// <returns>A failed service result. The data part of the result will be set to the default value for the specified type,
/// and it will include the provided failure messages.</returns>
public IServiceResult<T> Failed<T>(params Enum[] messages) => Failed<T>(default(T), messages);
#endregion
}
}

View File

@ -1,16 +0,0 @@
using AutoMapper;
using DigitalData.Core.Contracts.Application;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
/// <summary>
/// Provides a base implementation of <see cref="IServiceBase"/>.
/// </summary>
public class ServiceBase : ResponseService, IServiceBase, IResponseService
{
public ServiceBase(IStringLocalizer defaultLocalizer) : base(defaultLocalizer)
{
}
}
}

View File

@ -1,138 +0,0 @@
using DigitalData.Core.Contracts.Application;
using System.Text.Json.Serialization;
namespace DigitalData.Core.Application
{
/// <summary>
/// Represents the outcome of a service operation, encapsulating the success or failure status,
/// and any associated messages. It also supports optional translation of message keys for localization purposes.
/// </summary>
public class ServiceMessage : IServiceMessage
{
/// <summary>
/// Initializes a new instance of the ServiceMessage class.
/// </summary>
public ServiceMessage()
{
}
/// <summary>
/// Initializes a new instance of the ServiceMessage class, specifying the success status.
/// Optionally, a function for translating message keys can be provided.
/// If a translation function is provided, it will be used for both string and enum message keys.
/// </summary>
/// <param name="isSuccess">Indicates whether the service operation was successful.</param>
/// If provided, it will also be adapted for translating enum keys by converting them to strings first.</param>
[Obsolete("Deprecated: initialize objects using object initializer.")]
public ServiceMessage(bool isSuccess)
{
IsSuccess = isSuccess;
}
/// <summary>
/// Initializes a new instance of the ServiceMessage class with specified success status, and messages.
/// </summary>
/// <param name="isSuccess">Indicates whether the service operation was successful.</param>
/// <param name="messages">An array of messages related to the operation's outcome.</param>
[Obsolete("Deprecated: initialize objects using object initializer.")]
public ServiceMessage(bool isSuccess, params string[] messages)
{
IsSuccess = isSuccess;
Messages = messages.ToList<string>();
}
/// <summary>
/// Gets or sets a value indicating whether the service operation was successful.
/// </summary>
public bool IsSuccess { get; set; } = false;
/// <summary>
/// Represents the list of flags that indicate specific types of statuses or conditions in a service operation.
/// These flags help in categorizing the state of the operation more granularly, allowing for multiple conditions to be represented simultaneously.
/// </summary>
[JsonIgnore]
public ICollection<Enum> Flags { get; } = new List<Enum>();
/// <summary>
/// Checks if any of the current service message's flags matches the specified flag based on their string representations.
/// This method is useful for conditional logic where the exact string representation of the flag values is crucial.
/// </summary>
/// <param name="flag">The flag to check against the current service message's flags.</param>
/// <returns>true if a flag with a matching string representation exists; otherwise, false.</returns>
public bool HasFlag(Enum flag) => Flags.Any(f => f.ToString() == flag.ToString());
/// <summary>
/// [Obsolete("Deprecated: Use ClientMessages instead.")]
/// Gets a collection of messages associated with the service operation. These messages can be error descriptions,
/// success notifications, or other relevant information related to the operation's outcome.
/// </summary>
[Obsolete("Deprecated: Use ClientMessages instead.")]
public ICollection<string> Messages { get; init; } = new List<string>();
/// <summary>
/// Gets a collection of messages intended for client display. This replaces the deprecated 'Messages' property.
/// </summary>
public ICollection<string> ClientMessages { get; init; } = new List<string>();
/// <summary>
/// Gets a collection of messages used for tracing program execution at a fine-grained level. These are typically voluminous and detailed.
/// </summary>
[JsonIgnore]
public ICollection<string> TraceMessages { get; init; } = new List<string>();
/// <summary>
/// Gets a collection of messages helpful for debugging during development. These messages are often diagnostic.
/// </summary>
[JsonIgnore]
public ICollection<string> DebugMessages { get; init; } = new List<string>();
/// <summary>
/// Gets a collection of informational messages, less critical than warnings, generally used for non-critical notifications.
/// </summary>
[JsonIgnore]
public ICollection<string> InformationMessages { get; init; } = new List<string>();
/// <summary>
/// Gets a collection of messages indicating potential issues that are not necessarily errors, but which may require attention.
/// </summary>
[JsonIgnore]
public ICollection<string> WarningMessages { get; init; } = new List<string>();
/// <summary>
/// Gets a collection of error messages indicating failures or problems within the service.
/// </summary>
[JsonIgnore]
public ICollection<string> ErrorMessages { get; init; } = new List<string>();
/// <summary>
/// Gets a collection of messages indicating critical issues that require immediate attention.
/// </summary>
[JsonIgnore]
public ICollection<string> CriticalMessages { get; init; } = new List<string>();
/// <summary>
/// Adds a new message to the collection of messages associated with the service operation.
/// </summary>
/// <param name="message">The message to add.</param>
/// <returns>The current instance of ServiceMessage, allowing for method chaining.</returns>
[Obsolete("Deprecated: Use ClientMessages instead.")]
public IServiceMessage WithMessage(string message)
{
Messages.Add(message);
return this;
}
/// <summary>
/// Adds a message corresponding to the specified message key to the collection of messages associated with the service operation.
/// This method uses the string representation of the enum value as the message.
/// </summary>
/// <param name="messageKey">The enum value representing the message key.</param>
/// <returns>The current instance of ServiceMessage, allowing for method chaining.</returns>
[Obsolete("Deprecated: Use ClientMessages instead.")]
public IServiceMessage WithMessageKey(Enum messageKey)
{
Messages.Add(messageKey.ToString());
return this;
}
}
}

View File

@ -1,153 +0,0 @@
using DigitalData.Core.Contracts.Application;
using Microsoft.Extensions.Localization;
namespace DigitalData.Core.Application
{
/// <summary>
/// Provides extension methods for IServiceMessage to enhance the usability of service messages
/// by allowing easy addition of various types of messages including client, trace, debug, information,
/// warning, error, and critical messages. These methods support both direct messages and enum-based keys
/// for messages, facilitating localized or custom-formatted messages.
/// </summary>
public static class ServiceMessageExtensions
{
#region Flag
/// <summary>
/// Sets the specified flag on the service message or result, allowing for the categorization of the message based on specific conditions or statuses.
/// </summary>
/// <typeparam name="T">The type of IServiceMessage.</typeparam>
/// <param name="serviceMessage">The service message instance to modify.</param>
/// <param name="flag">The flag to set, indicating a specific condition or status.</param>
/// <returns>The service message instance with the updated flag, facilitating fluent chaining of methods.</returns>
public static T WithFlag<T>(this T serviceMessage, Enum flag) where T : IServiceMessage
{
serviceMessage.Flags.Add(flag);
return serviceMessage;
}
/// <summary>
/// Determines whether the service message has a flag indicating a security breach.
/// </summary>
/// <param name="serviceMessage">The service message instance to check.</param>
/// <returns>True if the service message has the security breach flag; otherwise, false.</returns>
public static bool HasSecurityBreachFlag(this IServiceMessage serviceMessage) => serviceMessage.HasFlag(Flag.SecurityBreach);
/// <summary>
/// Determines whether the service message has a flag indicating a data integrity issue.
/// </summary>
/// <param name="serviceMessage">The service message instance to check.</param>
/// <returns>True if the service message has the data integrity issue flag; otherwise, false.</returns>
public static bool HasDataIntegrityIssueFlag(this IServiceMessage serviceMessage) => serviceMessage.HasFlag(Flag.DataIntegrityIssue);
/// <summary>
/// Determines whether the service message has a flag indicating either a security breach or a data integrity issue, or both.
/// 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>
/// <param name="serviceMessage">The service message instance to check.</param>
/// <returns>True if the service message has the flag indicating either or both issues; otherwise, false.</returns>
public static bool HasSecurityBreachOrDataIntegrityFlag(this IServiceMessage serviceMessage) => serviceMessage.HasFlag(Flag.SecurityBreachOrDataIntegrity);
#endregion
#region StringLocalizer
/// <summary>
/// Retrieves the localized string for the specified key.
/// </summary>
/// <param name="key">The key for the localized string.</param>
/// <param name="localizer">The localizer to use for retrieving the localized string.</param>
/// <returns>The localized string associated with the specified key.</returns>
public static string LocalizedBy(this string key, IStringLocalizer localizer) => localizer[key];
/// <summary>
/// Retrieves the localized string for the specified enumeration key.
/// </summary>
/// <param name="key">The enumeration key for the localized string.</param>
/// <param name="localizer">The localizer to use for retrieving the localized string.</param>
/// <returns>The localized string associated with the specified enumeration key.</returns>
public static string LocalizedBy(this Enum key, IStringLocalizer localizer) => localizer[key.ToString()];
#endregion
#region ClientMessage
/// <summary>
/// Adds a single client message to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="message">The message to add.</param>
/// <returns>The modified service message instance.</returns>
public static T WithClientMessage<T>(this T serviceMessage, string message) where T : IServiceMessage
{
serviceMessage.ClientMessages.Add(message);
return serviceMessage;
}
#endregion
#region TraceMessages
/// <summary>
/// Adds a trace message to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="message">The trace message to add.</param>
/// <returns>The modified service message instance.</returns>
public static T WithTraceMessage<T>(this T serviceMessage, string message) where T : IServiceMessage
{
serviceMessage.TraceMessages.Add(message);
return serviceMessage;
}
#endregion
#region DebugMessages
/// <summary>
/// Adds a debug message to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="message">The debug message to add.</param>
/// <returns>The modified service message instance.</returns>
public static T WithDebugMessage<T>(this T serviceMessage, string message) where T : IServiceMessage
{
serviceMessage.DebugMessages.Add(message);
return serviceMessage;
}
#endregion
#region WarningMessages
/// <summary>
/// Adds a warning message to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="message">The warning message to add.</param>
/// <returns>The modified service message instance.</returns>
public static T WithWarningMessage<T>(this T serviceMessage, string message) where T : IServiceMessage
{
serviceMessage.WarningMessages.Add(message);
return serviceMessage;
}
#endregion
#region ErrorMessages
/// <summary>
/// Adds an error message to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="message">The error message to add.</param>
/// <returns>The modified service message instance.</returns>
public static T WithErrorMessage<T>(this T serviceMessage, string message) where T : IServiceMessage
{
serviceMessage.ErrorMessages.Add(message);
return serviceMessage;
}
#endregion
#region CriticalMessages
/// <summary>
/// Adds a critical message to the service message.
/// </summary>
/// <param name="serviceMessage">The service message to modify.</param>
/// <param name="message">The critical message to add.</param>
/// <returns>The modified service message instance.</returns>
public static T WithCriticalMessage<T>(this T serviceMessage, string message) where T : IServiceMessage
{
serviceMessage.CriticalMessages.Add(message);
return serviceMessage;
}
#endregion
}
}

View File

@ -1,55 +0,0 @@
using DigitalData.Core.Contracts.Application;
namespace DigitalData.Core.Application
{
/// <summary>
/// Represents the outcome of a service operation, encapsulating the success or failure status,
/// the data returned by the operation, and any associated messages.
/// </summary>
/// <typeparam name="T">The type of data returned by the service operation, if any.</typeparam>
public class ServiceResult<T> : ServiceMessage, IServiceResult<T>
{
/// <summary>
/// Initializes a new instance of the ServiceResult class.
public ServiceResult()
{
}
/// <summary>
/// Initializes a new instance of the ServiceResult class, specifying the success status is false and data.
/// Optionally, a function for translating message keys can be provided.
/// If a translation function is provided, it will be used for both string and enum message keys.
/// </summary>
/// <param name="data">The data associated with a successful operation.</param>
/// <param name="stringKeyTranslator">A function that translates a string key into its localized representation.
/// If provided, it will also be adapted for translating enum keys by converting them to strings first.</param>
[Obsolete("Deprecated: initialize objects using object initializers instead.")]
public ServiceResult(T? data)
{
Data = data;
IsSuccess = false;
}
/// <summary>
/// Initializes a new instance of the ServiceResult class with specified success status and data.
/// </summary>
/// <param name="data">The data associated with a successful operation.</param>
/// <param name="isSuccess">Indicates whether the service operation was successful.</param>
[Obsolete("Deprecated: initialize objects using object initializers instead.")]
public ServiceResult(T? data, bool isSuccess) : base(isSuccess) => Data = data;
/// <summary>
/// Initializes a new instance of the ServiceResult class with specified success status, data, and messages.
/// </summary>
/// <param name="data">The data associated with a successful operation.</param>
/// <param name="isSuccess">Indicates whether the service operation was successful.</param>
/// <param name="messages">An array of messages related to the operation's outcome.</param>
[Obsolete("Deprecated: Use ClientMessages instead.")]
public ServiceResult(T? data, bool isSuccess, params string[] messages) : base(isSuccess, messages) => Data = data;
/// <summary>
/// Gets or sets the data resulting from the service operation.
/// </summary>
public T? Data { get; set; } = default;
}
}

View File

@ -1,43 +0,0 @@
namespace DigitalData.Core.Application
{
/// <summary>
/// Specifies the destination of a message.
/// </summary>
public enum To
{
/// <summary>
/// Indicates that the message is intended for the client.
/// </summary>
Client,
/// <summary>
/// Indicates that the message is intended for tracing purposes.
/// </summary>
Trace,
/// <summary>
/// Indicates that the message is intended for debugging purposes.
/// </summary>
Debug,
/// <summary>
/// Indicates that the message is informational.
/// </summary>
Information,
/// <summary>
/// Indicates that the message is a warning.
/// </summary>
Warning,
/// <summary>
/// Indicates that the message represents an error.
/// </summary>
Error,
/// <summary>
/// Indicates that the message represents a critical issue.
/// </summary>
Critical
}
}

View File

@ -1,58 +1,43 @@
using DigitalData.Core.Contracts.Infrastructure; using DigitalData.Core.Contracts.Infrastructure;
using DigitalData.Core.DTO;
namespace DigitalData.Core.Contracts.Application namespace DigitalData.Core.Contracts.Application
{ {
/// <summary> public interface ICRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId>
/// Defines the contract for CRUD operations at the service level using Data Transfer Objects (DTOs) for entities of type TEntity,
/// wrapped in an IServiceResult to encapsulate the outcome of the operation, including success, data, and error messages.
/// </summary>
/// <typeparam name="TCRUDRepository">The repository type that provides CRUD operations for entities of type TEntity.</typeparam>
/// <typeparam name="TCreateDto">The type of the Data Transfer Object this service works with to create new entity.</typeparam>
/// <typeparam name="TReadDto">The type of the Data Transfer Object this service works with to read new entity.</typeparam>
/// <typeparam name="TUpdateDto">The type of the Data Transfer Object this service works with to update new entity.</typeparam>
/// <typeparam name="TEntity">The type of the entity this service maps to.</typeparam>
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
public interface ICRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : IServiceBase
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : class where TEntity : class where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : class where TEntity : class
{ {
/// <summary> Task<DataResult<TId>> CreateAsync(TCreateDto createDto);
/// Creates a new entity based on the provided DTO and returns the result wrapped in an IServiceResult,
/// including the created entity on success or an error message on failure.
/// </summary>
/// <param name="createDto">The createDto to create a new entity from.</param>
/// <returns>An IServiceResult containing the id of created entity or an error message.</returns>
Task<IServiceResult<TId>> CreateAsync(TCreateDto createDto);
/// <summary> /// <summary>
/// Retrieves an entity by its identifier and returns its readDTO representation wrapped in an IServiceResult, /// Retrieves an entity by its identifier and returns its readDTO representation wrapped in an IServiceResult,
/// including the readDTO on success or null and an error message on failure. /// including the readDTO on success or null and an error message on failure.
/// </summary> /// </summary>
/// <param name="id">The identifier of the entity to retrieve.</param> /// <param name="id">The identifier of the entity to retrieve.</param>
/// <returns>An IServiceResult containing the readDTO representing the found entity or null, with an appropriate message.</returns> /// <returns>An DataResult containing the readDTO representing the found entity or null, with an appropriate message.</returns>
Task<IServiceResult<TReadDto>> ReadByIdAsync(TId id); Task<DataResult<TReadDto>> ReadByIdAsync(TId id);
/// <summary> /// <summary>
/// Retrieves all entities and returns their readDTO representations wrapped in an IServiceResult, /// Retrieves all entities and returns their readDTO representations wrapped in an IServiceResult,
/// including a collection of readDTOs on success or an error message on failure. /// including a collection of readDTOs on success or an error message on failure.
/// </summary> /// </summary>
/// <returns>An IServiceResult containing a collection of readDTOs representing all entities or an error message.</returns> /// <returns>An DataResult containing a collection of readDTOs representing all entities or an error message.</returns>
Task<IServiceResult<IEnumerable<TReadDto>>> ReadAllAsync(); Task<DataResult<IEnumerable<TReadDto>>> ReadAllAsync();
/// <summary> /// <summary>
/// Updates an existing entity based on the provided updateDTO and returns the result wrapped in an IServiceMessage, /// Updates an existing entity based on the provided updateDTO and returns the result wrapped in an IServiceMessage,
/// indicating the success or failure of the operation, including the error messages on failure. /// indicating the success or failure of the operation, including the error messages on failure.
/// </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 IServiceMessage 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<IServiceMessage> UpdateAsync(TUpdateDto updateDto); Task<Result> UpdateAsync(TUpdateDto updateDto);
/// <summary> /// <summary>
/// Deletes an entity by its identifier and returns the result wrapped in an IServiceMessage, /// Deletes an entity by its identifier and returns the result wrapped in an IServiceMessage,
/// indicating the success or failure of the operation, including the error messages on failure. /// indicating the success or failure of the operation, including the error messages on failure.
/// </summary> /// </summary>
/// <param name="id">The identifier of the entity to delete.</param> /// <param name="id">The identifier of the entity to delete.</param>
/// <returns>An IServiceMessage indicating the outcome of the delete operation, with an appropriate message.</returns> /// <returns>An Result indicating the outcome of the delete operation, with an appropriate message.</returns>
Task<IServiceMessage> DeleteAsyncById(TId id); Task<Result> DeleteAsyncById(TId id);
/// <summary> /// <summary>
/// Asynchronously checks if an entity with the specified identifier exists within the data store. /// Asynchronously checks if an entity with the specified identifier exists within the data store.

View File

@ -1,8 +1,9 @@
using System.DirectoryServices; using DigitalData.Core.DTO;
using System.DirectoryServices;
namespace DigitalData.Core.Contracts.Application namespace DigitalData.Core.Contracts.Application
{ {
public interface IDirectorySearchService : IServiceBase public interface IDirectorySearchService
{ {
public string ServerName { get; } public string ServerName { get; }
@ -14,9 +15,9 @@ namespace DigitalData.Core.Contracts.Application
bool ValidateCredentials(string dirEntryUsername, string dirEntryPassword); bool ValidateCredentials(string dirEntryUsername, string dirEntryPassword);
IServiceResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties); DataResult<IEnumerable<ResultPropertyCollection>> FindAll(DirectoryEntry searchRoot, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
IServiceResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties); DataResult<IEnumerable<ResultPropertyCollection>> FindAllByUserCache(string username, string filter, SearchScope searchScope = SearchScope.Subtree, int sizeLimit = 5000, params string[] properties);
void SetSearchRootCache(string username, string password); void SetSearchRootCache(string username, string password);

View File

@ -1,185 +0,0 @@
namespace DigitalData.Core.Contracts.Application
{
/// <summary>
/// Defines the base functionality for a service, including creating service messages and results.
/// </summary>
public interface IResponseService
{
#region WITHOUT_MESSAGE
/// <summary>
/// Creates a simple service message indicating success or failure without any additional messages.
/// </summary>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <returns>A service message indicating the outcome of the operation without any messages.</returns>
IServiceMessage CreateMessage(bool isSuccess);
/// <summary>
/// Creates a service result with the specified data and a success flag, without any additional messages.
/// </summary>
/// <typeparam name="T">The type of data the service result will contain.</typeparam>
/// <param name="data">The data for the service result.</param>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <returns>A service result containing the data and indicating the outcome of the operation without any messages.</returns>
IServiceResult<T> CreateResult<T>(T? data, bool isSuccess);
/// <summary>
/// Creates a service message indicating a successful operation without any messages.
/// </summary>
/// <returns>A service message indicating a successful operation.</returns>
IServiceMessage Successful();
/// <summary>
/// Creates a service message indicating a failed operation without any messages.
/// </summary>
/// <returns>A service message indicating a failed operation.</returns>
IServiceMessage Failed();
/// <summary>
/// Creates a successful service result with the specified data, without any messages.
/// </summary>
/// <typeparam name="T">The type of data the service result will contain.</typeparam>
/// <param name="data">The data to include in the service result.</param>
/// <returns>A successful service result containing the data, indicating a successful operation without any messages.</returns>
IServiceResult<T> Successful<T>(T data);
/// <summary>
/// Creates a failed service result, optionally including data, without any messages.
/// </summary>
/// <typeparam name="T">The type of data the service result can contain.</typeparam>
/// <param name="data">Optional data to include in the failed service result.</param>
/// <returns>A failed service result, which may or may not contain data, indicating a failed operation without any messages.</returns>
IServiceResult<T> Failed<T>(T? data = default);
#endregion
#region WITH_STRING_MESSAGE
/// <summary>
/// Creates a service message.
/// </summary>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">The messages associated with the operation.</param>
/// <returns>A service message indicating the outcome of the operation and any associated messages.</returns>
IServiceMessage CreateMessage(bool isSuccess, params string[] messages);
/// <summary>
/// Creates a service result with the specified data.
/// </summary>
/// <typeparam name="T">The type of data the service result will contain.</typeparam>
/// <param name="data">The data for the service result.</param>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">The messages associated with the operation.</param>
/// <returns>A service result containing the data and indicating the outcome of the operation.</returns>
IServiceResult<T> CreateResult<T>(T? data, bool isSuccess, params string[] messages);
/// <summary>
/// Creates a successful service message.
/// </summary>
/// <param name="messages">The success messages.</param>
/// <returns>A successful service message.</returns>
IServiceMessage Successful(params string[] messages);
/// <summary>
/// Creates a failed service message.
/// </summary>
/// <param name="messages">The failure messages.</param>
/// <returns>A failed service message.</returns>
IServiceMessage Failed(params string[] messages);
/// <summary>
/// Creates a successful service result with the specified data.
/// </summary>
/// <typeparam name="T">The type of data the service result will contain.</typeparam>
/// <param name="data">The data to include in the service result.</param>
/// <param name="messages">The success messages.</param>
/// <returns>A successful service result containing the data.</returns>
IServiceResult<T> Successful<T>(T data, params string[] messages);
/// <summary>
/// Creates a failed service result, optionally including data.
/// </summary>
/// <typeparam name="T">The type of data the service result can contain.</typeparam>
/// <param name="data">Optional data to include in the failed service result.</param>
/// <param name="messages">The failure messages.</param>
/// <returns>A failed service result, which may or may not contain data.</returns>
IServiceResult<T> Failed<T>(T? data = default, params string[] messages);
/// <summary>
/// Creates a failed service result without explicitly including data, using only failure messages.
/// </summary>
/// <remarks>
/// This overload is useful when you want to indicate a failure without the need to return any specific data,
/// but still want to provide details about the failure through messages.
/// </remarks>
/// <typeparam name="T">The type of data the service result can contain. The result will contain the default value for this type.</typeparam>
/// <param name="messages">An array of failure messages associated with the operation. These provide detail about why the operation failed.</param>
/// <returns>A failed service result. The data part of the result will be set to the default value for the specified type.</returns>
IServiceResult<T> Failed<T>(params string[] messages);
#endregion
#region WITH_ENUM_MESSAGE
/// <summary>
/// Creates a service message using enumeration values.
/// </summary>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">The enumeration values associated with the operation.</param>
/// <returns>A service message indicating the outcome of the operation and any associated enumeration values.</returns>
IServiceMessage CreateMessage(bool isSuccess, params Enum[] messages);
/// <summary>
/// Creates a service result with the specified data and enumeration values for messages.
/// </summary>
/// <typeparam name="T">The type of data the service result will contain.</typeparam>
/// <param name="data">The data for the service result.</param>
/// <param name="isSuccess">Indicates if the operation was successful.</param>
/// <param name="messages">The enumeration values associated with the operation.</param>
/// <returns>A service result containing the data and indicating the outcome of the operation with enumeration values.</returns>
IServiceResult<T> CreateResult<T>(T? data, bool isSuccess, params Enum[] messages);
/// <summary>
/// Creates a successful service message using enumeration values.
/// </summary>
/// <param name="messages">The success enumeration values.</param>
/// <returns>A successful service message.</returns>
IServiceMessage Successful(params Enum[] messages);
/// <summary>
/// Creates a failed service message using enumeration values.
/// </summary>
/// <param name="messages">The failure enumeration values.</param>
/// <returns>A failed service message.</returns>
IServiceMessage Failed(params Enum[] messages);
/// <summary>
/// Creates a successful service result with the specified data, using enumeration values for messages.
/// </summary>
/// <typeparam name="T">The type of data the service result will contain.</typeparam>
/// <param name="data">The data to include in the service result.</param>
/// <param name="messages">The success enumeration values.</param>
/// <returns>A successful service result containing the data.</returns>
IServiceResult<T> Successful<T>(T data, params Enum[] messages);
/// <summary>
/// Creates a failed service result, optionally including data, using enumeration values for messages.
/// </summary>
/// <typeparam name="T">The type of data the service result can contain.</typeparam>
/// <param name="data">Optional data to include in the failed service result.</param>
/// <param name="messages">The failure enumeration values.</param>
/// <returns>A failed service result, which may or may not contain data.</returns>
IServiceResult<T> Failed<T>(T? data = default, params Enum[] messages);
/// <summary>
/// Creates a failed service result without explicitly including data, using only enumeration values for failure messages.
/// </summary>
/// <remarks>
/// This overload is useful when you want to indicate a failure without the need to return any specific data,
/// but still want to provide details about the failure through enumeration values.
/// </remarks>
/// <typeparam name="T">The type of data the service result can contain. The result will contain the default value for this type.</typeparam>
/// <param name="messages">An array of enumeration values associated with the operation. These provide detail about why the operation failed.</param>
/// <returns>A failed service result. The data part of the result will be set to the default value for the specified type.</returns>
IServiceResult<T> Failed<T>(params Enum[] messages);
#endregion
}
}

View File

@ -1,6 +0,0 @@
namespace DigitalData.Core.Contracts.Application
{
public interface IServiceBase : IResponseService
{
}
}

View File

@ -1,95 +0,0 @@
using System.Text.Json.Serialization;
namespace DigitalData.Core.Contracts.Application
{
/// <summary>
/// Defines a structured format for service messages, categorizing them into success indicators and various types of messages.
/// This interface segregates messages into client-facing messages and different levels of logging messages, facilitating targeted communications and diagnostics.
/// Properties are initialized once per instance and cannot be modified afterwards, promoting immutability.
/// </summary>
public interface IServiceMessage
{
/// <summary>
/// Gets or sets a value indicating whether the service operation was successful.
/// </summary>
bool IsSuccess { get; set; }
/// <summary>
/// Represents the list of flags that indicate specific types of statuses or conditions in a service operation.
/// These flags help in categorizing the state of the operation more granularly, allowing for multiple conditions to be represented simultaneously.
/// </summary>
[JsonIgnore]
public ICollection<Enum> Flags { get; }
/// <summary>
/// Checks if any of the current service message's flags matches the specified flag based on their string representations.
/// This method is useful for conditional logic where the exact string representation of the flag values is crucial.
/// </summary>
/// <param name="flag">The flag to check against the current service message's flags.</param>
/// <returns>true if a flag with a matching string representation exists; otherwise, false.</returns>
public bool HasFlag(Enum flag) => Flags.Any(f => f.ToString() == flag.ToString());
/// <summary>
/// [Obsolete("Deprecated: Use ClientMessages instead.")]
/// Gets a collection of messages associated with the service operation. These messages can be error descriptions,
/// success notifications, or other relevant information related to the operation's outcome.
/// </summary>
[Obsolete("Deprecated: Use ClientMessages instead.")]
ICollection<string> Messages { get; init; }
/// <summary>
/// Gets a collection of messages intended for client display. This replaces the deprecated 'Messages' property.
/// </summary>
ICollection<string> ClientMessages { get; init; }
/// <summary>
/// Gets a collection of messages used for tracing program execution at a fine-grained level. These are typically voluminous and detailed.
/// </summary>
[JsonIgnore]
ICollection<string> TraceMessages { get; init; }
/// <summary>
/// Gets a collection of messages helpful for debugging during development. These messages are often diagnostic.
/// </summary>
[JsonIgnore]
ICollection<string> DebugMessages { get; init; }
/// <summary>
/// Gets a collection of informational messages, less critical than warnings, generally used for non-critical notifications.
/// </summary>
[JsonIgnore]
ICollection<string> InformationMessages { get; init; }
/// <summary>
/// Gets a collection of messages indicating potential issues that are not necessarily errors, but which may require attention.
/// </summary>
[JsonIgnore]
ICollection<string> WarningMessages { get; init; }
/// <summary>
/// Gets a collection of error messages indicating failures or problems within the service.
/// </summary>
[JsonIgnore]
ICollection<string> ErrorMessages { get; init; }
/// <summary>
/// Gets a collection of messages indicating critical issues that require immediate attention.
/// </summary>
[JsonIgnore]
ICollection<string> CriticalMessages { get; init; }
/// <summary>
/// Adds a new message to the appropriate collection based on the message type.
/// </summary>
/// <param name="message">The message to add.</param>
/// <returns>The current instance of IServiceMessage, allowing for method chaining.</returns>
IServiceMessage WithMessage(string message);
/// <summary>
/// Adds a message corresponding to a specified message key to the appropriate collection, facilitating localization and standardization.
/// </summary>
/// <param name="messageKey">The enum value representing the message key.</param>
/// <returns>The current instance of IServiceMessage, allowing for method chaining.</returns>
IServiceMessage WithMessageKey(Enum messageKey);
}
}

View File

@ -1,19 +0,0 @@
namespace DigitalData.Core.Contracts.Application
{
/// <summary>
/// Represents the outcome of a service operation, extending IServiceMessage with the addition of a data payload.
/// This interface is generic, allowing for the specification of the type of data returned by the service operation.
/// It is used to communicate not just the success or failure of an operation, but also to return any relevant data
/// along with the operation's outcome.
/// </summary>
/// <typeparam name="T">The type of the data associated with the service operation's outcome. This could be a model,
/// a collection of models, or any other type relevant to the operation.</typeparam>
public interface IServiceResult<T> : IServiceMessage
{
/// <summary>
/// Gets or sets the data resulting from the service operation. This property is nullable to accommodate operations
/// that might not return data upon failure.
/// </summary>
T? Data { get; set; }
}
}

View File

@ -24,6 +24,10 @@
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" /> <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.5.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.DTO\DigitalData.Core.DTO.csproj" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="icon.png"> <None Update="icon.png">
<CopyToOutputDirectory>Never</CopyToOutputDirectory> <CopyToOutputDirectory>Never</CopyToOutputDirectory>

View File

@ -0,0 +1,7 @@
namespace DigitalData.Core.Contracts
{
internal interface IError
{
}
}

View File

@ -1,6 +1,6 @@
using AutoMapper; using AutoMapper;
namespace DigitalData.Core.Application namespace DigitalData.Core.DTO
{ {
public static class AutoMapperExtension public static class AutoMapperExtension
{ {

View File

@ -1,4 +1,4 @@
namespace DigitalData.Core.Application.DTO namespace DigitalData.Core.DTO
{ {
/// <summary> /// <summary>
/// Represents settings related to user cookie consent dialogs. Designed to be serialized into JSON format for use with JavaScript frontend libraries, /// Represents settings related to user cookie consent dialogs. Designed to be serialized into JSON format for use with JavaScript frontend libraries,

View File

@ -0,0 +1,22 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System.Configuration;
namespace DigitalData.Core.DTO
{
public static class DIExtensions
{
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,110 @@
using Microsoft.Extensions.Logging;
using System.Text;
namespace DigitalData.Core.DTO
{
public static class DTOExtensions
{
public static T Message<T>(this T result, string message) where T : Result
{
result.Messages.Add(message);
return result;
}
public static T Message<T>(this T result, params string[] messages) where T : Result
{
result.Messages.AddRange(messages);
return result;
}
public static T Notice<T>(this T result, Notice notice) where T : Result
{
result.Notices.Add(notice);
return result;
}
public static T Notice<T>(this T result, params Notice[] notices) where T : Result
{
result.Notices.AddRange(notices);
return result;
}
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;
}
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.ToList()
});
return result;
}
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.ToList()
});
return result;
}
public static I Then<I>(this Result result, Func<I> Try, Func<List<string>, List<Notice>, I> Catch)
{
return result.IsSuccess ? Try() : Catch(result.Messages, result.Notices);
}
public static I Then<T, I>(this DataResult<T> result, Func<T, I> Try, Func<List<string>, List<Notice>, I> Catch)
{
return result.IsSuccess ? Try(result.Data) : Catch(result.Messages, result.Notices);
}
public static async Task<I> Then<I>(this Task<Result> tResult, Func<I> Try, Func<List<string>, List<Notice>, I> Catch)
{
Result result = await tResult;
return result.IsSuccess ? Try() : Catch(result.Messages, result.Notices);
}
public static async Task<I> Then<T, I>(this Task<DataResult<T>> tResult, Func<T, I> Try, Func<List<string>, List<Notice>, I> Catch)
{
DataResult<T> result = await tResult;
return result.IsSuccess ? Try(result.Data) : Catch(result.Messages, result.Notices);
}
public static void LogNotice<T>(this ILogger<T> 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());
}
}
}
}

View File

@ -0,0 +1,10 @@
using System.Text.Json.Serialization;
namespace DigitalData.Core.DTO
{
public class DataResult<T> : Result
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public required T Data { get; init; }
}
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
</ItemGroup>
</Project>

View File

@ -1,4 +1,4 @@
namespace DigitalData.Core.Application namespace DigitalData.Core.DTO
{ {
/// <summary> /// <summary>
/// Defines flags that indicate specific types of status or conditions in a service operation. /// Defines flags that indicate specific types of status or conditions in a service operation.

View File

@ -0,0 +1,11 @@
using Microsoft.Extensions.Logging;
namespace DigitalData.Core.DTO
{
public class Notice
{
public Enum? Flag { get; init; } = null;
public LogLevel Level { get; init; } = LogLevel.None;
public List<string> Messages { get; init; } = new();
}
}

View File

@ -0,0 +1,40 @@
using System.Text.Json.Serialization;
namespace DigitalData.Core.DTO
{
public class Result
{
public bool IsSuccess { get; set; } = false;
public List<string> Messages { get; init; } = new();
[JsonIgnore]
public List<Notice> Notices = new();
public DataResult<T> Data<T>(T data) => new()
{
IsSuccess = IsSuccess,
Messages = Messages,
Notices = Notices,
Data = data
};
public static Result Success() => new() { IsSuccess = true };
public static Result Fail() => new() { IsSuccess = false };
public static DataResult<T> Success<T>(T data) => new()
{
IsSuccess = true,
Data = data
};
#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

@ -78,5 +78,11 @@ namespace DigitalData.Core.Infrastructure
var result = await _dbContext.SaveChangesAsync(); var result = await _dbContext.SaveChangesAsync();
return result > 0; return result > 0;
} }
/// <summary>
/// Asynchronously counts all entities in the repository.
/// </summary>
/// <returns>The total number of entities in the repository.</returns>
public virtual async Task<int> CountAsync() => await _dbSet.CountAsync();
} }
} }

View File

@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.API", "Dig
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Tests", "DigitalData.Core.Tests\DigitalData.Core.Tests.csproj", "{B54DEF90-C30C-44EA-9875-76F1B330CBB7}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DigitalData.Core.Tests", "DigitalData.Core.Tests\DigitalData.Core.Tests.csproj", "{B54DEF90-C30C-44EA-9875-76F1B330CBB7}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DigitalData.Core.DTO", "DigitalData.Core.DTO\DigitalData.Core.DTO.csproj", "{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -39,6 +41,10 @@ Global
{B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Debug|Any CPU.Build.0 = Debug|Any CPU {B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Release|Any CPU.ActiveCfg = Release|Any CPU {B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Release|Any CPU.Build.0 = Release|Any CPU {B54DEF90-C30C-44EA-9875-76F1B330CBB7}.Release|Any CPU.Build.0 = Release|Any CPU
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0B051A5F-BD38-47D1-BAFF-D44BA30D3FB7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE