diff --git a/.github/workflows/nuget-package.yml b/.github/workflows/nuget-package.yml new file mode 100644 index 00000000..7f7b7a2c --- /dev/null +++ b/.github/workflows/nuget-package.yml @@ -0,0 +1,75 @@ +name: Build and Publish NuGet Package + +on: + workflow_dispatch: + inputs: + project: + description: 'Select the project to build' + required: true + default: 'Path/To/Project1.csproj' + options: + - 'Path/To/Project1.csproj' + - 'Path/To/Project2.csproj' + version_type: + description: 'Select the version type' + required: true + default: 'patch' + options: + - 'major' + - 'minor' + - 'patch' + - 'preview' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup .NET Core + uses: actions/setup-dotnet@v2 + with: + dotnet-version: '7.x' + + - name: Get latest version from GitHub Packages + run: | + LATEST_VERSION=$(dotnet nuget list source --source "https://nuget.pkg.github.com//index.json" | grep "" | head -n 1 | awk '{print $2}') + echo "Latest version: $LATEST_VERSION" + echo "LATEST_VERSION=$LATEST_VERSION" >> $GITHUB_ENV + + - name: Determine new version + id: version + run: | + VERSION_TYPE="${{ github.event.inputs.version_type }}" + LATEST_VERSION="${{ env.LATEST_VERSION }}" + + # Extract major, minor, patch from latest version (assumes format major.minor.patch or major.minor.patch-preview) + MAJOR=$(echo $LATEST_VERSION | cut -d. -f1) + MINOR=$(echo $LATEST_VERSION | cut -d. -f2) + PATCH=$(echo $LATEST_VERSION | cut -d. -f3 | cut -d- -f1) # Removes preview suffix if present + + # Increment version based on user input + if [ "$VERSION_TYPE" == "major" ]; then + NEW_VERSION="$((MAJOR + 1)).0.0" + elif [ "$VERSION_TYPE" == "minor" ]; then + NEW_VERSION="$MAJOR.$((MINOR + 1)).0" + elif [ "$VERSION_TYPE" == "patch" ]; then + NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))" + elif [ "$VERSION_TYPE" == "preview" ]; then + PREVIEW_SUFFIX="-preview" + NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))$PREVIEW_SUFFIX" + fi + + echo "New version: $NEW_VERSION" + echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV + + - name: Restore dependencies + run: dotnet restore ${{ github.event.inputs.project }} + + - name: Build project + run: dotnet build ${{ github.event.inputs.project }} --configuration Release --no-restore + + - name: Pack NuGet package + run: dotnet pack ${{ github.event.inputs.project }} --configuration Release --no-build --output ./nuget-packages /p:PackageVersion=${{ env.NEW_VERSION }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index dfc747e3..4c071b2c 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ *.suo *.user *.sln.docstates +*.env # Build results [Dd]ebug/ diff --git a/source/Modules/Devon4Net.Infrastructure.AWS.Serverless/Devon4Net.Infrastructure.AWS.Serverless.csproj b/source/Modules/Devon4Net.Infrastructure.AWS.Serverless/Devon4Net.Infrastructure.AWS.Serverless.csproj index facff820..fceaa24a 100644 --- a/source/Modules/Devon4Net.Infrastructure.AWS.Serverless/Devon4Net.Infrastructure.AWS.Serverless.csproj +++ b/source/Modules/Devon4Net.Infrastructure.AWS.Serverless/Devon4Net.Infrastructure.AWS.Serverless.csproj @@ -35,10 +35,6 @@ True \ - - - - diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Application/ApplicationTypes/API/DevonfwApi.cs b/source/Modules/Devon4Net.Infrastructure.Common/Application/ApplicationTypes/API/DevonfwApi.cs index 12a88574..75afb636 100644 --- a/source/Modules/Devon4Net.Infrastructure.Common/Application/ApplicationTypes/API/DevonfwApi.cs +++ b/source/Modules/Devon4Net.Infrastructure.Common/Application/ApplicationTypes/API/DevonfwApi.cs @@ -11,6 +11,8 @@ using Devon4Net.Infrastructure.Common.Application.ApplicationTypes.API.Servers; using Devon4Net.Infrastructure.Common.Application.ApplicationTypes.API.Configuration; using Devon4Net.Infrastructure.Common.Application.Attributes; +using Devon4Net.Infrastructure.Common.Helpers; +using Devon4Net.Infrastructure.Common.Helpers.Interfaces; namespace Devon4Net.Infrastructure.Common.Application.ApplicationTypes.API { @@ -39,6 +41,8 @@ public static void InitializeDevonfwApi(this IWebHostBuilder builder, IHostBuild public static DevonfwOptions SetupDevonfw(this IServiceCollection services, IConfiguration configuration) { + services?.SetupDevonfwServices(); + DevonfwOptions = services?.GetTypedOptions(configuration, OptionsDefinition.DefaultSettingsNodeName); if (DevonfwOptions == null) @@ -53,11 +57,19 @@ public static DevonfwOptions SetupDevonfw(this IServiceCollection services, ICon services?.AddMvc(options => options.Filters.Add(typeof(ModelStateCheckerAttribute))); } + services.AddScoped(); + if (DevonfwOptions.UseIIS) services?.ConfigureIIS(DevonfwOptions.IIS); if (DevonfwOptions.UseXsrf) services?.ConfigureXsrf(); return DevonfwOptions; } + + private static void SetupDevonfwServices(this IServiceCollection services) + { + services.AddSingleton(); + services.AddTransient(typeof(IJsonHelper), typeof(JsonHelper)); + } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Application/Attributes/ExceptionHandlingFilterAttribute.cs b/source/Modules/Devon4Net.Infrastructure.Common/Application/Attributes/ExceptionHandlingFilterAttribute.cs new file mode 100644 index 00000000..28be8c0d --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Application/Attributes/ExceptionHandlingFilterAttribute.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc; +using Devon4Net.Infrastructure.Common.Exceptions; + +namespace Devon4Net.Infrastructure.Common.Application.Attributes +{ + public class ExceptionHandlingFilterAttribute : ExceptionFilterAttribute + { + public ExceptionHandlingFilterAttribute() { } + + public override Task OnExceptionAsync(ExceptionContext context) + { + if (context.Exception.InnerException != null) + Devon4NetLogger.Debug(context.Exception.InnerException?.ToString()!); + + context.Result = context.Exception switch + { + WebApiException webApiException => HandleContext(webApiException.StatusCode, webApiException.Message, webApiException.ShowMessage), + HttpRequestException httpRequestException => HandleContext((int?)httpRequestException.StatusCode, httpRequestException.Message), + _ => HandleContext(), + }; + + return Task.CompletedTask; + } + + private IActionResult HandleContext(int? statusCode = null, string? errorMessage = null, bool showMessage = false) + { + statusCode ??= StatusCodes.Status500InternalServerError; + + if (!showMessage || statusCode == StatusCodes.Status204NoContent || string.IsNullOrEmpty(errorMessage)) + { + return new ContentResult + { + StatusCode = (int)statusCode, + Content = string.Empty, + }; + } + + var response = new { error = errorMessage }; + + return new JsonResult(response) + { + StatusCode = (int)statusCode, + }; + } + } +} diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/Exception/ExceptionHandlingMiddleware.cs b/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/Exception/ExceptionHandlingMiddleware.cs deleted file mode 100644 index 46d78d89..00000000 --- a/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/Exception/ExceptionHandlingMiddleware.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Text.Json; -using Devon4Net.Infrastructure.Common.Exceptions; -using Devon4Net.Infrastructure.Common; -using Microsoft.AspNetCore.Http; - -namespace Devon4Net.Infrastructure.Common.Application.Middleware.Exception -{ - public class ExceptionHandlingMiddleware - { - private readonly RequestDelegate _next; - - public ExceptionHandlingMiddleware(RequestDelegate next) - { - _next = next; - } - - public async Task Invoke(HttpContext context) - { - try - { - await _next(context).ConfigureAwait(false); - } - catch (System.Exception ex) - { - await HandleException(ref context, ref ex).ConfigureAwait(false); - } - } - - private static Task HandleException(ref HttpContext context, ref System.Exception exception) - { - Devon4NetLogger.Error(exception); - - var exceptionTypeValue = exception.GetType(); - var exceptionInterfaces = exceptionTypeValue.GetInterfaces().Select(i => i.Name).ToList(); - exceptionInterfaces.Add(exceptionTypeValue.Name); - - return exceptionInterfaces switch - { - { } exceptionType when exceptionType.Contains("InvalidDataException") => HandleContext(ref context, - StatusCodes.Status422UnprocessableEntity), - { } exceptionType when exceptionType.Contains("ArgumentException") || - exceptionType.Contains("ArgumentNullException") || - exceptionType.Contains("NotFoundException") || - exceptionType.Contains("FileNotFoundException") => HandleContext(ref context, - StatusCodes.Status400BadRequest), - { } exceptionType when exceptionType.Contains("IWebApiException") => HandleContext(ref context, - ((IWebApiException) exception).StatusCode, exception.Message, - ((IWebApiException) exception).ShowMessage), - _ => HandleContext(ref context, StatusCodes.Status500InternalServerError, exception.Message) - }; - } - - private static Task HandleContext(ref HttpContext context, int? statusCode = null, string errorMessage = null, bool showMessage = false) - { - context.Response.Headers.Clear(); - context.Response.StatusCode = statusCode ?? StatusCodes.Status500InternalServerError; - - if (!showMessage || statusCode == StatusCodes.Status204NoContent || string.IsNullOrEmpty(errorMessage) ) return Task.CompletedTask; - - context.Response.ContentType = "application/json"; - return context.Response.WriteAsync(JsonSerializer.Serialize(new { error = errorMessage })); - } - } -} diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/KillSwicth/KillSwicthMiddleware.cs b/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/KillSwicth/KillSwicthMiddleware.cs index d58d6cee..8e7be34f 100644 --- a/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/KillSwicth/KillSwicthMiddleware.cs +++ b/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/KillSwicth/KillSwicthMiddleware.cs @@ -17,67 +17,23 @@ public KillSwicthMiddleware(RequestDelegate next) public async Task Invoke(HttpContext context, IOptionsMonitor killSwitch) { - try + if (killSwitch?.CurrentValue.UseKillSwitch == true) { - if (killSwitch?.CurrentValue.UseKillSwitch == true) + if (killSwitch.CurrentValue?.EnableRequests == true) { - if (killSwitch.CurrentValue?.EnableRequests == true) - { - await _next(context).ConfigureAwait(false); - } - else - { - context.Response.Headers.Clear(); - context.Response.StatusCode = killSwitch.CurrentValue?.HttpStatusCode > 0 ? killSwitch.CurrentValue.HttpStatusCode : 403; - await context.Response.WriteAsync(string.Empty).ConfigureAwait(false); - } + await _next(context).ConfigureAwait(false); } else { - await _next(context).ConfigureAwait(false); + context.Response.Headers.Clear(); + context.Response.StatusCode = killSwitch.CurrentValue?.HttpStatusCode > 0 ? killSwitch.CurrentValue.HttpStatusCode : 403; + await context.Response.WriteAsync(string.Empty).ConfigureAwait(false); } } -#pragma warning disable CA1031 // #warning directive - catch (System.Exception ex) -#pragma warning restore CA1031 // #warning directive + else { - await HandleException(ref context, ref ex).ConfigureAwait(false); + await _next(context).ConfigureAwait(false); } } - - private static Task HandleException(ref HttpContext context, ref System.Exception exception) - { - Devon4NetLogger.Error(exception); - - var exceptionTypeValue = exception.GetType(); - var exceptionInterfaces = exceptionTypeValue.GetInterfaces().Select(i => i.Name).ToList(); - exceptionInterfaces.Add(exceptionTypeValue.Name); - - return exceptionInterfaces switch - { - { } exceptionType when exceptionType.Contains("InvalidDataException") => HandleContext(ref context, - StatusCodes.Status422UnprocessableEntity), - { } exceptionType when exceptionType.Contains("ArgumentException") || - exceptionType.Contains("ArgumentNullException") || - exceptionType.Contains("NotFoundException") || - exceptionType.Contains("FileNotFoundException") => HandleContext(ref context, - StatusCodes.Status400BadRequest), - { } exceptionType when exceptionType.Contains("IWebApiException") => HandleContext(ref context, - ((IWebApiException) exception).StatusCode, exception.Message, - ((IWebApiException) exception).ShowMessage), - _ => HandleContext(ref context, StatusCodes.Status500InternalServerError, exception.Message) - }; - } - - private static Task HandleContext(ref HttpContext context, int? statusCode = null, string errorMessage = null, bool showMessage = false) - { - context.Response.Headers.Clear(); - context.Response.StatusCode = statusCode ?? StatusCodes.Status500InternalServerError; - - if (!showMessage || statusCode == StatusCodes.Status204NoContent || string.IsNullOrEmpty(errorMessage) ) return Task.CompletedTask; - - context.Response.ContentType = "application/json"; - return context.Response.WriteAsync(JsonSerializer.Serialize(new { error = errorMessage })); - } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/Logs/LoggerMiddleware.cs b/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/Logs/LoggerMiddleware.cs new file mode 100644 index 00000000..a8186257 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/Logs/LoggerMiddleware.cs @@ -0,0 +1,95 @@ +using System.Text; +using Devon4Net.Infrastructure.Common.Helpers.Interfaces; +using Microsoft.AspNetCore.Http; +using Serilog; +using Serilog.Events; + +namespace Devon4Net.Infrastructure.Common.Application.Middleware.Logs; + +public class LoggerMiddleware +{ + private readonly RequestDelegate _next; + private const string ResponseBodyEntity = "Response body"; + private const string RequestBodyEntity = "Request body"; + private const int MaxBodyResponse = 150; + + public LoggerMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context, IRecyclableMemoryHelper recyclableMemoryHelper) + { + try + { + using var bodyReader = recyclableMemoryHelper.GetMemoryStream(); + context.Request.EnableBuffering(); + + var originalResponseBody = context.Response.Body; + context.Response.Body = bodyReader; + + Devon4NetLogger.Information($"REQUEST STARTED {context.TraceIdentifier} | HttpMethod: {context.Request.Method} | Path: {context.Request.Path} "); + + await _next(context).ConfigureAwait(false); + bodyReader.Seek(0, SeekOrigin.Begin); + await bodyReader.CopyToAsync(originalResponseBody).ConfigureAwait(false); + + if (Log.IsEnabled(LogEventLevel.Information)) + { + if (context.Response.StatusCode is < StatusCodes.Status200OK or > StatusCodes.Status300MultipleChoices) Devon4NetLogger.Information(await HandleResponseStream(context.TraceIdentifier, context.Request?.Body, RequestBodyEntity).ConfigureAwait(false)); + Devon4NetLogger.Information(await HandleResponseStream(context.TraceIdentifier, context.Response.Body, ResponseBodyEntity, true).ConfigureAwait(false)); + Devon4NetLogger.Information($"REQUEST FINISHED {context.TraceIdentifier} | Status Code: {context.Response.StatusCode}"); + } + if (Log.IsEnabled(LogEventLevel.Warning)) + { + if (context.Response.StatusCode is >= StatusCodes.Status200OK and < StatusCodes.Status300MultipleChoices) return; + Devon4NetLogger.Warning(await HandleResponseStream(context.TraceIdentifier, context.Response.Body, ResponseBodyEntity, true).ConfigureAwait(false)); + Devon4NetLogger.Warning($"REQUEST FINISHED {context.TraceIdentifier} Status Code: {context.Response.StatusCode}"); + } + else + { + Devon4NetLogger.Debug(await HandleResponseStream(context.TraceIdentifier, context.Request?.Body, RequestBodyEntity).ConfigureAwait(false)); + Devon4NetLogger.Debug(await HandleResponseStream(context.TraceIdentifier, context.Response.Body, ResponseBodyEntity, true).ConfigureAwait(false)); + Devon4NetLogger.Debug($"REQUEST FINISHED {context.TraceIdentifier} | Status Code: {context.Response.StatusCode}"); + } + } + catch (System.Exception ex) + { + Devon4NetLogger.Error($"REQUEST FINISHED {context.TraceIdentifier} | Status Code: {context.Response.StatusCode} | Exception: {ex.Message}"); + throw; + } + } + + private static async Task HandleResponseStream(string identifier, Stream bodyStream, string entityDisclaimer, bool trimBody = false) + { + var stringBuilder = new StringBuilder(); + + stringBuilder.Append("REQUEST ").Append(identifier).Append(' ').Append(entityDisclaimer).Append(": "); + + if (bodyStream == null || !bodyStream.CanRead) + { + stringBuilder.Append("No data found."); + return stringBuilder.ToString(); + } + + using var bodyReader = new StreamReader(bodyStream); + bodyStream.Seek(0, SeekOrigin.Begin); + + if (trimBody) + { + var buffer = new char[MaxBodyResponse]; + var count = await bodyReader.ReadBlockAsync(buffer, 0, MaxBodyResponse); + stringBuilder.Append(new string(buffer, 0, count)); + } + else + { + var text = await bodyReader.ReadToEndAsync().ConfigureAwait(false); + if (string.IsNullOrWhiteSpace(text)) return default; + stringBuilder.Append(text.Replace("\n", "")); + } + + bodyStream.Seek(0, SeekOrigin.Begin); + + return stringBuilder.ToString(); + } +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/MiddlewareConfiguration.cs b/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/MiddlewareConfiguration.cs index 6972f7ad..9730ac4a 100644 --- a/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/MiddlewareConfiguration.cs +++ b/source/Modules/Devon4Net.Infrastructure.Common/Application/Middleware/MiddlewareConfiguration.cs @@ -5,9 +5,9 @@ using Microsoft.Extensions.Options; using Devon4Net.Infrastructure.Common.Application.Options; using Devon4Net.Infrastructure.Common.Application.Middleware.KillSwicth; -using Devon4Net.Infrastructure.Common.Application.Middleware.Exception; using Devon4Net.Infrastructure.Common.Application.Middleware.Headers; using Devon4Net.Infrastructure.Common.Application.Middleware.Certificates; +using Devon4Net.Infrastructure.Common.Application.Middleware.Logs; namespace Devon4Net.Infrastructure.Common.Application.Middleware { @@ -16,13 +16,15 @@ public static class MiddlewareConfiguration public static void SetupMiddleware(this IApplicationBuilder builder, IServiceCollection services) { using var serviceProvider = services.BuildServiceProvider(); + + builder.UseMiddleware(); + var killSwitch = serviceProvider.GetService>()?.Value; var certificates = serviceProvider.GetService>()?.Value; if (killSwitch?.UseKillSwitch == true) builder.UseMiddleware(); if (certificates?.ClientCertificate?.EnableClientCertificateCheck == true || certificates?.ClientCertificate?.RequireClientCertificate== true) builder.UseMiddleware(); - builder.UseMiddleware(); builder.UseMiddleware(); } diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Devon4Net.Infrastructure.Common.csproj b/source/Modules/Devon4Net.Infrastructure.Common/Devon4Net.Infrastructure.Common.csproj index 8c5ed5b9..a3a3882d 100644 --- a/source/Modules/Devon4Net.Infrastructure.Common/Devon4Net.Infrastructure.Common.csproj +++ b/source/Modules/Devon4Net.Infrastructure.Common/Devon4Net.Infrastructure.Common.csproj @@ -24,7 +24,6 @@ - @@ -34,6 +33,7 @@ + diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Enumerators/LogType.cs b/source/Modules/Devon4Net.Infrastructure.Common/Enumerators/LogType.cs deleted file mode 100644 index 36831a6d..00000000 --- a/source/Modules/Devon4Net.Infrastructure.Common/Enumerators/LogType.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Devon4Net.Infrastructure.Common.Enumerators -{ - public enum LogType - { - Debug = 0, - Info = 1, - Warning = 2, - Error = 3, - Fatal = 4 - } -} diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Errors/DevonError.cs b/source/Modules/Devon4Net.Infrastructure.Common/Errors/DevonError.cs new file mode 100644 index 00000000..4ef213a8 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Errors/DevonError.cs @@ -0,0 +1,20 @@ +using System.Net; + +namespace Devon4Net.Infrastructure.Common.Errors; + +public class DevonError +{ + public string Code { get; init; } + + public string Message { get; init; } + + public HttpStatusCode HttpStatus { get; private set; } + + public DevonError(string code, string message, HttpStatusCode? httpStatus = null) + { + Code = code; + Message = message; + HttpStatus = httpStatus ?? HttpStatusCode.InternalServerError; + } + +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/HttpCustomRequestException.cs b/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/HttpCustomRequestException.cs index 4d5371a6..aa61aad4 100644 --- a/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/HttpCustomRequestException.cs +++ b/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/HttpCustomRequestException.cs @@ -1,36 +1,44 @@ using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Infrastructure.Common.Exceptions { [Serializable] - public class HttpCustomRequestException : Exception, IWebApiException + public class HttpCustomRequestException : WebApiException { - public int StatusCode { get; } + /// + /// Gets the forced http status code to be fired on the exception manager. + /// + public override int StatusCode => StatusCodes.Status404NotFound; - public bool ShowMessage => false; + /// + /// Gets a value indicating whether show the message on the response?. + /// + public override bool ShowMessage => true; public HttpCustomRequestException() { - StatusCode = StatusCodes.Status500InternalServerError; } public HttpCustomRequestException(string message) : base(message) { - StatusCode = StatusCodes.Status500InternalServerError; } public HttpCustomRequestException(string message, int statusCode) : base(message) { - StatusCode = statusCode; } public HttpCustomRequestException(string message, Exception inner) : base(message, inner) { - StatusCode = StatusCodes.Status500InternalServerError; + } + + protected HttpCustomRequestException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/IWebApiException.cs b/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/IWebApiException.cs deleted file mode 100644 index d247fd4e..00000000 --- a/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/IWebApiException.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Runtime.Serialization; - -namespace Devon4Net.Infrastructure.Common.Exceptions -{ - /// Interface for webapi exceptions - public interface IWebApiException : ISerializable - { - /// Gets the status code. - /// The status code. - int StatusCode { get; } - /// Gets a value indicating whether [show message]. - /// - /// true if [show message]; otherwise, false. - bool ShowMessage { get; } - } -} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/WebApiException.cs b/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/WebApiException.cs new file mode 100644 index 00000000..bd2dd9c3 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Exceptions/WebApiException.cs @@ -0,0 +1,29 @@ +namespace Devon4Net.Infrastructure.Common.Exceptions +{ + /// Interface for webapi exceptions. + public abstract class WebApiException : Exception + { + protected WebApiException() + { + } + + protected WebApiException(string message) + : base(message) + { + } + + protected WebApiException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// Gets the status code. + /// The status code. + public virtual int StatusCode { get; } + + /// Gets a value indicating whether [show message]. + /// + /// true if [show message]; otherwise, false. + public virtual bool ShowMessage { get; } + } +} diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Helpers/AutoRegisterHelpers.cs b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/AutoRegisterHelpers.cs index c1ec1ec6..ff81c739 100644 --- a/source/Modules/Devon4Net.Infrastructure.Common/Helpers/AutoRegisterHelpers.cs +++ b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/AutoRegisterHelpers.cs @@ -22,7 +22,7 @@ public static class AutoRegisterHelpers public static AutoRegisterData RegisterAssemblyPublicNonGenericClasses(this IServiceCollection services, params Assembly[] assemblies) { if (assemblies.Length == 0) - assemblies = new[] {Assembly.GetCallingAssembly()}; + assemblies = new[] { Assembly.GetCallingAssembly() }; var allPublicTypes = assemblies.SelectMany(x => x.GetExportedTypes() .Where(y => y.IsClass && !y.IsAbstract && !y.IsGenericType && !y.IsNested)); @@ -50,12 +50,12 @@ public static AutoRegisterData Where(this AutoRegisterData autoRegData, FuncAutoRegister data produced by method /// Allows you to define the lifetime of the service - defaults to ServiceLifetime.Transient /// - public static IServiceCollection AsPublicImplementedInterfaces(this AutoRegisterData autoRegData, + public static IServiceCollection AsPublicImplementedInterfaces(this AutoRegisterData autoRegData, ServiceLifetime lifetime = ServiceLifetime.Transient) { if (autoRegData == null) throw new ArgumentNullException(nameof(autoRegData)); - foreach (var classType in (autoRegData.TypeFilter == null - ? autoRegData.TypesToConsider + foreach (var classType in (autoRegData.TypeFilter == null + ? autoRegData.TypesToConsider : autoRegData.TypesToConsider.Where(autoRegData.TypeFilter))) { var interfaces = classType.GetTypeInfo().ImplementedInterfaces; @@ -86,37 +86,37 @@ public static IServiceCollection AsSingletonPublicImplementedClasses(this AutoRe return autoRegData.Services; } - public static void AutoRegisterClasses(this IServiceCollection services, List assemblyContainerToScan, string sufixName = "Service") + public static void AutoRegisterClasses(this IServiceCollection services, List assembliesNamespaceToScan, + List suffixNames, ServiceLifetime serviceLifetime) { - if (assemblyContainerToScan == null || assemblyContainerToScan.Count == 0|| string.IsNullOrEmpty(sufixName)) return; + if (assembliesNamespaceToScan == null || assembliesNamespaceToScan.Count == 0 || suffixNames == null || + suffixNames.Count == 0) return; - foreach (var assembly in assemblyContainerToScan) + foreach (var assembly in assembliesNamespaceToScan) { - var assemblyToScan = Assembly.GetAssembly(assembly); - if (assemblyToScan == null) continue; - - services.RegisterAssemblyPublicNonGenericClasses(assemblyToScan) - .Where(x => x.Name.EndsWith(sufixName)) - .AsPublicImplementedInterfaces(); + foreach (var suffixName in suffixNames) + services.RegisterAssemblyPublicNonGenericClasses(assembly) + .Where(x => x.Name.EndsWith(suffixName)) + .AsPublicImplementedInterfaces(serviceLifetime); } } - public static void AutoRegisterClasses(this IServiceCollection services, List assemblyContainerToScan, List sufixNames) + public static void AutoRegisterClasses(this IServiceCollection services, List assemblyContainerToScan, + List suffixNames, ServiceLifetime serviceLifetime) { - if (assemblyContainerToScan == null || assemblyContainerToScan.Count == 0 || sufixNames == null || sufixNames.Count == 0) return; + if (assemblyContainerToScan == null || assemblyContainerToScan.Count == 0 || suffixNames == null || + suffixNames.Count == 0) return; foreach (var assembly in assemblyContainerToScan) { var assemblyToScan = Assembly.GetAssembly(assembly); if (assemblyToScan == null) continue; - foreach (var sufixName in sufixNames) - { + foreach (var suffixName in suffixNames) services.RegisterAssemblyPublicNonGenericClasses(assemblyToScan) - .Where(x => x.Name.EndsWith(sufixName)) - .AsPublicImplementedInterfaces(); - } + .Where(x => x.Name.EndsWith(suffixName)) + .AsPublicImplementedInterfaces(serviceLifetime); } } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Helpers/DevonErrorHelper.cs b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/DevonErrorHelper.cs new file mode 100644 index 00000000..d1dc57ad --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/DevonErrorHelper.cs @@ -0,0 +1,11 @@ +using System; +using Devon4Net.Infrastructure.Common.Errors; + +namespace Devon4Net.Infrastructure.Common.Helpers +{ + public static class DevonErrorHelper + { + public static int ToHigherHttpStatusCode(this IEnumerable errors) + => (int)errors.Max(x => x.HttpStatus); + } +} diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Helpers/Interfaces/IRecyclableMemoryHelper.cs b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/Interfaces/IRecyclableMemoryHelper.cs new file mode 100644 index 00000000..4b0111da --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/Interfaces/IRecyclableMemoryHelper.cs @@ -0,0 +1,7 @@ +namespace Devon4Net.Infrastructure.Common.Helpers.Interfaces; + +public interface IRecyclableMemoryHelper +{ + MemoryStream GetMemoryStream(); + MemoryStream GetMemoryStream(byte[] byteArray); +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Helpers/JsonHelper.cs b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/JsonHelper.cs index acb28826..0836adc1 100644 --- a/source/Modules/Devon4Net.Infrastructure.Common/Helpers/JsonHelper.cs +++ b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/JsonHelper.cs @@ -7,7 +7,7 @@ namespace Devon4Net.Infrastructure.Common.Helpers public class JsonHelper : IJsonHelper { private const string BuiltInTypes = "String, DateTime, DateTimeKind, DateTimeOffset, AsyncCallback, AttributeTargets, AttributeUsageAttribute, Boolean, Byte, Char, CharEnumerator, Base64FormattingOptions, DayOfWeek, DBNull, Decimal, Double, EnvironmentVariableTarget, EventHandler, GCCollectionMode, Guid, Int16, Int32, Int64, IntPtr, SByte, Single, TimeSpan, TimeZoneInfo, TypeCode, UInt16, UInt32, UInt64, UIntPtr"; - private readonly JsonSerializerOptions CamelJsonSerializerOptions = new () { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }; + private readonly JsonSerializerOptions CamelJsonSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull }; private JsonSerializerOptions JsonSerializerOptions { get; } @@ -21,7 +21,7 @@ public JsonHelper(JsonSerializerOptions jsonSerializerOptions) JsonSerializerOptions = jsonSerializerOptions; } - public T Deserialize(string input, bool useCamelCase= false) + public T Deserialize(string input, bool useCamelCase = false) { if (string.IsNullOrEmpty(input)) { @@ -40,7 +40,7 @@ public List Deserialize(List input, bool useCamelCase = false) { if (input == null || input.Count == 0) { - return default; + return new List(); } var result = new List(); @@ -62,6 +62,7 @@ public async Task Serialize(T input) using var reader = new StreamReader(stream); return await reader.ReadToEndAsync().ConfigureAwait(false); } + public string Serialize(object toPrint, bool useCamelCase = false) { return JsonSerializer.Serialize(toPrint, useCamelCase ? CamelJsonSerializerOptions : null); @@ -76,4 +77,4 @@ public async Task SerializeAsync(T input) return await reader.ReadToEndAsync().ConfigureAwait(false); } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Helpers/RecyclableMemoryHelper.cs b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/RecyclableMemoryHelper.cs new file mode 100644 index 00000000..1f5ca128 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Helpers/RecyclableMemoryHelper.cs @@ -0,0 +1,47 @@ +using Devon4Net.Infrastructure.Common.Helpers.Interfaces; +using Microsoft.IO; +using static Microsoft.IO.RecyclableMemoryStreamManager; + +namespace Devon4Net.Infrastructure.Common.Helpers; + +public class RecyclableMemoryHelper : IRecyclableMemoryHelper +{ + private int BlockSize { get; set; } + private int LargeBufferMultiple { get; set; } + private int MaxBufferSize { get; set; } + private int MaximumFreeSmallPoolBytes { get; set; } + private RecyclableMemoryStreamManager RecyclableMemoryStreamManager { get; } + + public RecyclableMemoryHelper() + { + BlockSize = 1024; + LargeBufferMultiple = 80 * 1024; + MaxBufferSize = 16 * LargeBufferMultiple; + MaximumFreeSmallPoolBytes = 250 * BlockSize; + RecyclableMemoryStreamManager = new RecyclableMemoryStreamManager(new Options + { + BlockSize = BlockSize, + LargeBufferMultiple = LargeBufferMultiple, + AggressiveBufferReturn = true, + GenerateCallStacks = false, + MaximumLargePoolFreeBytes = MaximumFreeSmallPoolBytes, + MaximumSmallPoolFreeBytes = MaximumFreeSmallPoolBytes, + MaximumBufferSize = MaxBufferSize, + } ); + } + + public RecyclableMemoryHelper(RecyclableMemoryStreamManager recyclableMemoryStreamManager) + { + RecyclableMemoryStreamManager = recyclableMemoryStreamManager; + } + + public MemoryStream GetMemoryStream() + { + return RecyclableMemoryStreamManager.GetStream(); + } + + public MemoryStream GetMemoryStream(byte[] byteArray) + { + return RecyclableMemoryStreamManager.GetStream(byteArray); + } +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Models/DevonResult.cs b/source/Modules/Devon4Net.Infrastructure.Common/Models/DevonResult.cs new file mode 100644 index 00000000..3c594259 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Models/DevonResult.cs @@ -0,0 +1,109 @@ +using Devon4Net.Infrastructure.Common.Errors; +using Devon4Net.Infrastructure.Common.Helpers; +using Microsoft.AspNetCore.Mvc; +using System.Net; +using System.Text.Json.Serialization; + +namespace Devon4Net.Infrastructure.Common.Models; + +public class DevonResult : IDevonResult +{ + protected DevonResult() { } + + public DevonResult(T value) + { + Data = value; + } + + public static implicit operator T(DevonResult result) => result.Data; + public static implicit operator DevonResult(T value) => new DevonResult(value); + + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public T Data { get; init; } + + [JsonIgnore] + public Type ValueType => typeof(T); + + [JsonInclude] + public bool IsSuccess => !Errors.Any(); + + [JsonInclude] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public IEnumerable Errors { get; protected set; } = []; + + public object GetData() + { + return Data; + } + + public static DevonResult Success(T value) + { + return new DevonResult(value); + } + + public static DevonResult Error(DevonError devonError) + { + return new DevonResult() { Errors = [devonError] }; + } + + public static DevonResult Error(IEnumerable devonErrors = null) + { + return new DevonResult() + { + Errors = devonErrors + }; + } + + public void AddError(DevonError error) + { + IEnumerable errors; + if (Errors != null) + { + errors = Errors.Append(error); + } + else + { + errors = []; + } + + Errors = errors; + } + + public IEnumerable? GetErrors() + { + return Errors; + } + + public bool ReturnedError(DevonError errorCode) + { + return Errors?.Any((DevonError myError) => myError.Code == $"{errorCode}") ?? false; + } + + public bool ReturnedError(IEnumerable errorCodes) + { + return errorCodes.Any((DevonError errorCode) => Errors?.Any((DevonError myError) => myError.Code == $"{errorCode}") ?? false); + } + + public IActionResult BuildResult(HttpStatusCode httpStatusCode = HttpStatusCode.OK) + { + if (IsSuccess) + { + T dataAsT = Data; + if(dataAsT == null) + { + return new NoContentResult(); + } + + return new OkObjectResult(dataAsT) + { + StatusCode = (int)httpStatusCode, + }; + + } + return new ObjectResult(Errors) + { + StatusCode = Errors.ToHigherHttpStatusCode(), + }; + } +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.Common/Models/IDevonResult.cs b/source/Modules/Devon4Net.Infrastructure.Common/Models/IDevonResult.cs new file mode 100644 index 00000000..b6d6ba11 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.Common/Models/IDevonResult.cs @@ -0,0 +1,11 @@ +using Devon4Net.Infrastructure.Common.Errors; + +namespace Devon4Net.Infrastructure.Common.Models +{ + public interface IDevonResult + { + IEnumerable Errors { get; } + Type ValueType { get; } + object GetData(); + } +} diff --git a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/AdminNotFoundException.cs b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/AdminNotFoundException.cs index 86637dbf..8b6d5bc1 100644 --- a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/AdminNotFoundException.cs +++ b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/AdminNotFoundException.cs @@ -1,5 +1,6 @@ using Devon4Net.Infrastructure.Common.Exceptions; using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Infrastructure.Kafka.Exceptions { @@ -7,14 +8,17 @@ namespace Devon4Net.Infrastructure.Kafka.Exceptions /// Custom exception AdminNotFoundException /// [Serializable] - public class AdminNotFoundException : Exception, IWebApiException + public class AdminNotFoundException : WebApiException { - public int StatusCode => StatusCodes.Status500InternalServerError; + /// + /// Gets the forced http status code to be fired on the exception manager. + /// + public override int StatusCode => StatusCodes.Status500InternalServerError; /// - /// Show the message on the response? + /// Gets a value indicating whether show the message on the response?. /// - public bool ShowMessage => true; + public override bool ShowMessage => true; /// /// Initializes a new instance of the class. @@ -40,5 +44,10 @@ public AdminNotFoundException(string message) public AdminNotFoundException(string message, Exception innerException) : base(message, innerException) { } + + protected AdminNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerException.cs b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerException.cs index 8e9a4826..a072fac2 100644 --- a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerException.cs +++ b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerException.cs @@ -1,5 +1,6 @@ using Devon4Net.Infrastructure.Common.Exceptions; using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Infrastructure.Kafka.Exceptions { @@ -7,14 +8,17 @@ namespace Devon4Net.Infrastructure.Kafka.Exceptions /// Custom exception ConsumerException /// [Serializable] - public class ConsumerException : Exception, IWebApiException + public class ConsumerException : WebApiException { - public int StatusCode => StatusCodes.Status500InternalServerError; + /// + /// Gets the forced http status code to be fired on the exception manager. + /// + public override int StatusCode => StatusCodes.Status500InternalServerError; /// - /// Show the message on the response? + /// Gets a value indicating whether show the message on the response?. /// - public bool ShowMessage => true; + public override bool ShowMessage => true; /// /// Initializes a new instance of the class. @@ -40,5 +44,10 @@ public ConsumerException(string message) public ConsumerException(string message, Exception innerException) : base(message, innerException) { } + + protected ConsumerException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerNotFoundException.cs b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerNotFoundException.cs index fd93e06c..512dd55a 100644 --- a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerNotFoundException.cs +++ b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ConsumerNotFoundException.cs @@ -1,5 +1,6 @@ using Devon4Net.Infrastructure.Common.Exceptions; using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Infrastructure.Kafka.Exceptions { @@ -7,14 +8,17 @@ namespace Devon4Net.Infrastructure.Kafka.Exceptions /// Custom exception ConsumerNotFoundException /// [Serializable] - public class ConsumerNotFoundException : Exception, IWebApiException + public class ConsumerNotFoundException : WebApiException { - public int StatusCode => StatusCodes.Status500InternalServerError; + /// + /// Gets the forced http status code to be fired on the exception manager. + /// + public override int StatusCode => StatusCodes.Status500InternalServerError; /// - /// Show the message on the response? + /// Gets a value indicating whether show the message on the response?. /// - public bool ShowMessage => true; + public override bool ShowMessage => true; /// /// Initializes a new instance of the class. @@ -40,5 +44,10 @@ public ConsumerNotFoundException(string message) public ConsumerNotFoundException(string message, Exception innerException) : base(message, innerException) { } + + protected ConsumerNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/DeliverMessageException.cs b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/DeliverMessageException.cs index 8577a733..c897188c 100644 --- a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/DeliverMessageException.cs +++ b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/DeliverMessageException.cs @@ -1,5 +1,6 @@ using Devon4Net.Infrastructure.Common.Exceptions; using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Infrastructure.Kafka.Exceptions { @@ -7,14 +8,17 @@ namespace Devon4Net.Infrastructure.Kafka.Exceptions /// Custom exception DeliverMessageException /// [Serializable] - public class DeliverMessageException : Exception, IWebApiException + public class DeliverMessageException : WebApiException { - public int StatusCode => StatusCodes.Status500InternalServerError; + /// + /// Gets the forced http status code to be fired on the exception manager. + /// + public override int StatusCode => StatusCodes.Status500InternalServerError; /// - /// Show the message on the response? + /// Gets a value indicating whether show the message on the response?. /// - public bool ShowMessage => true; + public override bool ShowMessage => true; /// /// Initializes a new instance of the class. @@ -40,5 +44,10 @@ public DeliverMessageException(string message) public DeliverMessageException(string message, Exception innerException) : base(message, innerException) { } + + protected DeliverMessageException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ProducerNotFoundException.cs b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ProducerNotFoundException.cs index 9eeadd20..6aa38da0 100644 --- a/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ProducerNotFoundException.cs +++ b/source/Modules/Devon4Net.Infrastructure.Kafka/Exceptions/ProducerNotFoundException.cs @@ -1,5 +1,6 @@ using Devon4Net.Infrastructure.Common.Exceptions; using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Infrastructure.Kafka.Exceptions { @@ -7,14 +8,17 @@ namespace Devon4Net.Infrastructure.Kafka.Exceptions /// Custom exception ProducerNotFoundException /// [Serializable] - public class ProducerNotFoundException : Exception, IWebApiException + public class ProducerNotFoundException : WebApiException { - public int StatusCode => StatusCodes.Status500InternalServerError; + /// + /// Gets the forced http status code to be fired on the exception manager. + /// + public override int StatusCode => StatusCodes.Status500InternalServerError; /// - /// Show the message on the response? + /// Gets a value indicating whether show the message on the response?. /// - public bool ShowMessage => true; + public override bool ShowMessage => true; /// /// Initializes a new instance of the class. @@ -40,5 +44,10 @@ public ProducerNotFoundException(string message) public ProducerNotFoundException(string message, Exception innerException) : base(message, innerException) { } + + protected ProducerNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } } } diff --git a/source/Modules/Devon4Net.Infrastructure.Logger/Devon4Net.Infrastructure.Logger.csproj b/source/Modules/Devon4Net.Infrastructure.Logger/Devon4Net.Infrastructure.Logger.csproj index b36fc1ac..6ca76b7a 100644 --- a/source/Modules/Devon4Net.Infrastructure.Logger/Devon4Net.Infrastructure.Logger.csproj +++ b/source/Modules/Devon4Net.Infrastructure.Logger/Devon4Net.Infrastructure.Logger.csproj @@ -24,7 +24,6 @@ - diff --git a/source/Modules/Devon4Net.Infrastructure.Logger/LogConfiguration.cs b/source/Modules/Devon4Net.Infrastructure.Logger/LogConfiguration.cs index d3721048..2d8e5126 100644 --- a/source/Modules/Devon4Net.Infrastructure.Logger/LogConfiguration.cs +++ b/source/Modules/Devon4Net.Infrastructure.Logger/LogConfiguration.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Serilog; using Serilog.Sinks.Graylog; +using Serilog.Sinks.Graylog.Core.Transport; namespace Devon4Net.Infrastructure.Logger; @@ -23,32 +24,28 @@ public static void SetupLog(this IServiceCollection services, IConfiguration con if (logOptions == null) return; - LoggerConfiguration = CreateLoggerConfiguration(); - + LoggerConfiguration = CreateLoggerConfiguration(configuration); + services.AddSingleton(ConfigureLog(logOptions)); services.AddSingleton(LoggerConfiguration); - - if (logOptions.UseAopTrace) - { - SetupLogAop(ref services, logOptions); - } + + if (logOptions.UseAopTrace) SetupLogAop(ref services, logOptions); } private static ILoggerFactory ConfigureLog(LogOptions logOptions) { - SetLogLevel(logOptions.LogLevel?.Default!); ConfigureLogFile(logOptions); ConfigureLogSeq(logOptions); ConfigureLogSqLiteDb(logOptions); SetupGraylog(logOptions); - + return CreateLoggerFactory(); } private static void ConfigureLogFile(LogOptions logOptions) { if (!logOptions.UseLogFile) return; - + var logFile = logOptions.LogFile != null ? string.Format(logOptions.LogFile, DateTime.Today.ToShortDateString().Replace("/", string.Empty)) : DefaultLogFile; @@ -58,28 +55,28 @@ private static void ConfigureLogFile(LogOptions logOptions) private static void ConfigureLogSeq(LogOptions logOptions) { if (!string.IsNullOrEmpty(logOptions.SeqLogServerHost)) - { LoggerConfiguration = LoggerConfiguration.WriteTo.Seq(logOptions.SeqLogServerHost); - } } - + private static void ConfigureLogSqLiteDb(LogOptions logOptions) { if (logOptions.UseSqLiteDb && !string.IsNullOrEmpty(logOptions.SqliteDatabase)) - { - LoggerConfiguration = LoggerConfiguration.WriteTo.SQLite(GetValidPath(logOptions.SqliteDatabase, DefaultSqliteFile)); - } + LoggerConfiguration = + LoggerConfiguration.WriteTo.SQLite(GetValidPath(logOptions.SqliteDatabase, DefaultSqliteFile)); } - - private static LoggerConfiguration CreateLoggerConfiguration() + + private static LoggerConfiguration CreateLoggerConfiguration(IConfiguration configuration) { - return new LoggerConfiguration().Enrich.FromLogContext().WriteTo.Console(); //NOSONAR false positive + return new LoggerConfiguration() //NOSONAR false positive + .ReadFrom.Configuration(configuration) + .Enrich.FromLogContext().WriteTo.Console(); } - + private static ILoggerFactory CreateLoggerFactory() { Log.Logger = LoggerConfiguration.CreateLogger(); - return LoggerFactory.Create(logging => { logging.AddSerilog(Log.Logger); }).AddSerilog(); + + return LoggerFactory.Create(logging => logging.AddSerilog(Log.Logger)); } private static void SetupLogAop(ref IServiceCollection services, LogOptions logOptions) @@ -89,7 +86,7 @@ private static void SetupLogAop(ref IServiceCollection services, LogOptions logO services.AddMvc(options => options.Filters.Add(new AopControllerAttribute(logOptions.UseAopTrace))); services.AddMvc(options => options.Filters.Add(new AopExceptionFilterAttribute())); } - + private static void SetupGraylog(LogOptions logOptions) { if (logOptions?.UseGraylog == true && logOptions.GrayLog != null) @@ -108,50 +105,23 @@ private static void SetupGraylog(LogOptions logOptions) } } - private static Serilog.Sinks.Graylog.Core.Transport.TransportType GetGraylogTransportTypeFromString(string transportType) + private static TransportType GetGraylogTransportTypeFromString(string transportType) { - if (transportType == null) return Serilog.Sinks.Graylog.Core.Transport.TransportType.Udp; + if (transportType == null) return TransportType.Udp; return transportType.ToLower() switch { - "tcp" => Serilog.Sinks.Graylog.Core.Transport.TransportType.Tcp, - "udp" => Serilog.Sinks.Graylog.Core.Transport.TransportType.Udp, - "http" => Serilog.Sinks.Graylog.Core.Transport.TransportType.Http, - _ => Serilog.Sinks.Graylog.Core.Transport.TransportType.Udp + "tcp" => TransportType.Tcp, + "udp" => TransportType.Udp, + "http" => TransportType.Http, + _ => TransportType.Udp }; } private static string GetValidPath(string inputPath, string optionalFileName) { if (string.IsNullOrEmpty(inputPath) || string.IsNullOrEmpty(Path.GetFileName(inputPath))) - { return Path.Combine(FileOperations.GetApplicationPath() ?? string.Empty, optionalFileName); - } return Path.GetFullPath(inputPath); } - - private static void SetLogLevel(string logEventLevel = "verbose") - { - switch (logEventLevel.ToLower()) - { - case "warning": - LoggerConfiguration.MinimumLevel.Warning(); - return; - case "verbose": - LoggerConfiguration.MinimumLevel.Verbose(); - return; - case "fatal": - LoggerConfiguration.MinimumLevel.Fatal(); - return; - case "error": - LoggerConfiguration.MinimumLevel.Error(); - return; - case "information": - LoggerConfiguration.MinimumLevel.Information(); - return; - default: - LoggerConfiguration.MinimumLevel.Debug(); - return; - } - } } \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.MediatR/Behaviors/ValidationBehavior.cs b/source/Modules/Devon4Net.Infrastructure.MediatR/Behaviors/ValidationBehavior.cs new file mode 100644 index 00000000..354c8b94 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.MediatR/Behaviors/ValidationBehavior.cs @@ -0,0 +1,45 @@ +using FluentValidation; +using MediatR; + +namespace Devon4Net.Infrastructure.MediatR.Behaviors; + +/// +/// Represents the validation behavior middleware. +/// +/// The request type. +/// The response type. +public sealed class ValidationBehavior : IPipelineBehavior + where TRequest : class, IRequest + where TResponse : class +{ + private readonly IEnumerable> _validators; + + /// + /// Initializes a new instance of the class. + /// + /// The validator for the current request type. + public ValidationBehavior(IEnumerable> validators) => _validators = validators; + + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + if (!_validators.Any()) + { + return await next(); + } + + var context = new ValidationContext(request); + + var failures = _validators + .Select(v => v.Validate(context)) + .SelectMany(result => result.Errors) + .Where(f => f != null) + .ToList(); + + if (failures.Count != 0) + { + throw new ValidationException(failures); + } + + return await next(); + } +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.MediatR/Command/CommandBase.cs b/source/Modules/Devon4Net.Infrastructure.MediatR/Command/CommandBase.cs index cb858b73..dbb5c7fa 100644 --- a/source/Modules/Devon4Net.Infrastructure.MediatR/Command/CommandBase.cs +++ b/source/Modules/Devon4Net.Infrastructure.MediatR/Command/CommandBase.cs @@ -2,7 +2,5 @@ namespace Devon4Net.Infrastructure.MediatR.Command { - public class CommandBase : ActionBase where T : class - { - } -} + public abstract record CommandBase : ActionBase where T : class; +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.MediatR/Common/ActionBase.cs b/source/Modules/Devon4Net.Infrastructure.MediatR/Common/ActionBase.cs index a1537375..ab5896fa 100644 --- a/source/Modules/Devon4Net.Infrastructure.MediatR/Common/ActionBase.cs +++ b/source/Modules/Devon4Net.Infrastructure.MediatR/Common/ActionBase.cs @@ -2,7 +2,7 @@ namespace Devon4Net.Infrastructure.MediatR.Common { - public class ActionBase : IRequest where T : class + public record ActionBase : IRequest where T : class { public DateTime Timestamp { get; } public string MessageType { get; } @@ -15,4 +15,4 @@ protected ActionBase() MessageType = GetType().Name; } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.MediatR/MediatRConfiguration.cs b/source/Modules/Devon4Net.Infrastructure.MediatR/MediatRConfiguration.cs index 1edbfd17..a6a609fd 100644 --- a/source/Modules/Devon4Net.Infrastructure.MediatR/MediatRConfiguration.cs +++ b/source/Modules/Devon4Net.Infrastructure.MediatR/MediatRConfiguration.cs @@ -1,20 +1,17 @@ -using System.Reflection; -using Devon4Net.Infrastructure.Common.Constants; +using Devon4Net.Infrastructure.Common.Constants; using Devon4Net.Infrastructure.Common.Handlers; -using Devon4Net.Infrastructure.Common.Helpers; -using Devon4Net.Infrastructure.Common.Helpers.Interfaces; -using Devon4Net.Infrastructure.LiteDb.LiteDb; using Devon4Net.Infrastructure.LiteDb.Repository; -using Devon4Net.Infrastructure.Common; using Devon4Net.Infrastructure.MediatR.Data.Service; -using Devon4Net.Infrastructure.MediatR.Domain.Database; using Devon4Net.Infrastructure.MediatR.Domain.Entities; using Devon4Net.Infrastructure.MediatR.Domain.ServiceInterfaces; using Devon4Net.Infrastructure.MediatR.Handler; using Devon4Net.Infrastructure.MediatR.Options; -using MediatR; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System.Reflection; +using Devon4Net.Infrastructure.Common; +using Devon4Net.Infrastructure.LiteDb.LiteDb; +using Devon4Net.Infrastructure.MediatR.Domain.Database; namespace Devon4Net.Infrastructure.MediatR { @@ -25,23 +22,20 @@ public static void SetupMediatR(this IServiceCollection services, IConfiguration var mediatROptions = services.GetTypedOptions(configuration, OptionsDefinition.MediatR); if (mediatROptions?.EnableMediatR != true) return; - ConfigureMediatRGenericDependencyInjection(ref services); - SetupMediatRBackupLocalDatabase(ref services, ref mediatROptions); + services.AddTransient(typeof(IMediatRHandler), typeof(MediatRHandler)); + if (mediatROptions.Backup?.UseLocalBackup != true) return; + SetupMediatRBackupLocalDatabase(ref services); } - private static void ConfigureMediatRGenericDependencyInjection(ref IServiceCollection services) + private static void SetupMediatRBackupLocalDatabase(ref IServiceCollection services) { - services.AddTransient(typeof(IJsonHelper), typeof(JsonHelper)); services.AddTransient(typeof(ILiteDbRepository), typeof(LiteDbRepository)); services.AddTransient(typeof(IMediatRBackupService), typeof(MediatRBackupService)); services.AddTransient(typeof(IMediatRHandler), typeof(MediatRHandler)); services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); - } - private static void SetupMediatRBackupLocalDatabase(ref IServiceCollection services, ref MediatROptions mediatROptions) - { + Devon4NetLogger.Information("Please setup your database in order to have the RabbitMq messaging backup feature"); - if (mediatROptions.Backup?.UseLocalBackup != true) return; Devon4NetLogger.Information("RabbitMq messaging backup feature is going to be used via LiteDb"); services.AddSingleton(); diff --git a/source/Modules/Devon4Net.Infrastructure.MediatR/Query/QueryBase.cs b/source/Modules/Devon4Net.Infrastructure.MediatR/Query/QueryBase.cs index 37789aad..d749a862 100644 --- a/source/Modules/Devon4Net.Infrastructure.MediatR/Query/QueryBase.cs +++ b/source/Modules/Devon4Net.Infrastructure.MediatR/Query/QueryBase.cs @@ -2,7 +2,5 @@ namespace Devon4Net.Infrastructure.MediatR.Query { - public abstract class QueryBase : ActionBase where T : class - { - } + public abstract record QueryBase : ActionBase where T : class; } \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.MediatR/Samples/Query/GetUserQuery.cs b/source/Modules/Devon4Net.Infrastructure.MediatR/Samples/Query/GetUserQuery.cs index f16a887c..d123930a 100644 --- a/source/Modules/Devon4Net.Infrastructure.MediatR/Samples/Query/GetUserQuery.cs +++ b/source/Modules/Devon4Net.Infrastructure.MediatR/Samples/Query/GetUserQuery.cs @@ -3,7 +3,7 @@ namespace Devon4Net.Infrastructure.MediatR.Samples.Query { - public class GetUserQuery : QueryBase + public record GetUserQuery : QueryBase { public Guid UserId { get; set; } @@ -11,6 +11,5 @@ public GetUserQuery(Guid userId) { UserId = userId; } - } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/DatabaseConfiguration.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/DatabaseConfiguration.cs index d2cec005..393ef83b 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/DatabaseConfiguration.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/DatabaseConfiguration.cs @@ -12,16 +12,23 @@ public static class SetupDatabaseConfiguration private const int MaxRetryCount = 10; private static ServiceLifetime ServiceLifetime { get; set; } - public static void SetupDatabase(this IServiceCollection services, string connectionString, DatabaseType databaseType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient, CosmosConfigurationParams cosmosConfigurationParams = null) where T : DbContext + public static void SetupDatabase(this IServiceCollection services, string connectionString, + DatabaseType databaseType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient, + CosmosConfigurationParams cosmosConfigurationParams = null) where T : DbContext { - if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentException($"The provided connection string ({connectionString}) provided does not exists."); + if (string.IsNullOrWhiteSpace(connectionString)) + throw new ArgumentException( + $"The provided connection string ({connectionString}) provided does not exists."); ServiceLifetime = serviceLifetime; SetDatabase(services, databaseType, cosmosConfigurationParams, connectionString); } - public static async Task SetupDatabase(this IServiceCollection services, IConfiguration configuration, string connectionStringName, DatabaseType databaseType, ServiceLifetime serviceLifetime = ServiceLifetime.Transient, bool migrate = false, CosmosConfigurationParams cosmosConfigurationParams = null) where T : DbContext + public static async Task SetupDatabase(this IServiceCollection services, IConfiguration configuration, + string connectionStringName, DatabaseType databaseType, + ServiceLifetime serviceLifetime = ServiceLifetime.Transient, bool migrate = false, + CosmosConfigurationParams cosmosConfigurationParams = null) where T : DbContext { - IConfigurationSection connectionString = GetConnectionString(configuration, connectionStringName); + var connectionString = GetConnectionString(configuration, connectionStringName); ServiceLifetime = serviceLifetime; @@ -32,9 +39,13 @@ public static async Task SetupDatabase(this IServiceCollection services, ICon private static IConfigurationSection GetConnectionString(IConfiguration configuration, string connectionStringName) { var applicationConnectionStrings = configuration.GetSection("ConnectionStrings").GetChildren(); - if (applicationConnectionStrings == null) throw new ArgumentException("There are no connection strings provided."); - var connectionString = applicationConnectionStrings.FirstOrDefault(c => string.Equals(c.Key, connectionStringName, StringComparison.CurrentCultureIgnoreCase)); - if (connectionString == null || string.IsNullOrEmpty(connectionString.Value)) throw new ArgumentException($"The provided connection string ({connectionStringName}) provided does not exists."); + if (applicationConnectionStrings == null) + throw new ArgumentException("There are no connection strings provided."); + var connectionString = applicationConnectionStrings.FirstOrDefault(c => + string.Equals(c.Key, connectionStringName, StringComparison.CurrentCultureIgnoreCase)); + if (connectionString == null || string.IsNullOrEmpty(connectionString.Value)) + throw new ArgumentException( + $"The provided connection string ({connectionStringName}) provided does not exists."); return connectionString; } @@ -45,15 +56,15 @@ private static async Task Migrate(IServiceCollection services) where T : DbCo using var sp = services.BuildServiceProvider(); if (sp == null) { - Devon4NetLogger.Error($"Unable to build the service provider, the migration {typeof(T).FullName} will not be launched"); + Devon4NetLogger.Error( + $"Unable to build the service provider, the migration {typeof(T).FullName} will not be launched"); } else { var context = sp.GetService(typeof(T)); if (context == null) - { - Devon4NetLogger.Error($"Unable to resolve {typeof(T).FullName} and the migration will not be launched"); - } + Devon4NetLogger.Error( + $"Unable to resolve {typeof(T).FullName} and the migration will not be launched"); ((T)context)?.Database.Migrate(); await sp.DisposeAsync().ConfigureAwait(false); @@ -71,60 +82,64 @@ private static void SetDatabase(IServiceCollection services, DatabaseType dat switch (databaseType) { case DatabaseType.SqlServer: - services.AddDbContext(options => options.UseSqlServer(connectionString, - sqlServerOptionsAction: sqlOptions => - { - sqlOptions.EnableRetryOnFailure( - maxRetryCount: MaxRetryCount, - maxRetryDelay: TimeSpan.FromSeconds(MaxRetryDelay), - errorNumbersToAdd: null); - }),ServiceLifetime); + services.AddDbContext(options => options.UseSqlServer(connectionString), ServiceLifetime); break; + case DatabaseType.InMemory: services.AddDbContext(options => options.UseInMemoryDatabase(connectionString), ServiceLifetime); break; + case DatabaseType.MySql: - services.AddDbContext(options => options.UseMySql(ServerVersion.AutoDetect(connectionString), sqlOptions => - { - sqlOptions.EnableRetryOnFailure( - maxRetryCount: MaxRetryCount, - maxRetryDelay: TimeSpan.FromSeconds(MaxRetryDelay), - errorNumbersToAdd: new List()); - }),ServiceLifetime); + services.AddDbContext(options => options.UseMySql(ServerVersion.AutoDetect(connectionString), + sqlOptions => + { + sqlOptions.EnableRetryOnFailure( + MaxRetryCount, + TimeSpan.FromSeconds(MaxRetryDelay), + new List()); + }), ServiceLifetime); break; + case DatabaseType.MariaDb: - services.AddDbContext(options => options.UseMySql(ServerVersion.AutoDetect(connectionString), sqlOptions => - { - sqlOptions.EnableRetryOnFailure( - maxRetryCount: MaxRetryCount, - maxRetryDelay: TimeSpan.FromSeconds(MaxRetryDelay), - errorNumbersToAdd: new List()); - }),ServiceLifetime); + services.AddDbContext(options => options.UseMySql(ServerVersion.AutoDetect(connectionString), + sqlOptions => + { + sqlOptions.EnableRetryOnFailure( + MaxRetryCount, + TimeSpan.FromSeconds(MaxRetryDelay), + new List()); + }), ServiceLifetime); break; + case DatabaseType.Sqlite: services.AddDbContext(options => options.UseSqlite(connectionString), ServiceLifetime); break; + case DatabaseType.Cosmos: if (cosmosConfigurationParams == null) throw new ArgumentException("The Cosmos configuration can not be null."); services.AddDbContext(options => options.UseCosmos(cosmosConfigurationParams.Endpoint, cosmosConfigurationParams.Key, cosmosConfigurationParams.DatabaseName), ServiceLifetime); break; + case DatabaseType.PostgreSQL: services.AddDbContext(options => options.UseNpgsql(connectionString, sqlOptions => { sqlOptions.EnableRetryOnFailure( - maxRetryCount: MaxRetryCount, - maxRetryDelay: TimeSpan.FromSeconds(MaxRetryDelay), - errorCodesToAdd: new List()); - }),ServiceLifetime); + MaxRetryCount, + TimeSpan.FromSeconds(MaxRetryDelay), + new List()); + }), ServiceLifetime); break; + case DatabaseType.FireBird: services.AddDbContext(options => options.UseFirebird(connectionString), ServiceLifetime); break; + case DatabaseType.Oracle: services.AddDbContext(options => options.UseOracle(connectionString), ServiceLifetime); break; + default: throw new ArgumentException("Not provided a database driver"); } diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/PredicateBuilder.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/PredicateBuilder.cs index c8e20a6c..ad5ed6a3 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/PredicateBuilder.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Common/PredicateBuilder.cs @@ -10,33 +10,41 @@ public static class PredicateBuilder public static Expression> True() { return _ => true; } /// - /// Creates a predicate that evaluates to false. + /// Creates a predicate that evaluates to false. /// - public static Expression> False() { return _ => false; } + public static Expression> False() + { + return _ => false; + } /// - /// Creates a predicate expression from the specified lambda expression. + /// Creates a predicate expression from the specified lambda expression. /// - public static Expression> Create(Expression> predicate) { return predicate; } + public static Expression> Create(Expression> predicate) + { + return predicate; + } /// - /// Combines the first predicate with the second using the logical "and". + /// Combines the first predicate with the second using the logical "and". /// - public static Expression> And(this Expression> first, Expression> second) + public static Expression> And(this Expression> first, + Expression> second) { return first.Compose(second, Expression.AndAlso); } /// - /// Combines the first predicate with the second using the logical "or". + /// Combines the first predicate with the second using the logical "or". /// - public static Expression> Or(this Expression> first, Expression> second) + public static Expression> Or(this Expression> first, + Expression> second) { return first.Compose(second, Expression.OrElse); } /// - /// Negates the predicate. + /// Negates the predicate. /// public static Expression> Not(this Expression> expression) { @@ -45,9 +53,10 @@ public static Expression> Not(this Expression> ex } /// - /// Combines the first expression with the second using the specified merge function. + /// Combines the first expression with the second using the specified merge function. /// - static Expression Compose(this Expression first, Expression second, Func merge) + private static Expression Compose(this Expression first, Expression second, + Func merge) { // zip parameters (map from parameters of second to parameters of first) var map = first.Parameters @@ -61,29 +70,27 @@ static Expression Compose(this Expression first, Expression second, return Expression.Lambda(merge(first.Body, secondBody), first.Parameters); } - class ParameterRebinder : ExpressionVisitor + private sealed class ParameterRebinder : ExpressionVisitor { - readonly Dictionary _map; + private readonly Dictionary _map; private ParameterRebinder(Dictionary map) { - this._map = map ?? new Dictionary(); + _map = map ?? new Dictionary(); } - public static Expression ReplaceParameters(Dictionary map, Expression exp) + public static Expression ReplaceParameters(Dictionary map, + Expression exp) { return new ParameterRebinder(map).Visit(exp); } protected override Expression VisitParameter(ParameterExpression node) { - if (_map.TryGetValue(node, out ParameterExpression replacement)) - { - node = replacement; - } + if (_map.TryGetValue(node, out var replacement)) node = replacement; return base.VisitParameter(node); } } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Devon4Net.Infrastructure.UnitOfWork.csproj b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Devon4Net.Infrastructure.UnitOfWork.csproj index de1c57e2..7a9fdc12 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Devon4Net.Infrastructure.UnitOfWork.csproj +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Devon4Net.Infrastructure.UnitOfWork.csproj @@ -23,15 +23,15 @@ - - 1701;1702;8034;1705;NU1605;NU1608;NU1701;CS1705 - 0 + + 1701;1702;8034;1705;NU1605;NU1608;NU1701;CS1705 + 0 - - 1701;1702;8034;1705;NU1605;NU1608;NU1701;CS1705 - 0 + + 1701;1702;8034;1705;NU1605;NU1608;NU1701;CS1705 + 0 @@ -49,25 +49,26 @@ + - + - - - True - \ - - - True - \ - - - True - \ - - + + + True + \ + + + True + \ + + + True + \ + + diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/ContextNullException.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/ContextNullException.cs index 51f30a70..76db857f 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/ContextNullException.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/ContextNullException.cs @@ -1,4 +1,6 @@ -namespace Devon4Net.Infrastructure.UnitOfWork.Exceptions +using System.Runtime.Serialization; + +namespace Devon4Net.Infrastructure.UnitOfWork.Exceptions { [Serializable] public class ContextNullException : Exception @@ -15,9 +17,9 @@ public ContextNullException(string message, Exception innerException) : base(mes { } - protected ContextNullException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) + protected ContextNullException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/RepositoryNotFoundException.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/RepositoryNotFoundException.cs index a4997a6e..586912aa 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/RepositoryNotFoundException.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/RepositoryNotFoundException.cs @@ -1,4 +1,6 @@ -namespace Devon4Net.Infrastructure.UnitOfWork.Exceptions +using System.Runtime.Serialization; + +namespace Devon4Net.Infrastructure.UnitOfWork.Exceptions { [Serializable] public class RepositoryNotFoundException : Exception @@ -15,9 +17,9 @@ public RepositoryNotFoundException(string message, Exception innerException) : b { } - protected RepositoryNotFoundException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) + protected RepositoryNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/TransactionNullException.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/TransactionNullException.cs index 7bdb6510..0787c89d 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/TransactionNullException.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Exceptions/TransactionNullException.cs @@ -1,4 +1,6 @@ -namespace Devon4Net.Infrastructure.UnitOfWork.Exceptions +using System.Runtime.Serialization; + +namespace Devon4Net.Infrastructure.UnitOfWork.Exceptions { [Serializable] public class TransactionNullException : Exception @@ -15,9 +17,9 @@ public TransactionNullException(string message, Exception innerException) : base { } - protected TransactionNullException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) + protected TransactionNullException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Pagination/PaginationBase.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Pagination/PaginationBase.cs index 6698688c..f0947dbe 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Pagination/PaginationBase.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Pagination/PaginationBase.cs @@ -7,14 +7,8 @@ public abstract class PaginationBase public int PageSize { get; set; } public int RowCount { get; set; } - public int FirstRowOnPage - { - get { return ((CurrentPage - 1) * PageSize) + 1; } - } + public int FirstRowOnPage => (CurrentPage - 1) * PageSize + 1; - public int LastRowOnPage - { - get { return Math.Min(CurrentPage * PageSize, RowCount); } - } + public int LastRowOnPage => Math.Min(CurrentPage * PageSize, RowCount); } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Projector/IProjector.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Projector/IProjector.cs new file mode 100644 index 00000000..2e4ab351 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Projector/IProjector.cs @@ -0,0 +1,20 @@ +using System.Linq.Expressions; + +namespace Devon4Net.Infrastructure.UnitOfWork.Projector; + +public interface IProjector +{ + /// + /// Get Projection from entity + /// + /// + /// + /// + /// + /// + /// + Task> GetProjection( + Func, IQueryable> projection, + int? page = null, + int? pageSize = null) where TEntity : class; +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Projector/Projector.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Projector/Projector.cs new file mode 100644 index 00000000..972a1f2c --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Projector/Projector.cs @@ -0,0 +1,42 @@ +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; + +namespace Devon4Net.Infrastructure.UnitOfWork.Projector; + +public class Projector : IProjector +{ + private readonly DbContext _context; + + protected Projector(DbContext context) + { + _context = context; + } + + /// + /// Get Projection from entity + /// + /// + /// + /// + /// + /// + /// + public async Task> GetProjection( + Func, IQueryable> projection, + int? page = null, + int? pageSize = null) where TEntity : class + { + var dbSet = _context.Set(); + + var query = dbSet.AsQueryable(); + + if (page != null && pageSize != null) + { + query = query.Skip((int)(page - 1) * (int)pageSize).Take((int)pageSize); + } + + var projectionQuery = projection(query); + + return await projectionQuery.ToListAsync(); + } +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/RawSqlRepository/IRawSqlRepository.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/RawSqlRepository/IRawSqlRepository.cs new file mode 100644 index 00000000..2ff84fb5 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/RawSqlRepository/IRawSqlRepository.cs @@ -0,0 +1,43 @@ +using System.Data; + +namespace Devon4Net.Infrastructure.UnitOfWork.RawSqlRepository +{ + public interface IRawSqlRepository + { + /// + /// This method executes a SQL query against the database using the Dapper library. + /// + /// + /// + /// + /// + /// + /// It returns an IEnumerable of the provided object containing the results of the query. + /// + Task> QueryAsync(string sql, object parameters = null, IDbTransaction transaction = null); + + /// + /// This method executes a SQL query against the database using the Dapper library. + /// + /// + /// + /// + /// + /// + /// Returns the first result, or null if the query returns no results. + /// + Task QueryFirstOrDefaultAsync(string sql, object parameters = null, IDbTransaction transaction = null); + + /// + /// This method executes a SQL query against the database using the Dapper library. + /// + /// + /// + /// + /// + /// + /// It returns the number of affected rows. + /// + Task ExecuteAsync(string sql, object parameters = null, IDbTransaction transaction = null); + } +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/RawSqlRepository/RawSqlRepository.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/RawSqlRepository/RawSqlRepository.cs new file mode 100644 index 00000000..b52430e8 --- /dev/null +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/RawSqlRepository/RawSqlRepository.cs @@ -0,0 +1,66 @@ +using System.Data; +using Dapper; +using Microsoft.EntityFrameworkCore; + +namespace Devon4Net.Infrastructure.UnitOfWork.RawSqlRepository +{ + public class RawSqlRepository : IRawSqlRepository + { + /// + /// Dapper Base Repository + /// + /// The data base context to work with + /// + protected RawSqlRepository(DbContext context) + { + DbContext = context; + } + + private DbContext DbContext { get; set; } + + /// + /// This method executes a SQL query against the database using the Dapper library. + /// + /// + /// + /// + /// + /// + /// It returns an IEnumerable of the provided object containing the results of the query. + /// + public async Task> QueryAsync(string sql, object parameters = null, IDbTransaction transaction = null) + { + return await DbContext.Database.GetDbConnection().QueryAsync(sql, parameters, transaction); + } + + /// + /// This method executes a SQL query against the database using the Dapper library. + /// + /// + /// + /// + /// + /// + /// Returns the first result, or null if the query returns no results. + /// + public async Task QueryFirstOrDefaultAsync(string sql, object parameters = null, IDbTransaction transaction = null) + { + return await DbContext.Database.GetDbConnection().QueryFirstOrDefaultAsync(sql, parameters, transaction); + } + + /// + /// This method executes a SQL query against the database using the Dapper library. + /// + /// + /// + /// + /// + /// + /// It returns the number of affected rows. + /// + public async Task ExecuteAsync(string sql, object parameters = null, IDbTransaction transaction = null) + { + return await DbContext.Database.GetDbConnection().ExecuteAsync(sql, parameters, transaction); + } + } +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Repository/Repository.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Repository/Repository.cs index 5b8eec4d..f88e9166 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Repository/Repository.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Repository/Repository.cs @@ -1,19 +1,16 @@ using System.ComponentModel; using System.Linq.Expressions; +using System.Runtime.CompilerServices; using Devon4Net.Infrastructure.Common; using Devon4Net.Infrastructure.UnitOfWork.Exceptions; using Devon4Net.Infrastructure.UnitOfWork.Pagination; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; namespace Devon4Net.Infrastructure.UnitOfWork.Repository { public class Repository : IRepository where T : class { - private DbContext DbContext { get; set; } - private bool DbContextBehaviour { get; } - - private IQueryable Queryable => SetQuery(); - /// /// Initialization class /// @@ -25,7 +22,12 @@ public Repository(DbContext context, bool dbContextBehaviour = false) DbContextBehaviour = dbContextBehaviour; } - public async Task Create(T entity, bool autoSaveChanges= true, bool detach = true) + private DbContext DbContext { get; set; } + private bool DbContextBehaviour { get; } + + private IQueryable Queryable => SetQuery(); + + public async Task Create(T entity, bool autoSaveChanges = true, bool detach = true) { try { @@ -107,9 +109,11 @@ public async Task> Get(Expression> predicate = null) return await GetQueryFromPredicate(predicate).ToListAsync().ConfigureAwait(false); } - public async Task> Get(Expression> predicate, Expression> keySelector, ListSortDirection sortDirection) + public async Task> Get(Expression> predicate, Expression> keySelector, + ListSortDirection sortDirection) { - return await GetSortedQueryFromPredicate(predicate, keySelector, sortDirection).ToListAsync().ConfigureAwait(false); + return await GetSortedQueryFromPredicate(predicate, keySelector, sortDirection).ToListAsync() + .ConfigureAwait(false); } public async Task> Get(IList include, Expression> predicate = null) @@ -117,7 +121,8 @@ public async Task> Get(IList include, Expression> return await GetResultSetWithNestedProperties(include, predicate).ToListAsync().ConfigureAwait(false); } - public Task> Get(int currentPage, int pageSize, IList includedNestedFiels, Expression> predicate = null) + public Task> Get(int currentPage, int pageSize, IList includedNestedFiels, + Expression> predicate = null) { return GetResultSetWithNestedPropertiesPaged(currentPage, pageSize, includedNestedFiels, predicate); } @@ -127,9 +132,11 @@ public Task> Get(int currentPage, int pageSize, Expression> Get(int currentPage, int pageSize, Expression> predicate , Expression> keySelector, ListSortDirection sortDirection) + public Task> Get(int currentPage, int pageSize, Expression> predicate, + Expression> keySelector, ListSortDirection sortDirection) { - return GetPagedResult(currentPage, pageSize, GetSortedQueryFromPredicate(predicate, keySelector, sortDirection)); + return GetPagedResult(currentPage, pageSize, + GetSortedQueryFromPredicate(predicate, keySelector, sortDirection)); } public Task Count(Expression> predicate = null) @@ -139,38 +146,73 @@ public Task Count(Expression> predicate = null) private IQueryable GetQueryFromPredicate(Expression> predicate) { - return predicate != null ? Queryable.Where(predicate): Queryable; + return predicate != null ? Queryable.Where(predicate) : Queryable; + } + + public Task> ExecuteSpGetList(string spName, params SqlParameter[] parameters) + { + GenerateSpQuery(spName, parameters, out var parameterValues, out var query); + + return DbContext.Set().FromSqlInterpolated(FormattableStringFactory.Create(query, parameterValues)) + .ToListAsync(); } - private IQueryable GetSortedQueryFromPredicate(Expression> predicate, Expression> keySelector, ListSortDirection sortDirection) + public Task ExecuteSp(string spName, params SqlParameter[] parameters) + { + GenerateSpQuery(spName, parameters, out var parameterValues, out var query); + + return DbContext.Database.ExecuteSqlInterpolatedAsync(FormattableStringFactory.Create(query, parameterValues)); + } + + private static void GenerateSpQuery(string spName, SqlParameter[] parameters, out string parameterValues, out string query) + { + var parameterList = new List(parameters); + var parameterPlaceholders = string.Join(", ", parameterList.Select((p, i) => $"@p{i}")); + parameterValues = string.Join(", ", parameterList.Select((p, i) => $"@p{i} = {p.Value}")); + + query = $"EXEC {spName} {parameterPlaceholders}"; + } + + private IQueryable GetSortedQueryFromPredicate(Expression> predicate, + Expression> keySelector, ListSortDirection sortDirection) { if (sortDirection == ListSortDirection.Ascending) - { return predicate != null ? Queryable.Where(predicate).OrderBy(keySelector) : Queryable.OrderBy(keySelector); - } - return predicate != null ? Queryable.Where(predicate).OrderByDescending(keySelector) : Queryable.OrderByDescending(keySelector); + return predicate != null + ? Queryable.Where(predicate).OrderByDescending(keySelector) + : Queryable.OrderByDescending(keySelector); } - private IQueryable GetResultSetWithNestedProperties(IList includedNestedFiels, Expression> predicate = null) + private IQueryable GetResultSetWithNestedProperties(IList includedNestedFiels, + Expression> predicate = null) { - return includedNestedFiels.Aggregate(GetQueryFromPredicate(predicate), (current, property) => current.Include(property)); + return includedNestedFiels.Aggregate(GetQueryFromPredicate(predicate), + (current, property) => current.Include(property)); } - private Task> GetResultSetWithNestedPropertiesPaged(int currentPage, int pageSize, IList includedNestedFiels, Expression> predicate = null) + private Task> GetResultSetWithNestedPropertiesPaged(int currentPage, int pageSize, + IList includedNestedFiels, Expression> predicate = null) { return GetPagedResult(currentPage, pageSize, GetResultSetWithNestedProperties(includedNestedFiels, predicate)); } - private static async Task> GetPagedResult(int currentPage, int pageSize, IQueryable resultList) + private static async Task> GetPagedResult(int currentPage, int pageSize, + IQueryable resultList) { - var pagedResult = new PaginationResult() { CurrentPage = currentPage, PageSize = pageSize, RowCount = await resultList.CountAsync().ConfigureAwait(false) }; + var pagedResult = new PaginationResult + { + CurrentPage = currentPage, + PageSize = pageSize, + RowCount = await resultList.CountAsync().ConfigureAwait(false) + }; var pageCount = (double)pagedResult.RowCount / pageSize; pagedResult.PageCount = (int)Math.Ceiling(pageCount); var skip = (currentPage - 1) * pageSize; - pagedResult.Results = await resultList.AsNoTracking().Skip(skip).Take(pageSize).ToListAsync().ConfigureAwait(false); + pagedResult.Results = + await resultList.AsNoTracking().Skip(skip).Take(pageSize).ToListAsync().ConfigureAwait(false); return pagedResult; } @@ -181,13 +223,14 @@ private IQueryable SetQuery() where S : class return DbContext.Set().AsNoTracking(); } - private void SetContextBehaviour( bool enabled) + private void SetContextBehaviour(bool enabled) { DbContext.ChangeTracker.AutoDetectChangesEnabled = enabled; DbContext.ChangeTracker.LazyLoadingEnabled = enabled; - DbContext.ChangeTracker.QueryTrackingBehavior = enabled ? QueryTrackingBehavior.TrackAll : QueryTrackingBehavior.NoTracking; + DbContext.ChangeTracker.QueryTrackingBehavior = + enabled ? QueryTrackingBehavior.TrackAll : QueryTrackingBehavior.NoTracking; } internal void SetContext(DbContext context) @@ -195,4 +238,4 @@ internal void SetContext(DbContext context) DbContext = context ?? throw new ContextNullException(nameof(context)); } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Service/IService.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Service/IService.cs deleted file mode 100644 index 6fcc50b6..00000000 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Service/IService.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Devon4Net.Infrastructure.UnitOfWork.Service -{ - public interface IService - { - } -} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Service/Service.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Service/Service.cs deleted file mode 100644 index 67377b95..00000000 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/Service/Service.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Devon4Net.Infrastructure.UnitOfWork.UnitOfWork; -using Microsoft.EntityFrameworkCore; - -namespace Devon4Net.Infrastructure.UnitOfWork.Service -{ - public class Service : IService where TContext : DbContext - { - public IUnitOfWork UoW { get; } - - public Service(IUnitOfWork uoW) - { - UoW = uoW; - } - } -} diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/IUnitOfWork.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/IUnitOfWork.cs index d22b98d8..707813ad 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/IUnitOfWork.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/IUnitOfWork.cs @@ -1,14 +1,22 @@ -using Microsoft.EntityFrameworkCore; +using System.Data; using Microsoft.EntityFrameworkCore.Storage; namespace Devon4Net.Infrastructure.UnitOfWork.UnitOfWork { - public interface IUnitOfWork where TContext : DbContext + public interface IUnitOfWork { - Task GetTransaction(); - Task Commit(IDbContextTransaction transaction); - T Repository() where T : class; - T Repository() where T : class where TS : class; + /// + /// Begins the transaction + /// + Task GetDbTransaction(int secondsTimeout = 30); + IExecutionStrategy CreateExecutionStrategy(); + + Task SaveChanges(); + + /// + /// Commit the transaction if is correct, else rollback. Both cases dispose. + /// + Task CommitTransaction(IDbTransaction transaction); } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/UnitOfWork.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/UnitOfWork.cs index 369d33a0..354491c1 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/UnitOfWork.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWork/UnitOfWork.cs @@ -1,26 +1,35 @@ -using Devon4Net.Infrastructure.Common; +using System.Data; +using Devon4Net.Infrastructure.Common; using Devon4Net.Infrastructure.UnitOfWork.Exceptions; using Devon4Net.Infrastructure.UnitOfWork.Repository; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; -using System.Globalization; namespace Devon4Net.Infrastructure.UnitOfWork.UnitOfWork { - public class UnitOfWork : IUnitOfWork where TContext : DbContext, IDisposable + public class UnitOfWork : IUnitOfWork where TContext : DbContext { - private TContext Context { get; } - private IServiceProvider ServiceProvider { get; } + private const int DefaultSecondsTimeout = 30; - public UnitOfWork(TContext context, IServiceProvider serviceProvider) + protected UnitOfWork(TContext context) { Context = context ?? throw new ContextNullException(nameof(context)); - ServiceProvider = serviceProvider; } - public Task GetTransaction() + private TContext Context { get; } + + public async Task GetDbTransaction(int secondsTimeout = DefaultSecondsTimeout) { - return Context.Database.BeginTransactionAsync(); + Context.Database.SetCommandTimeout(secondsTimeout); + + var dbContextTransaction = await Context.Database.BeginTransactionAsync(); + return dbContextTransaction.GetDbTransaction(); + } + + public Task SaveChanges() + { + //Auditable entities + return Context.SaveChangesAsync(); } public IExecutionStrategy CreateExecutionStrategy() @@ -28,119 +37,28 @@ public IExecutionStrategy CreateExecutionStrategy() return Context.Database.CreateExecutionStrategy(); } - public Task Commit(IDbContextTransaction transaction) + public async Task CommitTransaction(IDbTransaction transaction) { if (transaction == null) - { throw new TransactionNullException("Transaction cannot be null to perform transaction operations."); - } - var transactionSavePointName = DateTime.UtcNow.ToString("yyyyMMddHHmmssfff", CultureInfo.InvariantCulture); - - return Task.Run(async () => - { - try - { - transaction.CreateSavepoint(transactionSavePointName); - _ = await SaveChanges().ConfigureAwait(false); - await transaction.CommitAsync().ConfigureAwait(false); - } - catch (DbUpdateConcurrencyException ex) - { - Devon4NetLogger.Error(ex); - RollbackTransaction(transaction, transactionSavePointName); - throw; - } - catch (Exception ex) - { - Devon4NetLogger.Error(ex); - RollbackTransaction(transaction, transactionSavePointName); - throw; - } - }); - } - - private async Task SaveChanges() - { try { - await Context.SaveChangesAsync().ConfigureAwait(false); - return true; - } - catch (OperationCanceledException ex) - { - Devon4NetLogger.Error(ex); - throw; - } - catch (DbUpdateConcurrencyException ex) - { - foreach (var entry in ex.Entries) - { - var databaseValues = entry.GetDatabaseValues(); + await SaveChanges(); - // Refresh original values to bypass next concurrency check - entry.OriginalValues.SetValues(databaseValues); - } - - Devon4NetLogger.Error(ex); - throw; + transaction.Commit(); } - catch (DbUpdateException ex) + catch (Exception ex) { Devon4NetLogger.Error(ex); + transaction.Rollback(); throw; } - } - - /// - /// ets the typed repository class - /// - /// The inherited repository class - /// The instantiated repository class - public T Repository() where T : class - { - return GetRepository(); - } - - /// - /// Gets the typed repository class and sets the database context - /// - /// The inherited repository class - /// The entity class - /// The instantiated repository class - public T Repository() where T : class where TS : class - { - var repository = GetRepository(); - - (repository as Repository)?.SetContext(Context); - - return repository; - } - - private static void RollbackTransaction(IDbContextTransaction transaction, string savePoint) - { - if (transaction == null) - { - const string message = "Transaction cannot be null to perform transaction operations"; - Devon4NetLogger.Error(message); - throw new TransactionNullException(message); - } - - transaction.RollbackToSavepoint(savePoint); - transaction.Dispose(); - } - - private T GetRepository() where T : class - { - var repositoryType = typeof(T); - var repository = ServiceProvider.GetService(repositoryType); - - if (repository == null) + finally { - throw new RepositoryNotFoundException($"The repository {repositoryType.Name} was not found in the IOC container. Plase register the repository during startup."); + Context.Database.SetCommandTimeout(DefaultSecondsTimeout); + transaction.Dispose(); } - - return repository as T; } } -} +} \ No newline at end of file diff --git a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWorkConfiguration.cs b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWorkConfiguration.cs index 93d24e58..3cc3d2d8 100644 --- a/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWorkConfiguration.cs +++ b/source/Modules/Devon4Net.Infrastructure.UnitOfWork/UnitOfWorkConfiguration.cs @@ -3,29 +3,12 @@ using Devon4Net.Infrastructure.UnitOfWork.UnitOfWork; using Microsoft.Extensions.DependencyInjection; -namespace Devon4Net.Infrastructure.UnitOfWork +namespace Devon4Net.Infrastructure.UnitOfWork; + +public static class UnitOfWorkConfiguration { - public static class UnitOfWorkConfiguration + public static void SetupUnitOfWork(this IServiceCollection services) { - public static void SetupUnitOfWork(this IServiceCollection services) - { - services.AddTransient(typeof(IRepository<>), typeof(Repository<>)); - services.AddTransient(typeof(IUnitOfWork<>), typeof(UnitOfWork<>)); - } - - /// - /// Auto registers the Service and Repository classes from the assembly that contains the assemblyContainerClass class - /// - /// dotner service collection - /// Type of the class that is located in the assembly to scan - /// Sufix namme of Service class - /// Sufix namme of Repository class - public static void SetupUnitOfWork(this IServiceCollection services, Type assemblyContainerClass, string serviceSufix = "Service", string repositorySufix = "Repository") - { - services.AddTransient(typeof(IRepository<>), typeof(Repository<>)); - services.AddTransient(typeof(IUnitOfWork<>), typeof(UnitOfWork<>)); - - services.AutoRegisterClasses(new List { assemblyContainerClass }, new List { serviceSufix, repositorySufix }); - } + services.AddTransient(typeof(IRepository<>), typeof(Repository<>)); } -} +} \ No newline at end of file diff --git a/source/Modules/nuget.sh b/source/Modules/nuget.sh new file mode 100644 index 00000000..42e0b312 --- /dev/null +++ b/source/Modules/nuget.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Directory where the NuGet packages will be stored +nugets_directory="$(pwd)/nugets" + +# Ask the user for the version input +read -p "Enter the new version: " new_version + +# Create the nugets directory if it doesn't exist +mkdir -p "$nugets_directory"/"$new_version" + +# Find all csproj files and build/pack their projects +find . -type f -name "*.csproj" | while read -r csproj_file; do + echo "Processing project: $csproj_file" + + # Update the version in the csproj file + sed -i "s#.*#$new_version#g" "$csproj_file" + + # Get the directory of the csproj file + project_dir=$(dirname "$csproj_file") + + # Build the project + dotnet build --configuration Release "$project_dir" + + # Pack the project + dotnet pack --configuration Release "$project_dir" + + # Move the generated nupkg files to the nugets directory + mv "$project_dir"/bin/Release/*"$new_version".nupkg "$nugets_directory"/"$new_version" +done \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/.dockerignore b/source/Templates/CleanArchitecture/.dockerignore new file mode 100644 index 00000000..fe1152bd --- /dev/null +++ b/source/Templates/CleanArchitecture/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/AssemblyReference.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/AssemblyReference.cs new file mode 100644 index 00000000..c685a74f --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/AssemblyReference.cs @@ -0,0 +1,3 @@ +namespace Devon4Net.Application; + +public sealed record AssemblyReference; \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Converters/EmployeeConverter.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Converters/EmployeeConverter.cs new file mode 100644 index 00000000..3b3a756a --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Converters/EmployeeConverter.cs @@ -0,0 +1,29 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Domain.Entities; + +namespace Devon4Net.Application.Converters; + +/// +/// EmployeeConverter +/// +public static class EmployeeConverter +{ + /// + /// ModelToDto transformation + /// + /// + /// + public static EmployeeDto ModelToDto(Employee? item) + { + if (item == null) return new EmployeeDto(); + + return new EmployeeDto + { + Id = item.Id, + Name = item.Name, + Surname = item.Surname, + Mail = item.Mail + }; + } + +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Devon4Net - Backup.Application.csproj b/source/Templates/CleanArchitecture/Devon4Net.Application/Devon4Net - Backup.Application.csproj new file mode 100644 index 00000000..caa546ce --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Devon4Net - Backup.Application.csproj @@ -0,0 +1,28 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Devon4Net.Application.csproj b/source/Templates/CleanArchitecture/Devon4Net.Application/Devon4Net.Application.csproj new file mode 100644 index 00000000..fca4c24b --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Devon4Net.Application.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Dtos/EmployeeDto.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Dtos/EmployeeDto.cs new file mode 100644 index 00000000..d1db3e37 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Dtos/EmployeeDto.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace Devon4Net.Application.Dtos; + +/// +/// Employee definition +/// +public class EmployeeDto +{ + /// + /// the Id + /// + public Guid Id { get; init; } + + /// + /// the Name + /// + [Required] + public string? Name { get; init; } + + /// + /// the Surname + /// + [Required] + public string? Surname { get; init; } + + /// + /// the Mail + /// + [Required] + public string? Mail { get; init; } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Exceptions/EmployeeNotFoundException.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Exceptions/EmployeeNotFoundException.cs new file mode 100644 index 00000000..2eb54d0a --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Exceptions/EmployeeNotFoundException.cs @@ -0,0 +1,51 @@ +using Devon4Net.Infrastructure.Common.Exceptions; +using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; + +namespace Devon4Net.Application.Exceptions; + +/// +/// Custom exception EmployeeNotFoundException +/// +[Serializable] +public class EmployeeNotFoundException : WebApiException +{ + /// + /// Gets the forced http status code to be fired on the exception manager. + /// + public override int StatusCode => StatusCodes.Status404NotFound; + + /// + /// Gets a value indicating whether show the message on the response?. + /// + public override bool ShowMessage => true; + + public EmployeeNotFoundException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public EmployeeNotFoundException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message + /// and a reference to the inner exception that caused this exception. + /// + /// The message that describes the error. + /// The exception that caused this exception. + public EmployeeNotFoundException(string message, Exception innerException) + : base(message, innerException) + { + } + + protected EmployeeNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeCommand.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeCommand.cs new file mode 100644 index 00000000..b3d0b3be --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeCommand.cs @@ -0,0 +1,6 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Infrastructure.MediatR.Command; + +namespace Devon4Net.Application.Features.Command.CreateEmployee; + +public record CreateEmployeeCommand(string? Name, string? Surname, string? Mail) : CommandBase; \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeCommandValidation.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeCommandValidation.cs new file mode 100644 index 00000000..9558ca6b --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeCommandValidation.cs @@ -0,0 +1,13 @@ +using FluentValidation; + +namespace Devon4Net.Application.Features.Command.CreateEmployee; + +public class InsertAuthorCommandValidation : AbstractValidator +{ + public InsertAuthorCommandValidation() + { + RuleFor(x => x.Name).NotEmpty().WithMessage("FistName must be not empty"); + RuleFor(x => x.Surname).NotEmpty(); + RuleFor(x => x.Mail).NotEmpty().EmailAddress(); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeHandler.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeHandler.cs new file mode 100644 index 00000000..473ad191 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Command/CreateEmployee/CreateEmployeeHandler.cs @@ -0,0 +1,46 @@ +using Devon4Net.Application.Converters; +using Devon4Net.Application.Dtos; +using Devon4Net.Application.Ports.Repositories; +using Devon4Net.Infrastructure.Common; +using MediatR; + +namespace Devon4Net.Application.Features.Command.CreateEmployee; + +public class CreateEmployeeHandler : IRequestHandler +{ + private readonly IEmployeeRepository _employeeRepository; + + public CreateEmployeeHandler(IEmployeeRepository employeeRepository) + { + _employeeRepository = employeeRepository; + } + + public async Task Handle(CreateEmployeeCommand request, CancellationToken cancellationToken) + { + Devon4NetLogger.Debug("Started CreateEmployeeHandler"); + + CheckRequestParams(request); + + var employee = await _employeeRepository.Create(request.Name!, request.Surname!, request.Mail!); + + return EmployeeConverter.ModelToDto(employee); + } + + private static void CheckRequestParams(CreateEmployeeCommand request) + { + if (string.IsNullOrEmpty(request.Name) || string.IsNullOrWhiteSpace(request.Name)) + { + throw new ArgumentException("The 'name' field can not be null."); + } + + if (string.IsNullOrEmpty(request.Surname) || string.IsNullOrWhiteSpace(request.Surname)) + { + throw new ArgumentException("The 'surName' field can not be null."); + } + + if (string.IsNullOrEmpty(request.Mail) || string.IsNullOrWhiteSpace(request.Mail)) + { + throw new ArgumentException("The 'mail' field can not be null."); + } + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetAllEmployees/GetAllEmployeesHandler.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetAllEmployees/GetAllEmployeesHandler.cs new file mode 100644 index 00000000..8e655590 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetAllEmployees/GetAllEmployeesHandler.cs @@ -0,0 +1,22 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Application.Ports.Projectors; +using Devon4Net.Infrastructure.Common; +using MediatR; + +namespace Devon4Net.Application.Features.Queries.GetAllEmployees; + +public class GetAllEmployeesHandler : IRequestHandler> +{ + private readonly IEmployeeProjector _employeeProjector; + + public GetAllEmployeesHandler(IEmployeeProjector employeeProjector) + { + _employeeProjector = employeeProjector; + } + + public Task> Handle(GetAllEmployeesQuery request, CancellationToken cancellationToken) + { + Devon4NetLogger.Debug("Started GetAllEmployeesHandler"); + return _employeeProjector.GetEmployees(); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetAllEmployees/GetAllEmployeesQuery.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetAllEmployees/GetAllEmployeesQuery.cs new file mode 100644 index 00000000..2d29ecb8 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetAllEmployees/GetAllEmployeesQuery.cs @@ -0,0 +1,6 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Infrastructure.MediatR.Query; + +namespace Devon4Net.Application.Features.Queries.GetAllEmployees; + +public record GetAllEmployeesQuery : QueryBase>; \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetEmployeeById/GetEmployeeByIdHandler.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetEmployeeById/GetEmployeeByIdHandler.cs new file mode 100644 index 00000000..a431ef36 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetEmployeeById/GetEmployeeByIdHandler.cs @@ -0,0 +1,32 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Application.Ports.Projectors; +using Devon4Net.Infrastructure.Common; +using Devon4Net.Infrastructure.Common.Errors; +using Devon4Net.Infrastructure.Common.Models; +using MediatR; + +namespace Devon4Net.Application.Features.Queries.GetEmployeeById; + +public class GetEmployeeByIdHandler : IRequestHandler> +{ + private readonly IEmployeeProjector _employeeProjector; + + public GetEmployeeByIdHandler(IEmployeeProjector employeeProjector) + { + _employeeProjector = employeeProjector; + } + + public async Task> Handle(GetEmployeeByIdQuery request, CancellationToken cancellationToken) + { + Devon4NetLogger.Debug("Started GetEmployeeByIdHandler"); + var employee = await _employeeProjector.GetEmployeeById(request.Id); + + if (employee == null) + { + var error = new DevonError("123", "user not found", System.Net.HttpStatusCode.NotFound); + return DevonResult.Error(error); + } + + return employee; + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetEmployeeById/GetEmployeeByIdQuery.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetEmployeeById/GetEmployeeByIdQuery.cs new file mode 100644 index 00000000..4699ebd4 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Features/Queries/GetEmployeeById/GetEmployeeByIdQuery.cs @@ -0,0 +1,7 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Infrastructure.Common.Models; +using Devon4Net.Infrastructure.MediatR.Query; + +namespace Devon4Net.Application.Features.Queries.GetEmployeeById; + +public record GetEmployeeByIdQuery(Guid Id) : QueryBase>; \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/IEmployeeUoW.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/IEmployeeUoW.cs new file mode 100644 index 00000000..855b4732 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/IEmployeeUoW.cs @@ -0,0 +1,8 @@ +using Devon4Net.Infrastructure.UnitOfWork.UnitOfWork; + +namespace Devon4Net.Application.Ports; + +public interface IEmployeeUoW: IUnitOfWork +{ + +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/Projectors/IEmployeeProjector.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/Projectors/IEmployeeProjector.cs new file mode 100644 index 00000000..ec7bc391 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/Projectors/IEmployeeProjector.cs @@ -0,0 +1,10 @@ +using Devon4Net.Application.Dtos; + +namespace Devon4Net.Application.Ports.Projectors; + +public interface IEmployeeProjector +{ + public Task GetEmployeeById(Guid id); + + public Task> GetEmployees(); +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/Repositories/IEmployeeRepository.cs b/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/Repositories/IEmployeeRepository.cs new file mode 100644 index 00000000..4b28420b --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Application/Ports/Repositories/IEmployeeRepository.cs @@ -0,0 +1,41 @@ +using System.Linq.Expressions; +using Devon4Net.Domain.Entities; +using Devon4Net.Infrastructure.UnitOfWork.Repository; + +namespace Devon4Net.Application.Ports.Repositories; + +/// +/// EmployeeRepository interface +/// +public interface IEmployeeRepository : IRepository +{ + /// + /// GetEmployee + /// + /// + /// + Task> GetEmployee(Expression>? predicate = null); + + /// + /// GetEmployeeById + /// + /// + /// + Task GetEmployeeById(Guid id); + + /// + /// Create + /// + /// + /// + /// + /// + Task Create(string name, string surName, string mail); + + /// + /// DeleteEmployeeById + /// + /// + /// + Task DeleteEmployeeById(Guid id); +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.CleanArchitecture.sln b/source/Templates/CleanArchitecture/Devon4Net.CleanArchitecture.sln new file mode 100644 index 00000000..eef1c8b2 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.CleanArchitecture.sln @@ -0,0 +1,76 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34024.191 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C6718118-8896-49D4-948E-7B1BF8CD52CD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9BDC90DC-9BFB-4CD6-BD2B-023A756DBA0F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{91851FFC-D596-460D-8DB7-263ECDA77773}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "Devon4Net.CleanArchitecure.docker-compose", "Devon4Net.CleanArchitecure.docker-compose.dcproj", "{486A3585-A387-4BD4-BA66-8B991FB42F73}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Application", "Devon4Net.Application\Devon4Net.Application.csproj", "{9AB931AC-BFE6-47D7-8A6A-254D911E003A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Domain", "Devon4Net.Domain\Devon4Net.Domain.csproj", "{180F7F20-F88A-4A97-BCDD-874C4480BF8A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Infrastructure", "Devon4Net.Infrastructure\Devon4Net.Infrastructure.csproj", "{F6B4D676-3775-4998-AE14-350929AFD2E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.IntegrationTests", "Devon4Net.IntegrationTests\Devon4Net.IntegrationTests.csproj", "{4BAFF196-7474-482D-B600-35096C00533D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.UnitTests", "Devon4Net.UnitTests\Devon4Net.UnitTests.csproj", "{B4C3DF5E-4739-44B7-846E-2E61801CD7C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.WebAPI", "Devon4Net.WebAPI\Devon4Net.WebAPI.csproj", "{32FDA553-436A-4D4B-B13C-65AA6906B4E1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {486A3585-A387-4BD4-BA66-8B991FB42F73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {486A3585-A387-4BD4-BA66-8B991FB42F73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {486A3585-A387-4BD4-BA66-8B991FB42F73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {486A3585-A387-4BD4-BA66-8B991FB42F73}.Release|Any CPU.Build.0 = Release|Any CPU + {9AB931AC-BFE6-47D7-8A6A-254D911E003A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9AB931AC-BFE6-47D7-8A6A-254D911E003A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9AB931AC-BFE6-47D7-8A6A-254D911E003A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9AB931AC-BFE6-47D7-8A6A-254D911E003A}.Release|Any CPU.Build.0 = Release|Any CPU + {180F7F20-F88A-4A97-BCDD-874C4480BF8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {180F7F20-F88A-4A97-BCDD-874C4480BF8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {180F7F20-F88A-4A97-BCDD-874C4480BF8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {180F7F20-F88A-4A97-BCDD-874C4480BF8A}.Release|Any CPU.Build.0 = Release|Any CPU + {F6B4D676-3775-4998-AE14-350929AFD2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F6B4D676-3775-4998-AE14-350929AFD2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F6B4D676-3775-4998-AE14-350929AFD2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F6B4D676-3775-4998-AE14-350929AFD2E2}.Release|Any CPU.Build.0 = Release|Any CPU + {4BAFF196-7474-482D-B600-35096C00533D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BAFF196-7474-482D-B600-35096C00533D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BAFF196-7474-482D-B600-35096C00533D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BAFF196-7474-482D-B600-35096C00533D}.Release|Any CPU.Build.0 = Release|Any CPU + {B4C3DF5E-4739-44B7-846E-2E61801CD7C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B4C3DF5E-4739-44B7-846E-2E61801CD7C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B4C3DF5E-4739-44B7-846E-2E61801CD7C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B4C3DF5E-4739-44B7-846E-2E61801CD7C1}.Release|Any CPU.Build.0 = Release|Any CPU + {32FDA553-436A-4D4B-B13C-65AA6906B4E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32FDA553-436A-4D4B-B13C-65AA6906B4E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32FDA553-436A-4D4B-B13C-65AA6906B4E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32FDA553-436A-4D4B-B13C-65AA6906B4E1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {486A3585-A387-4BD4-BA66-8B991FB42F73} = {9BDC90DC-9BFB-4CD6-BD2B-023A756DBA0F} + {9AB931AC-BFE6-47D7-8A6A-254D911E003A} = {9BDC90DC-9BFB-4CD6-BD2B-023A756DBA0F} + {180F7F20-F88A-4A97-BCDD-874C4480BF8A} = {9BDC90DC-9BFB-4CD6-BD2B-023A756DBA0F} + {F6B4D676-3775-4998-AE14-350929AFD2E2} = {9BDC90DC-9BFB-4CD6-BD2B-023A756DBA0F} + {4BAFF196-7474-482D-B600-35096C00533D} = {91851FFC-D596-460D-8DB7-263ECDA77773} + {B4C3DF5E-4739-44B7-846E-2E61801CD7C1} = {91851FFC-D596-460D-8DB7-263ECDA77773} + {32FDA553-436A-4D4B-B13C-65AA6906B4E1} = {9BDC90DC-9BFB-4CD6-BD2B-023A756DBA0F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {09065A63-6429-42AD-8ED3-28EF422E543A} + EndGlobalSection +EndGlobal diff --git a/source/Templates/CleanArchitecture/Devon4Net.CleanArchitecure.docker-compose.dcproj b/source/Templates/CleanArchitecture/Devon4Net.CleanArchitecure.docker-compose.dcproj new file mode 100644 index 00000000..d09a521b --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.CleanArchitecure.docker-compose.dcproj @@ -0,0 +1,19 @@ + + + + 6f9dad10-d077-4ca1-8baa-a42d093f934e + 2.1 + Linux + False + LaunchBrowser + devon4net.clean-architecture.web-api + {Scheme}://localhost:{ServicePort}/swagger + + + + docker-compose.yml + + + + + diff --git a/source/Templates/CleanArchitecture/Devon4Net.Domain/Devon4Net.Domain.csproj b/source/Templates/CleanArchitecture/Devon4Net.Domain/Devon4Net.Domain.csproj new file mode 100644 index 00000000..8db37fb2 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Domain/Devon4Net.Domain.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + enable + + + + $(SuppressWarnings);CA2211;CA1822;CA1303 + + + diff --git a/source/Templates/CleanArchitecture/Devon4Net.Domain/Entities/Employee.cs b/source/Templates/CleanArchitecture/Devon4Net.Domain/Entities/Employee.cs new file mode 100644 index 00000000..3611fd25 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Domain/Entities/Employee.cs @@ -0,0 +1,38 @@ +using System.ComponentModel.DataAnnotations; + +namespace Devon4Net.Domain.Entities; + +/// +/// Entity class for Employee +/// +public class Employee +{ + public Employee(string name, string surname, string mail) + { + Id = Guid.NewGuid(); + Name = name; + Surname = surname; + Mail = mail; + } + + /// + /// Id + /// + [Key] + public Guid Id { get; private set; } + + /// + /// Name + /// + public string Name { get; private set; } + + /// + /// Surname + /// + public string Surname { get; private set; } + + /// + /// mail + /// + public string Mail { get; private set; } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/EmployeeUoW.cs b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/EmployeeUoW.cs new file mode 100644 index 00000000..be84d2a4 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/EmployeeUoW.cs @@ -0,0 +1,12 @@ +using Devon4Net.Application.Ports; +using Devon4Net.Infrastructure.Persistence; +using Devon4Net.Infrastructure.UnitOfWork.UnitOfWork; + +namespace Devon4Net.Infrastructure.Adapters; + +public class EmployeeUoW : UnitOfWork, IEmployeeUoW +{ + public EmployeeUoW(EmployeeContext context) : base(context) + { + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/Projectors/EmployeeProjector.cs b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/Projectors/EmployeeProjector.cs new file mode 100644 index 00000000..7f450717 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/Projectors/EmployeeProjector.cs @@ -0,0 +1,45 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Application.Exceptions; +using Devon4Net.Application.Ports.Projectors; +using Devon4Net.Domain.Entities; +using Devon4Net.Infrastructure.Persistence; +using Devon4Net.Infrastructure.UnitOfWork.Projector; + +namespace Devon4Net.Infrastructure.Adapters.Projectors; + +public class EmployeeProjector : Projector, IEmployeeProjector +{ + public EmployeeProjector(EmployeeContext context) : base(context) + { + } + + public async Task GetEmployeeById(Guid id) + { + var query = (IQueryable employeeQuery) => employeeQuery + .Select(employee => new EmployeeDto + { + Id = employee.Id, + Name = employee.Name, + Surname = employee.Surname, + Mail = employee.Mail + }) + .Where(employee => employee.Id == id); + + var employeeDtos = await GetProjection(query); + + return employeeDtos.FirstOrDefault(); + } + + public Task> GetEmployees() + { + var query = (IQueryable employeeQuery) => employeeQuery.Select(employee => new EmployeeDto + { + Id = employee.Id, + Name = employee.Name, + Surname = employee.Surname, + Mail = employee.Mail + }); + + return GetProjection(query); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/Repositories/EmployeeRepository.cs b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/Repositories/EmployeeRepository.cs new file mode 100644 index 00000000..de75c283 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Adapters/Repositories/EmployeeRepository.cs @@ -0,0 +1,72 @@ +using System.Linq.Expressions; +using Devon4Net.Application.Ports.Repositories; +using Devon4Net.Domain.Entities; +using Devon4Net.Infrastructure.Common; +using Devon4Net.Infrastructure.Persistence; +using Devon4Net.Infrastructure.UnitOfWork.Repository; + +namespace Devon4Net.Infrastructure.Adapters.Repositories; + +public class EmployeeRepository : Repository, IEmployeeRepository +{ + /// + /// Constructor + /// + /// + public EmployeeRepository(EmployeeContext context) : base(context) + { + } + + /// + /// Get Employee method + /// + /// + /// + public Task> GetEmployee(Expression> predicate = null) + { + Devon4NetLogger.Debug("GetEmployee method from EmployeeRepository EmployeeService"); + return Get(predicate); + } + + /// + /// Gets the Employee by id + /// + /// + /// + public Task GetEmployeeById(Guid id) + { + Devon4NetLogger.Debug($"GetEmployeeById method from repository EmployeeService with value : {id}"); + return GetFirstOrDefault(t => t.Id == id); + } + + /// + /// Creates the Employee + /// + /// + /// + /// + /// + public Task Create(string name, string surName, string mail) + { + Devon4NetLogger.Debug($"SetEmployee method from repository EmployeeService with value : {name}"); + return Create(new Employee(name, surName, mail)); + } + + /// + /// Deletes the Employee by id + /// + /// + /// + public async Task DeleteEmployeeById(Guid id) + { + Devon4NetLogger.Debug($"DeleteEmployeeById method from repository EmployeeService with value : {id}"); + var deleted = await Delete(t => t.Id == id).ConfigureAwait(false); + + if (deleted) + { + return id; + } + + throw new ArgumentException($"The Employee entity {id} has not been deleted."); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/AssemblyReference.cs b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/AssemblyReference.cs new file mode 100644 index 00000000..6b89acf9 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/AssemblyReference.cs @@ -0,0 +1,3 @@ +namespace Devon4Net.Infrastructure; + +public sealed record AssemblyReference; \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Devon4Net.Infrastructure.csproj b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Devon4Net.Infrastructure.csproj new file mode 100644 index 00000000..b9dcbf2b --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Devon4Net.Infrastructure.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + + + + + + + + diff --git a/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Persistence/EmployeeContext.cs b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Persistence/EmployeeContext.cs new file mode 100644 index 00000000..fdb5c21e --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Persistence/EmployeeContext.cs @@ -0,0 +1,17 @@ +using Devon4Net.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Devon4Net.Infrastructure.Persistence; + +/// +/// Employee database context definition +/// +public class EmployeeContext(DbContextOptions options) : DbContext(options) +{ + private const string Schema = "employees"; + + public DbSet Employee => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder + .ApplyConfigurationsFromAssembly(typeof(EmployeeContext).Assembly).HasDefaultSchema(Schema); +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Persistence/EntityConfiguration/EmployeeConfiguration.cs b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Persistence/EntityConfiguration/EmployeeConfiguration.cs new file mode 100644 index 00000000..c2a550cd --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.Infrastructure/Persistence/EntityConfiguration/EmployeeConfiguration.cs @@ -0,0 +1,24 @@ +using Devon4Net.Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Devon4Net.Infrastructure.Persistence.EntityConfiguration; + +public class EmployeeEntityTypeConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(e => e.Id) + .IsRequired() + .HasMaxLength(255); + builder.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255); + builder.Property(e => e.Surname) + .IsRequired() + .HasMaxLength(255); + builder.Property(e => e.Mail) + .IsRequired() + .HasMaxLength(255); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.IntegrationTests/Devon4Net.IntegrationTests.csproj b/source/Templates/CleanArchitecture/Devon4Net.IntegrationTests/Devon4Net.IntegrationTests.csproj new file mode 100644 index 00000000..afec5507 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.IntegrationTests/Devon4Net.IntegrationTests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/source/Templates/CleanArchitecture/Devon4Net.IntegrationTests/UnitTest1.cs b/source/Templates/CleanArchitecture/Devon4Net.IntegrationTests/UnitTest1.cs new file mode 100644 index 00000000..08ce9d82 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.IntegrationTests/UnitTest1.cs @@ -0,0 +1,15 @@ +using Xunit; + +namespace Devon4Net.IntegrationTests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + //Arrange + //Act + //Assert + Assert.True(true); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.UnitTests/Devon4Net.UnitTests.csproj b/source/Templates/CleanArchitecture/Devon4Net.UnitTests/Devon4Net.UnitTests.csproj new file mode 100644 index 00000000..cd1f7cc0 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.UnitTests/Devon4Net.UnitTests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + enable + + false + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/RootCA.crt b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/RootCA.crt new file mode 100644 index 00000000..513d0680 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/RootCA.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIUFh7tIM1nq2XzSM4jy6Fu4yBTrX0wDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCRVMxETAPBgNVBAgMCFZhbGVuY2lhMREwDwYDVQQHDAhW +YWxlbmNpYTEVMBMGA1UECgwMQ2VydGlmaWNhdGVzMRgwFgYDVQQDDA9sb2NhbGhv +c3QubG9jYWwwHhcNMjAwOTAzMTEwNjA5WhcNMjQwOTAyMTEwNjA5WjBkMQswCQYD +VQQGEwJFUzERMA8GA1UECAwIVmFsZW5jaWExETAPBgNVBAcMCFZhbGVuY2lhMRUw +EwYDVQQKDAxDZXJ0aWZpY2F0ZXMxGDAWBgNVBAMMD2xvY2FsaG9zdC5sb2NhbDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdeJfXptbcHGA8xVjTOjKfR +727UtXomEqc+9KO1jpT9e/FPcrfu0Jafvsfh3yPTd9pSDIqazqQxnX1Nh3EOiY8p +jUqq5ADUCbBypWCZlj0m/lME32SGCSnhVrxScDtYylGyBH1XmJuyb/umiEEh3uhk +98KuxdFj5IoVUQ61fR7AB1wpqRuyoKNjfrDaKtjtEyENZRRJ1mx8rZpuRgoOKkCF +1z/RzJuzimLB1+ZOErK7gMA67B3dS8SLa8W414vmB10ffB4RD9csHnQAapqDCIiX ++YQHTRU+YFkm6J+s8WX36F2FdOehIWnrSuV5VprJPkTBglyxigMWrdi0OtJ+kKMC +AwEAAaNTMFEwHQYDVR0OBBYEFDMeRVNIZlI9cWJio1knxEgEgwcdMB8GA1UdIwQY +MBaAFDMeRVNIZlI9cWJio1knxEgEgwcdMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggEBAKLFvRWe/630uD5bbwDu8aDTz4soDxgmZcMyi6/So2dF+XrD +D/Yq84bNpZhuUYelnDsxq6XZZMdQDztGZcX1G5kPabkW6kC2mb+KoPs2Be9ZJpu8 +aTHeOGKkgsWa05lAVQ5xSkDUpLGI8jSF3DLnRo0hKgKTe38iqErSMnOrPMR5drXw +rT35eOszU7PbhCcFWucGFH5uoFYfv709w8VeIB2dyq4po2FObGo//Zsy/n7xl+Oe +Fo8w0D/X2YsfSojqaSFLZl9t/3frJdiF1pYt6z6v911IvR086z7q0s0kmW8dxkVy +KCjhocdtf4iik3V8WENZPFtIReTIEn0eGEr/n7s= +-----END CERTIFICATE----- diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/RootCA.pem b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/RootCA.pem new file mode 100644 index 00000000..513d0680 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/RootCA.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqTCCApGgAwIBAgIUFh7tIM1nq2XzSM4jy6Fu4yBTrX0wDQYJKoZIhvcNAQEL +BQAwZDELMAkGA1UEBhMCRVMxETAPBgNVBAgMCFZhbGVuY2lhMREwDwYDVQQHDAhW +YWxlbmNpYTEVMBMGA1UECgwMQ2VydGlmaWNhdGVzMRgwFgYDVQQDDA9sb2NhbGhv +c3QubG9jYWwwHhcNMjAwOTAzMTEwNjA5WhcNMjQwOTAyMTEwNjA5WjBkMQswCQYD +VQQGEwJFUzERMA8GA1UECAwIVmFsZW5jaWExETAPBgNVBAcMCFZhbGVuY2lhMRUw +EwYDVQQKDAxDZXJ0aWZpY2F0ZXMxGDAWBgNVBAMMD2xvY2FsaG9zdC5sb2NhbDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKdeJfXptbcHGA8xVjTOjKfR +727UtXomEqc+9KO1jpT9e/FPcrfu0Jafvsfh3yPTd9pSDIqazqQxnX1Nh3EOiY8p +jUqq5ADUCbBypWCZlj0m/lME32SGCSnhVrxScDtYylGyBH1XmJuyb/umiEEh3uhk +98KuxdFj5IoVUQ61fR7AB1wpqRuyoKNjfrDaKtjtEyENZRRJ1mx8rZpuRgoOKkCF +1z/RzJuzimLB1+ZOErK7gMA67B3dS8SLa8W414vmB10ffB4RD9csHnQAapqDCIiX ++YQHTRU+YFkm6J+s8WX36F2FdOehIWnrSuV5VprJPkTBglyxigMWrdi0OtJ+kKMC +AwEAAaNTMFEwHQYDVR0OBBYEFDMeRVNIZlI9cWJio1knxEgEgwcdMB8GA1UdIwQY +MBaAFDMeRVNIZlI9cWJio1knxEgEgwcdMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggEBAKLFvRWe/630uD5bbwDu8aDTz4soDxgmZcMyi6/So2dF+XrD +D/Yq84bNpZhuUYelnDsxq6XZZMdQDztGZcX1G5kPabkW6kC2mb+KoPs2Be9ZJpu8 +aTHeOGKkgsWa05lAVQ5xSkDUpLGI8jSF3DLnRo0hKgKTe38iqErSMnOrPMR5drXw +rT35eOszU7PbhCcFWucGFH5uoFYfv709w8VeIB2dyq4po2FObGo//Zsy/n7xl+Oe +Fo8w0D/X2YsfSojqaSFLZl9t/3frJdiF1pYt6z6v911IvR086z7q0s0kmW8dxkVy +KCjhocdtf4iik3V8WENZPFtIReTIEn0eGEr/n7s= +-----END CERTIFICATE----- diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/localhost.crt b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/localhost.crt new file mode 100644 index 00000000..9a08a599 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/localhost.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIID1DCCArygAwIBAgIJAMt7yDoYrBXfMA0GCSqGSIb3DQEBCwUAMGQxCzAJBgNV +BAYTAkVTMREwDwYDVQQIDAhWYWxlbmNpYTERMA8GA1UEBwwIVmFsZW5jaWExFTAT +BgNVBAoMDENlcnRpZmljYXRlczEYMBYGA1UEAwwPbG9jYWxob3N0LmxvY2FsMB4X +DTE5MTAxMDExMDUwM1oXDTIyMDczMDExMDUwM1owZDELMAkGA1UEBhMCRVMxETAP +BgNVBAgMCFZhbGVuY2lhMREwDwYDVQQHDAhWYWxlbmNpYTEVMBMGA1UECgwMQ2Vy +dGlmaWNhdGVzMRgwFgYDVQQDDA9sb2NhbGhvc3QubG9jYWwwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDAsevHKZ4/YgRuXWRbkJ6My6+3S37Zb7ajxVEa +KGUQkRyGElRJk8AbDJVYjBxdE30vM9OFdnuQk+kywWlAkC0yXZosEfPloCcGX3Oe +NAcs/4XVnYSlnfSaVUgG4LJdOtyNJgrWVC+yl1qyHW+M4uqjx1BVw3TkTBgozvOr ++eiIvyrDQmSB5jR/A+BejquSbLDMUv35n6q0PrNy6ZdO+AeB7X5Jm1a/PVDe017o +WcG8PBs7wAbd/GVnZo842HnTh0cPurtrTMwsKqpJWcV+5pBnnvJRkHc7jzg8fk4n +gAnmOWJYl1DySFJE9XGo3cB8xhYuuTB5bvyTXDT5DzTaEpNpAgMBAAGjgYgwgYUw +HwYDVR0jBBgwFoAUQvhY+l9vU1lwwKk2gO+nJ4plMZ0wCQYDVR0TBAIwADALBgNV +HQ8EBAMCBPAwSgYDVR0RBEMwQYIJbG9jYWxob3N0gg9sb2NhbGhvc3QubG9jYWyC +CTEyNy4wLjAuMYILZmFrZTEubG9jYWyCC2Zha2UyLmxvY2FsMA0GCSqGSIb3DQEB +CwUAA4IBAQC3LrullXQJSlkxLooPNd9LxWwjHiak3hCyvpQ3r09DCSd9JJSE+E5o +3N2TPOolmCDHZDAbUtUdIeHH3AUDRHKbEz2DACQg4fyb6ByL3Qe8L5NHGo3ELFfc +dmbXxM53J9fQ66Fm8kzWXoZlHdSboCuf8OlxdtRQZzJZA6pcwu/qx6DGc3/1K2iJ +PLajHC36p/fudSl41DJIZBdNYsd1NmlW48im+fOvcBCXVGzST2J3954u7CrtTffG +rYA9X/EQZ7n9y7tymuhYkMCDCKJNSkX1fu0tkoFskuxRJK3lKcGJ59o1VsNHR3Eb +1+n85nSqQrPQaa35DOEq6z6IWg3M/tby +-----END CERTIFICATE----- diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/localhost.pfx b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/localhost.pfx new file mode 100644 index 00000000..b63d0382 Binary files /dev/null and b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Certificates/localhost.pfx differ diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Configuration.cs b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Configuration.cs new file mode 100644 index 00000000..0a3237fd --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Configuration.cs @@ -0,0 +1,59 @@ +using Devon4Net.Infrastructure.Common.Helpers; +using Devon4Net.Infrastructure.MediatR.Behaviors; +using FluentValidation; +using MediatR; +using System.Reflection; +using Devon4Net.Infrastructure.Persistence; +using Devon4Net.Infrastructure.UnitOfWork.Common; +using Devon4Net.Infrastructure.UnitOfWork.Enums; + +namespace Devon4Net.Presentation; + +public static class Configuration +{ + public static void SetupCustomDependencyInjection(this IServiceCollection services, IConfiguration configuration) + { + SetupDatabase(services, configuration); + SetupMediatRHandlers(services); + SetUpAutoRegisterClasses(services); + } + + private static void SetUpAutoRegisterClasses(IServiceCollection services) + { + List assemblyNamespaceToScan = new() + { + typeof(Application.AssemblyReference).Assembly, + typeof(Infrastructure.AssemblyReference).Assembly + }; + + var suffixNamesToRegister = new List + { + "Projector", + "Repository", + "Service", + "QueryBuilder" + }; + + services.AutoRegisterClasses(assemblyNamespaceToScan, suffixNamesToRegister, ServiceLifetime.Transient); + } + + private static void SetupMediatRHandlers(IServiceCollection services) + { + var assembly = typeof(Application.AssemblyReference).Assembly; + + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(assembly)); + services.AddValidatorsFromAssembly(assembly); + + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); + } + + /// + /// Setup here your database connections. + /// + /// + /// + private static void SetupDatabase(IServiceCollection services, IConfiguration configuration) + { + services.SetupDatabase(configuration, "Employee", DatabaseType.InMemory).ConfigureAwait(false); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Controllers/EmployeeController.cs b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Controllers/EmployeeController.cs new file mode 100644 index 00000000..faf921c3 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Controllers/EmployeeController.cs @@ -0,0 +1,44 @@ +using Devon4Net.Application.Dtos; +using Devon4Net.Application.Features.Command.CreateEmployee; +using Devon4Net.Application.Features.Queries.GetAllEmployees; +using Devon4Net.Application.Features.Queries.GetEmployeeById; +using Devon4Net.Infrastructure.Common.Application.Attributes; +using Devon4Net.Infrastructure.MediatR.Handler; +using Microsoft.AspNetCore.Mvc; + +namespace Devon4Net.WebAPI.Controllers; + +[ApiController] +[Route("/employees")] +[ServiceFilter(typeof(ExceptionHandlingFilterAttribute))] +public class EmployeeController : ControllerBase +{ + private readonly IMediatRHandler _mediator; + + public EmployeeController(IMediatRHandler mediator) + { + _mediator = mediator; + } + + [HttpPost] + public Task CreateEmployee([FromBody] EmployeeDto employeeDto) + { + var command = new CreateEmployeeCommand(employeeDto.Name, employeeDto.Surname, employeeDto.Mail); + return _mediator.CommandAsync(command); + } + + [HttpGet] + public Task> GetAllEmployees() + { + var query = new GetAllEmployeesQuery(); + return _mediator.QueryAsync(query); + } + + [HttpGet("{employeeId}")] + public async Task GetEmployeeById([FromRoute] Guid employeeId) + { + var query = new GetEmployeeByIdQuery(employeeId); + var result = await _mediator.QueryAsync(query); + return result.BuildResult(); + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Devon4Net.WebAPI.csproj b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Devon4Net.WebAPI.csproj new file mode 100644 index 00000000..dd07587d --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Devon4Net.WebAPI.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + ..\.. + 83711c0b-b386-4d97-a3da-a96a5d6863cf + + + + + + + + + + + + + + + Always + + + + diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Dockerfile b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Dockerfile new file mode 100644 index 00000000..22859815 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Dockerfile @@ -0,0 +1,57 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +ARG USERNAME=${USER_NAME} +ARG USER_UID=1000 +ARG USER_GID=$USER_UID + +# Create the user +RUN groupadd --gid "$USER_GID" "$USERNAME" \ + && useradd --uid "$USER_UID" --gid "$USER_GID" -m "$USERNAME" \ + # [Optional] Add sudo support. Omit if you don't need to install software after connecting. + && apt-get update \ + && apt-get install --no-install-recommends -y sudo \ + && echo "$USERNAME" ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/"$USERNAME" \ + && chmod 0440 /etc/sudoers.d/"$USERNAME" \ + && apt-get clean +# ******************************************************** +# * Anything else you want to do like clean up goes here * +# ******************************************************** + +# [Optional] Set the default user. Omit if you want to keep the default as root. +USER $USERNAME +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG USERNAME +USER $USERNAME +ARG BUILD_CONFIGURATION=Release + +WORKDIR /src +COPY . . +# COPY ["Templates/CleanArchitecture/Devon4Net.WebAPI/Devon4Net.WebAPI.csproj", "Templates/CleanArchitecture/Devon4Net.WebAPI/"] +# COPY ["Templates/CleanArchitecture/Devon4Net.Application/Devon4Net.Application.csproj", "Templates/CleanArchitecture/Devon4Net.Application/"] +# COPY ["Templates/CleanArchitecture/Devon4Net.Infrastructure/Devon4Net.Infrastructure.csproj", "Templates/CleanArchitecture/Devon4Net.Infrastructure/"] +# COPY ["Templates/CleanArchitecture/Devon4Net.Domain/Devon4Net.Domain.csproj", "Templates/CleanArchitecture/Devon4Net.Domain/"] +RUN dotnet restore "source/Templates/CleanArchitecture/Devon4Net.WebAPI/Devon4Net.WebAPI.csproj" + +# COPY . . +# COPY ./Templates/CleanArchitecture/Devon4Net.WebAPI/ /Templates/CleanArchitecture/Devon4Net.WebAPI/ +# COPY ./Templates/CleanArchitecture/Devon4Net.Application/ /Templates/CleanArchitecture/Devon4Net.Application/ +# COPY ./Templates/CleanArchitecture/Devon4Net.Infrastructure/ /Templates/CleanArchitecture/Devon4Net.Infrastructure/ +# COPY ./Templates/CleanArchitecture/Devon4Net.Domain/ /Templates/CleanArchitecture/Devon4Net.Domain/ +# COPY ["Modules", "Modules"] + +WORKDIR "/src/source/Templates/CleanArchitecture/Devon4Net.WebAPI" +RUN dotnet build "Devon4Net.WebAPI.csproj" -c ${BUILD_CONFIGURATION} --no-restore -o /app/build + +FROM build AS publish +USER $USERNAME +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Devon4Net.WebAPI.csproj" -c ${BUILD_CONFIGURATION} -o /app/publish /p:UseAppHost=false + +FROM base AS final +USER $USERNAME +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Devon4Net.WebAPI.dll"] diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Program.cs b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Program.cs new file mode 100644 index 00000000..f02d00ac --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Program.cs @@ -0,0 +1,42 @@ +using Devon4Net.Infrastructure.Common.Application.ApplicationTypes.API; +using Devon4Net.Infrastructure.Common.Application.Middleware; +using Devon4Net.Infrastructure.Cors; +using Devon4Net.Infrastructure.Logger; +using Devon4Net.Infrastructure.MediatR; +using Devon4Net.Infrastructure.UnitOfWork; +using Devon4Net.Presentation; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container +builder.WebHost.InitializeDevonfwApi(builder.Host); +builder.Services.AddControllers(); + +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.SetupDevonfw(builder.Configuration); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.SetupLog(builder.Configuration); +builder.Services.SetupMiddleware(builder.Configuration); +builder.Services.SetupUnitOfWork(); +builder.Services.SetupMediatR(builder.Configuration); +builder.Services.SetupCustomDependencyInjection(builder.Configuration); +builder.Services.SetupCors(builder.Configuration); + +var app = builder.Build(); +app.SetupCors(); +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.SetupMiddleware(builder.Services); + +app.UseHttpsRedirection(); +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Properties/launchSettings.json b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Properties/launchSettings.json new file mode 100644 index 00000000..d1c57178 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/Properties/launchSettings.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39688", + "sslPort": 44302 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:8085" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:8085" + }, + "App1.Presentation": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7235;http://localhost:5263", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/appsettings.Development.json b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/appsettings.Development.json new file mode 100644 index 00000000..1684214b --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/appsettings.Development.json @@ -0,0 +1,138 @@ +{ + "devonfw": { + "UseDetailedErrorsKey": true, + "UseIIS": false, + "UseSwagger": true, + "UseXsrf": true, + "UseModelStateValidation": true, + "Environment": "Development", + "ForceUseHttpsRedirection": false, + "Kestrel": { + "UseHttps": false, + "HttpProtocol": "Http1", //Http1, Http2, Http1AndHttp2, none + "ApplicationPort": 8085, + "SslProtocol": "none", //Tls12, Tls13, none. For Https2 Tls12 is needed + "ExtraSettings": { + "KeepAliveTimeout": 120, //in seconds + "MaxConcurrentConnections": 100, + "MaxConcurrentUpgradedConnections": 100, + "MaxRequestBodySize": 28.6, //In MB. The default maximum request body size is 30,000,000 bytes, which is approximately 28.6 MB + "Http2MaxStreamsPerConnection": 100, + "Http2InitialConnectionWindowSize": 131072, // From 65,535 and less than 2^31 (2,147,483,648) + "Http2InitialStreamWindowSize": 98304, // From 65,535 and less than 2^31 (2,147,483,648) + "AllowSynchronousIO": true + } + } + }, + + "Certificates": { + "ServerCertificate": { + "Certificate": "", + "CertificatePassword": "" + }, + "ClientCertificate": { + "RequireClientCertificate": false, + "CheckCertificateRevocation": false, + "ClientCertificates": { + "Whitelist": [] + } + } + }, + + "ConnectionStrings": { + "Employee": "Employee" + }, + + "JWT": { + "Audience": "devon4Net", + "Issuer": "devon4Net", + "TokenExpirationTime": 60, + "ValidateIssuer": true, + "ValidateIssuerSigningKey": true, + "ValidateLifetime": true, + "RequireSignedTokens": true, + "RequireExpirationTime": true, + "RequireAudience": true, + "ClockSkew": 5, + "Security": { + "SecretKeyEncryptionAlgorithm": "", + //HmacSha256, HmacSha384, HmacSha512, HmacSha256Signature, HmacSha384Signature, HmacSha512Signature + "SecretKey": "", + "Certificate": "", + "CertificatePassword": "", + "CertificateEncryptionAlgorithm": "", + "RefreshTokenEncryptionAlgorithm": "" + //HmacSha256, HmacSha384, HmacSha512, HmacSha256Signature, HmacSha384Signature, HmacSha512Signature + } + }, + + "KillSwitch": { + "UseKillSwitch": false, + "EnableRequests": false, + "HttpStatusCode": 403 + }, + + "Serilog": { + "UseLogFile": true, + "UseSQLiteDb": true, + "UseGraylog": false, + "UseAOPTrace": false, + "MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Error", + "Microsoft.EntityFrameworkCore": "Warning" + } + }, + "SqliteDatabase": "logs/log.db", + "LogFile": "logs/{0}_devonfw.log", + "SeqLogServerHost": "", + "GrayLog": { + "GrayLogHost": "127.0.0.1", + "GrayLogPort": "12201", + "GrayLogProtocol": "UDP", + "MaxUdpMessageSize": 8192 + } + }, + + "Cors": //[], //Empty array allows all origins with the policy "CorsPolicy" + [ //Comma separated Origins, Headers, ExposedHeaders and Methods + { + "CorsPolicy": "CorsPolicy", + "Origins": "http://localhost:4200,https://localhost:4200,http://localhost,https://localhost;http://localhost:8085,https://localhost:8085", + "Headers": "accept,content-type,origin,x-custom-header,authorization,cAppVersion,cStructure,cUser,cPostId", + "ExposedHeaders": "x-custom-header,custom-authorization", + "Methods": "GET,POST,HEAD,PUT,DELETE", + "AllowCredentials": true + } + ], + + "Swagger": { + "Version": "v1", + "Title": "devon4net API", + "Description": "devon4net API Contract", + "Terms": "https://www.devonfw.com/terms-of-use/", + "Contact": { + "Name": "devonfw", + "Email": "sample@mail.com", + "Url": "https://www.devonfw.com" + }, + "License": { + "Name": "devonfw - Terms of Use", + "Url": "https://www.devonfw.com/terms-of-use/" + }, + "Endpoint": { + "Name": "V1 Docs", + "Url": "/swagger/v1/swagger.json", + "UrlUi": "swagger", + "RouteTemplate": "swagger/v1/{documentName}/swagger.json" + } + }, + + "MediatR": { + "EnableMediatR": true, + "Backup": { + "UseLocalBackup": false, + } + } +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/appsettings.json b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/appsettings.json new file mode 100644 index 00000000..84c0f344 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/appsettings.json @@ -0,0 +1,3 @@ +{ + "Environment": "Development" +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/Devon4Net.WebAPI/libman.json b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/libman.json new file mode 100644 index 00000000..ceee2710 --- /dev/null +++ b/source/Templates/CleanArchitecture/Devon4Net.WebAPI/libman.json @@ -0,0 +1,5 @@ +{ + "version": "1.0", + "defaultProvider": "cdnjs", + "libraries": [] +} \ No newline at end of file diff --git a/source/Templates/CleanArchitecture/docker-compose.override.yml b/source/Templates/CleanArchitecture/docker-compose.override.yml new file mode 100644 index 00000000..4d6394e9 --- /dev/null +++ b/source/Templates/CleanArchitecture/docker-compose.override.yml @@ -0,0 +1,12 @@ +version: '3.4' + +services: + devon4net.clean-architecture.web-api: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_Kestrel__Certificates__Default__Password=${PASSWORD_ENV_SEEDED} + - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/aspnetapp.pfx + volumes: + - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro + - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro + - ${USERPROFILE}\.aspnet\https:/https/ diff --git a/source/Templates/CleanArchitecture/docker-compose.yml b/source/Templates/CleanArchitecture/docker-compose.yml new file mode 100644 index 00000000..ef05112f --- /dev/null +++ b/source/Templates/CleanArchitecture/docker-compose.yml @@ -0,0 +1,20 @@ +version: '3.4' +services: + devon4net.clean-architecture.web-api: + build: + context: ../../../. + dockerfile: source/Templates/CleanArchitecture/Devon4Net.WebAPI/Dockerfile + args: + BUILD_CONFIGURATION: Release + USERNAME: ${USER_NAME} + ports: + - "80:8085" + - "443:9000" + environment: + - ASPNETCORE_URLS=https://+:443;http://+:80 + networks: + - mydevnetwork + +networks: + mydevnetwork: + driver: bridge \ No newline at end of file diff --git a/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Business/FileManagement/Services/FileService.cs b/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Business/FileManagement/Services/FileService.cs index 0b988121..435acb31 100644 --- a/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Business/FileManagement/Services/FileService.cs +++ b/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Business/FileManagement/Services/FileService.cs @@ -1,21 +1,17 @@  -using Devon4Net.Application.Kafka.Consumer.Domain.Database; using Devon4Net.Application.Kafka.Consumer.Domain.RepositoryInterfaces; using Devon4Net.Application.Kafka.Consumer.Business.FileManagement.Dto; using Devon4Net.Application.Kafka.Consumer.Business.FileManagement.Converters; -using Devon4Net.Infrastructure.UnitOfWork.Service; -using Devon4Net.Infrastructure.UnitOfWork.UnitOfWork; namespace Devon4Net.Application.Kafka.Consumer.Business.FileManagement.Services { - - public class FileService: Service, IFileService + public class FileService: IFileService { private readonly IFileRepository _fileRepository; - public FileService(IUnitOfWork uoW) : base(uoW) + public FileService(IFileRepository fileRepository) { - _fileRepository = uoW.Repository(); + _fileRepository = fileRepository; } public DataPieceDto CreateFile(DataPieceDto dataPiece) diff --git a/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Program.cs b/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Program.cs index 057f5180..536727db 100644 --- a/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Program.cs +++ b/source/Templates/Kafka/Devon4Net.Application.Kafka.Consumer/Program.cs @@ -23,7 +23,7 @@ //UoW CONFIGURATION builder.Services.SetupDependencyInjection(builder.Configuration); -builder.Services.SetupUnitOfWork(typeof(DIConfiguration)); +builder.Services.SetupUnitOfWork(); //KAFKA CONFIGURATION builder.Services.SetupKafka(builder.Configuration); diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Exceptions/EmployeeNotFoundException.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Exceptions/EmployeeNotFoundException.cs index b931fd1a..d411ef43 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Exceptions/EmployeeNotFoundException.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Exceptions/EmployeeNotFoundException.cs @@ -1,5 +1,5 @@ using Devon4Net.Infrastructure.Common.Exceptions; -using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Application.WebAPI.Business.EmployeeManagement.Exceptions { @@ -7,17 +7,17 @@ namespace Devon4Net.Application.WebAPI.Business.EmployeeManagement.Exceptions /// Custom exception EmployeeNotFoundException /// [Serializable] - public class EmployeeNotFoundException : Exception, IWebApiException + public class EmployeeNotFoundException : WebApiException { /// - /// The forced http status code to be fired on the exception manager + /// Gets the forced http status code to be fired on the exception manager. /// - public int StatusCode => StatusCodes.Status404NotFound; + public override int StatusCode => StatusCodes.Status404NotFound; /// - /// Show the message on the response? + /// Gets a value indicating whether show the message on the response?. /// - public bool ShowMessage => true; + public override bool ShowMessage => true; /// /// Initializes a new instance of the class. @@ -43,5 +43,10 @@ public EmployeeNotFoundException(string message) public EmployeeNotFoundException(string message, Exception innerException) : base(message, innerException) { } + + protected EmployeeNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } } } diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Service/EmployeeService.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Service/EmployeeService.cs index 5b117582..a867273e 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Service/EmployeeService.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/EmployeeManagement/Service/EmployeeService.cs @@ -3,28 +3,25 @@ using Devon4Net.Application.WebAPI.Business.EmployeeManagement.Converters; using Devon4Net.Application.WebAPI.Business.EmployeeManagement.Dto; using Devon4Net.Application.WebAPI.Business.EmployeeManagement.Exceptions; -using Devon4Net.Application.WebAPI.Domain.Database; using Devon4Net.Application.WebAPI.Domain.Entities; using Devon4Net.Application.WebAPI.Domain.RepositoryInterfaces; -using Devon4Net.Infrastructure.UnitOfWork.UnitOfWork; -using Devon4Net.Infrastructure.UnitOfWork.Service; namespace Devon4Net.Application.WebAPI.Business.EmployeeManagement.Service { /// /// Employee service implementation /// - public class EmployeeService: Service, IEmployeeService + public class EmployeeService: IEmployeeService { private readonly IEmployeeRepository _employeeRepository; /// /// Constructor /// - /// - public EmployeeService(IUnitOfWork uoW) : base(uoW) + /// + public EmployeeService(IEmployeeRepository employeeRepository) { - _employeeRepository = uoW.Repository(); + _employeeRepository = employeeRepository; } /// diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Commands/CreateTodoCommand.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Commands/CreateTodoCommand.cs index ba601294..b2cb2f59 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Commands/CreateTodoCommand.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Commands/CreateTodoCommand.cs @@ -6,7 +6,7 @@ namespace Devon4Net.Application.WebAPI.Business.MediatRManagement.Commands /// /// THe command to create a TO-DO /// - public class CreateTodoCommand : CommandBase + public record CreateTodoCommand : CommandBase { /// /// Description diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Exceptions/TodoNotFoundException.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Exceptions/TodoNotFoundException.cs index 6b0287f5..4d9f5443 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Exceptions/TodoNotFoundException.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Exceptions/TodoNotFoundException.cs @@ -1,5 +1,6 @@ using Devon4Net.Infrastructure.Common.Exceptions; using Microsoft.AspNetCore.Http; +using System.Runtime.Serialization; namespace Devon4Net.Application.WebAPI.Business.MediatRManagement.Exceptions { @@ -7,17 +8,17 @@ namespace Devon4Net.Application.WebAPI.Business.MediatRManagement.Exceptions /// Custom exception TodoNotFoundException /// [Serializable] - public class TodoNotFoundException : Exception, IWebApiException + public class TodoNotFoundException : WebApiException { /// - /// The forced http status code to be fired on the exception manager + /// Gets the forced http status code to be fired on the exception manager. /// - public int StatusCode => StatusCodes.Status204NoContent; + public override int StatusCode => StatusCodes.Status500InternalServerError; /// - /// Show the message on the response? + /// Gets a value indicating whether show the message on the response?. /// - public bool ShowMessage => true; + public override bool ShowMessage => true; /// /// Initializes a new instance of the class. @@ -43,5 +44,15 @@ public TodoNotFoundException(string message) public TodoNotFoundException(string message, Exception innerException) : base(message, innerException) { } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + protected TodoNotFoundException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base() + { + } } } diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Queries/GetTodoQuery.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Queries/GetTodoQuery.cs index a2b30ca8..55c8b1d5 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Queries/GetTodoQuery.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/MediatRManagement/Queries/GetTodoQuery.cs @@ -5,7 +5,7 @@ namespace Devon4Net.Application.WebAPI.Business.MediatRManagement.Queries /// /// /// - public class GetTodoQuery : QueryBase + public record GetTodoQuery : QueryBase { diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/TodoManagement/Service/TodoService.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/TodoManagement/Service/TodoService.cs index d703ccd1..7aa14e94 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/TodoManagement/Service/TodoService.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Business/TodoManagement/Service/TodoService.cs @@ -2,28 +2,25 @@ using Devon4Net.Infrastructure.Common; using Devon4Net.Application.WebAPI.Business.TodoManagement.Converters; using Devon4Net.Application.WebAPI.Business.TodoManagement.Dto; -using Devon4Net.Application.WebAPI.Domain.Database; using Devon4Net.Application.WebAPI.Domain.Entities; using Devon4Net.Application.WebAPI.Domain.RepositoryInterfaces; -using Devon4Net.Infrastructure.UnitOfWork.Service; -using Devon4Net.Infrastructure.UnitOfWork.UnitOfWork; namespace Devon4Net.Application.WebAPI.Business.TodoManagement.Service { /// /// Service implementation /// - public class TodoService: Service, ITodoService + public class TodoService: ITodoService { private readonly ITodoRepository _todoRepository; /// /// Constructor /// - /// - public TodoService(IUnitOfWork uoW) : base(uoW) + /// + public TodoService(ITodoRepository todoRepository) { - _todoRepository = uoW.Repository(); + _todoRepository = todoRepository; } /// diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Configuration/DevonConfiguration.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Configuration/DevonConfiguration.cs index d7ed7b16..e68af62c 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Configuration/DevonConfiguration.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Configuration/DevonConfiguration.cs @@ -1,4 +1,4 @@ -using Devon4Net.Application.WebAPI.Configuration; +using System.Reflection; using Devon4Net.Application.WebAPI.Business.EmployeeManagement.Dto; using Devon4Net.Application.WebAPI.Business.EmployeeManagement.Validators; using Devon4Net.Application.WebAPI.Business.MediatRManagement.Commands; @@ -13,18 +13,14 @@ using Devon4Net.Infrastructure.FluentValidation; using Devon4Net.Infrastructure.JWT.Common; using Devon4Net.Infrastructure.MediatR.Options; -using Devon4Net.Infrastructure.MediatR.Samples.Handler; -using Devon4Net.Infrastructure.MediatR.Samples.Model; -using Devon4Net.Infrastructure.MediatR.Samples.Query; using Devon4Net.Infrastructure.RabbitMQ.Options; using Devon4Net.Infrastructure.RabbitMQ.Samples.Handllers; using FluentValidation; -using FluentValidation.AspNetCore; using MediatR; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System.Security.Claims; +using Devon4Net.Infrastructure.Common.Helpers; +using Devon4Net.Infrastructure.MediatR.Behaviors; using Devon4Net.Infrastructure.RabbitMQ; using Devon4Net.Infrastructure.UnitOfWork.Common; using Devon4Net.Infrastructure.UnitOfWork.Enums; @@ -48,6 +44,7 @@ public static class DevonConfiguration public static void SetupCustomDependencyInjection(this IServiceCollection services, IConfiguration configuration) { SetupDatabase(services, configuration); + SetUpAutoRegisterClasses(services); SetupJwtPolicies(services); SetupFluentValidators(services); @@ -66,6 +63,24 @@ public static void SetupCustomDependencyInjection(this IServiceCollection servic SetupMediatRHandlers(services); } } + + private static void SetUpAutoRegisterClasses(IServiceCollection services) + { + List assemblyNamespaceToScan = new() + { + Assembly.GetExecutingAssembly(), + }; + + var suffixNamesToRegister = new List + { + "Projector", + "Repository", + "Service", + "QueryBuilder" + }; + + services.AutoRegisterClasses(assemblyNamespaceToScan, suffixNamesToRegister, ServiceLifetime.Transient); + } private static void SetupRabbitHandlers(IServiceCollection services) { @@ -75,9 +90,12 @@ private static void SetupRabbitHandlers(IServiceCollection services) private static void SetupMediatRHandlers(IServiceCollection services) { - services.AddTransient(typeof(IRequestHandler), typeof(GetUserhandler)); - services.AddTransient(typeof(IRequestHandler), typeof(GetTodoHandler)); - services.AddTransient(typeof(IRequestHandler), typeof(CreateTodoHandler)); + var assembly = Assembly.GetExecutingAssembly(); + + services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(assembly)); + services.AddValidatorsFromAssembly(assembly); + + services.AddScoped(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); } private static void SetupFluentValidators(IServiceCollection services) diff --git a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Program.cs b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Program.cs index b79ce4c0..37590762 100644 --- a/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Program.cs +++ b/source/Templates/WebAPI/Devon4Net.Application.WebAPI/Program.cs @@ -26,7 +26,7 @@ builder.Services.SetupCircuitBreaker(builder.Configuration); builder.Services.SetupCors(builder.Configuration); builder.Services.SetupJwt(builder.Configuration); -builder.Services.SetupUnitOfWork(typeof(Program)); +builder.Services.SetupUnitOfWork(); builder.Services.SetupLiteDb(builder.Configuration); builder.Services.SetupRabbitMq(builder.Configuration); builder.Services.SetupMediatR(builder.Configuration); diff --git a/source/devon4net.sln b/source/devon4net.sln index 9d32e929..f2a34627 100644 --- a/source/devon4net.sln +++ b/source/devon4net.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.4.33027.239 @@ -96,6 +95,26 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Infrastructure.AW EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Infrastructure.AWS.Logger", "Modules\Devon4Net.Infrastructure.AWS.Logger\Devon4Net.Infrastructure.AWS.Logger.csproj", "{5895B5E1-96E0-492D-9E35-5F66247B74AD}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CleanArchitecture", "CleanArchitecture", "{4EC816D6-3F55-41AD-B805-955F251C03EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Application", "Templates\CleanArchitecture\Devon4Net.Application\Devon4Net.Application.csproj", "{16D8E11F-7734-4B69-92E8-F1CD960A2C38}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Domain", "Templates\CleanArchitecture\Devon4Net.Domain\Devon4Net.Domain.csproj", "{5C1E97EE-D116-4237-A86C-496E123F429D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.Infrastructure", "Templates\CleanArchitecture\Devon4Net.Infrastructure\Devon4Net.Infrastructure.csproj", "{A65EBB59-D598-46A0-B80C-0840D97E3A69}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.IntegrationTests", "Templates\CleanArchitecture\Devon4Net.IntegrationTests\Devon4Net.IntegrationTests.csproj", "{727B7E51-F913-4196-99E3-B9D01CDBA2E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.UnitTests", "Templates\CleanArchitecture\Devon4Net.UnitTests\Devon4Net.UnitTests.csproj", "{4C685698-3083-4BB0-BFF4-6DA152115D2C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B55F2379-2A06-4761-B1E7-DAD1D14A93BC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3A21B19A-CDDB-4849-87F2-EBC575EA889F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Devon4Net.WebAPI", "Templates\CleanArchitecture\Devon4Net.WebAPI\Devon4Net.WebAPI.csproj", "{D853C05D-76B5-4D9D-B419-437ADD7C23FD}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "Devon4Net.CleanArchitecure.docker-compose", "Templates\CleanArchitecture\Devon4Net.CleanArchitecure.docker-compose.dcproj", "{6F9DAD10-D077-4CA1-8BAA-A42D093F934E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -368,6 +387,62 @@ Global {5895B5E1-96E0-492D-9E35-5F66247B74AD}.Release|Any CPU.Build.0 = Release|Any CPU {5895B5E1-96E0-492D-9E35-5F66247B74AD}.Release|x64.ActiveCfg = Release|Any CPU {5895B5E1-96E0-492D-9E35-5F66247B74AD}.Release|x64.Build.0 = Release|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Debug|x64.ActiveCfg = Debug|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Debug|x64.Build.0 = Debug|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Release|Any CPU.Build.0 = Release|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Release|x64.ActiveCfg = Release|Any CPU + {16D8E11F-7734-4B69-92E8-F1CD960A2C38}.Release|x64.Build.0 = Release|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Debug|x64.ActiveCfg = Debug|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Debug|x64.Build.0 = Debug|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Release|Any CPU.Build.0 = Release|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Release|x64.ActiveCfg = Release|Any CPU + {5C1E97EE-D116-4237-A86C-496E123F429D}.Release|x64.Build.0 = Release|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Debug|x64.ActiveCfg = Debug|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Debug|x64.Build.0 = Debug|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Release|Any CPU.Build.0 = Release|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Release|x64.ActiveCfg = Release|Any CPU + {A65EBB59-D598-46A0-B80C-0840D97E3A69}.Release|x64.Build.0 = Release|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Debug|x64.ActiveCfg = Debug|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Debug|x64.Build.0 = Debug|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Release|Any CPU.Build.0 = Release|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Release|x64.ActiveCfg = Release|Any CPU + {727B7E51-F913-4196-99E3-B9D01CDBA2E2}.Release|x64.Build.0 = Release|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Debug|x64.Build.0 = Debug|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Release|Any CPU.Build.0 = Release|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Release|x64.ActiveCfg = Release|Any CPU + {4C685698-3083-4BB0-BFF4-6DA152115D2C}.Release|x64.Build.0 = Release|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Debug|x64.ActiveCfg = Debug|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Debug|x64.Build.0 = Debug|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Release|Any CPU.Build.0 = Release|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Release|x64.ActiveCfg = Release|Any CPU + {D853C05D-76B5-4D9D-B419-437ADD7C23FD}.Release|x64.Build.0 = Release|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Debug|x64.ActiveCfg = Debug|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Debug|x64.Build.0 = Debug|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Release|Any CPU.Build.0 = Release|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Release|x64.ActiveCfg = Release|Any CPU + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -414,6 +489,16 @@ Global {B5260FA4-98B1-4697-8924-19216B23E79B} = {7CE8B360-8848-4581-9575-5592130484DB} {D56193D2-6C50-42E6-AF12-18E151D162AF} = {7CE8B360-8848-4581-9575-5592130484DB} {5895B5E1-96E0-492D-9E35-5F66247B74AD} = {77A34DAA-FCE2-4162-B842-EF1609A4FB2D} + {4EC816D6-3F55-41AD-B805-955F251C03EB} = {DB733C6F-054A-443A-B3E2-0EEC7D2BCB4F} + {16D8E11F-7734-4B69-92E8-F1CD960A2C38} = {B55F2379-2A06-4761-B1E7-DAD1D14A93BC} + {5C1E97EE-D116-4237-A86C-496E123F429D} = {B55F2379-2A06-4761-B1E7-DAD1D14A93BC} + {A65EBB59-D598-46A0-B80C-0840D97E3A69} = {B55F2379-2A06-4761-B1E7-DAD1D14A93BC} + {727B7E51-F913-4196-99E3-B9D01CDBA2E2} = {3A21B19A-CDDB-4849-87F2-EBC575EA889F} + {4C685698-3083-4BB0-BFF4-6DA152115D2C} = {3A21B19A-CDDB-4849-87F2-EBC575EA889F} + {B55F2379-2A06-4761-B1E7-DAD1D14A93BC} = {4EC816D6-3F55-41AD-B805-955F251C03EB} + {3A21B19A-CDDB-4849-87F2-EBC575EA889F} = {4EC816D6-3F55-41AD-B805-955F251C03EB} + {D853C05D-76B5-4D9D-B419-437ADD7C23FD} = {B55F2379-2A06-4761-B1E7-DAD1D14A93BC} + {6F9DAD10-D077-4CA1-8BAA-A42D093F934E} = {B55F2379-2A06-4761-B1E7-DAD1D14A93BC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E2CBC81F-F2EE-4BD5-B567-AAF8057444D1}