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

@@ -0,0 +1,22 @@
using AutoMapper;
namespace DigitalData.Core.DTO
{
public static class AutoMapperExtension
{
/// <summary>
/// Maps a source object to a destination object, or throws an exception if the mapping result is null.
/// </summary>
/// <typeparam name="TSource">The source object type.</typeparam>
/// <typeparam name="TDestination">The destination object type.</typeparam>
/// <param name="source">The source object to map from.</param>
/// <returns>The mapped destination object.</returns>
/// <exception cref="MappingResultNullException">Thrown when the mapping result is null.</exception>
public static TDestination MapOrThrow<TDestination>(this IMapper mapper, object source)
{
return mapper.Map<TDestination>(source) ?? throw new AutoMapperMappingException(
$"Mapping to {typeof(TDestination).FullName} resulted in a null object. " +
"Hint: Ensure that the AutoMapper profile configuration for this mapping is correct.");
}
}
}

View File

@@ -0,0 +1,74 @@
namespace DigitalData.Core.DTO
{
/// <summary>
/// Represents settings related to user cookie consent dialogs. Designed to be serialized into JSON format for use with JavaScript frontend libraries,
/// such as the bootstrap-cookie-consent-settings at the GitHub repository: https://github.com/shaack/bootstrap-cookie-consent-settings
/// </summary>
public class CookieConsentSettings
{
/// <summary>
/// URL to the privacy policy page.
/// </summary>
public string? PrivacyPolicyUrl { get; set; }
/// <summary>
/// URL to the legal notice page.
/// </summary>
public string? LegalNoticeUrl { get; set; }
/// <summary>
/// URL to the content of the dialog box.
/// </summary>
public string? ContentURL { get; set; }
/// <summary>
/// CSS class for the 'Agree' button.
/// </summary>
public string? ButtonAgreeClass { get; set; }
/// <summary>
/// CSS class for the 'Don't Agree' button.
/// </summary>
public string? ButtonDontAgreeClass { get; set; }
/// <summary>
/// CSS class for the 'Save' button.
/// </summary>
public string? ButtonSaveClass { get; set; }
/// <summary>
/// Language in which the modal is displayed.
/// </summary>
public string? Lang { get; set; }
/// <summary>
/// Default language for the modal if the user's browser language is not supported.
/// </summary>
public string? DefaultLang { get; set; }
/// <summary>
/// Name of the cookie used to store the consent status.
/// </summary>
public string? CookieName { get; set; }
/// <summary>
/// Number of days the cookie will be stored.
/// </summary>
public int CookieStorageDays { get; set; }
/// <summary>
/// Identifier for the modal dialog element.
/// </summary>
public string? ModalId { get; set; }
/// <summary>
/// Indicates whether to also store the settings in the browser's localStorage.
/// </summary>
public bool AlsoUseLocalStorage { get; set; }
/// <summary>
/// List of categories for cookie consent.
/// </summary>
public List<string>? Categories { get; set; }
}
}

View File

@@ -0,0 +1,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

@@ -0,0 +1,44 @@
namespace DigitalData.Core.DTO
{
/// <summary>
/// Defines flags that indicate specific types of status or conditions in a service operation.
/// These flags help in categorizing and identifying specific circumstances or issues that may arise during execution.
/// </summary>
public enum Flag
{
/// <summary>
/// Indicates a security breach or vulnerability has been detected during the service operation.
/// </summary>
SecurityBreach,
/// <summary>
/// Indicates a potential issue with data integrity during the service operation.
/// This flag is used when data may have been altered, corrupted, or is otherwise unreliable,
/// which could impact the accuracy or trustworthiness of the operation's results.
/// </summary>
DataIntegrityIssue,
/// <summary>
/// Indicates that either a security breach, a data integrity issue, or both have been detected during the service operation.
/// This flag is used when it is not sure whether the problem is security or data integrity. In this case, data integrity should be checked first.
/// </summary>
SecurityBreachOrDataIntegrity,
/// <summary>
/// Indicates a possible security breach during the service operation.
/// </summary>
PossibleSecurityBreach,
/// <summary>
/// Indicates a possible issue with data integrity during the service operation.
/// This flag is used when there is a suspicion of data alteration, corruption, or unreliability.
/// </summary>
PossibleDataIntegrityIssue,
/// <summary>
/// Indicates that either a possible security breach, a possible data integrity issue, or both have been detected during the service operation.
/// This flag is used when it is uncertain whether the issue is related to security, data integrity, or both.
/// </summary>
PossibleSecurityBreachOrDataIntegrity
}
}

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