diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..de197596 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..254d1f38 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.cs diff=csharp diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 00000000..88d3429e --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,26 @@ +name: publish +on: + push: + branches: [main] +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: DotNet Setup + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "9.x.x" + env: + NUGET_PACKAGES: ${{ runner.temp }}/nuget/packages + + - name: DotNet Pack + run: find ./source -name '*.csproj' -exec dotnet pack {} --configuration Release --output packages \; + + - name: DotNet NuGet Delete + run: curl -s https://azuresearch-usnc.nuget.org/query?q=owner:rafaelfgx | jq -r '.data[] | "dotnet nuget delete \(.id) \(.version) --non-interactive --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}"' | xargs -I {} bash -c "{}" + + - name: DotNet NuGet Push + run: dotnet nuget push "packages/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }} --skip-duplicate --no-symbols diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..0ab2e299 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*.bat +*.dll +*.exe +*.lib +*.log +*.pdb +*.ps1 +*.suo +*.tmp +*.user +*.userprefs +*.vspx +.vs +[Bb]in +[Dd]ebug +[Ll]og +[Oo]bj +[Rr]elease +[Rr]eleases +[Tt]est[Rr]esults diff --git a/license.md b/license.md new file mode 100644 index 00000000..1f95d26c --- /dev/null +++ b/license.md @@ -0,0 +1,5 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..fd7a5912 --- /dev/null +++ b/readme.md @@ -0,0 +1,41 @@ +# DotNetCore + +![](https://github.com/rafaelfgx/DotNetCore/actions/workflows/publish.yaml/badge.svg) + +.NET Nuget Packages. + +https://www.nuget.org/profiles/rafaelfgx + +## Documentation + +[AspNetCore](https://github.com/rafaelfgx/DotNetCore/tree/main/source/AspNetCore) + +[Domain](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Domain) + +[EntityFrameworkCore](https://github.com/rafaelfgx/DotNetCore/tree/main/source/EntityFrameworkCore) + +[Extensions](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Extensions) + +[IoC](https://github.com/rafaelfgx/DotNetCore/tree/main/source/IoC) + +[Logging](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Logging) + +[Mapping](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Mapping) + +[Mediator](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Mediator) + +[MongoDB](https://github.com/rafaelfgx/DotNetCore/tree/main/source/MongoDB) + +[Objects](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Objects) + +[RabbitMQ](https://github.com/rafaelfgx/DotNetCore/tree/main/source/RabbitMQ) + +[Repositories](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Repositories) + +[Results](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Results) + +[Security](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Security) + +[Services](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Services) + +[Validation](https://github.com/rafaelfgx/DotNetCore/tree/main/source/Validation) diff --git a/source/AspNetCore/Attributes/EnumAuthorizeAttribute.cs b/source/AspNetCore/Attributes/EnumAuthorizeAttribute.cs new file mode 100644 index 00000000..5b62b99f --- /dev/null +++ b/source/AspNetCore/Attributes/EnumAuthorizeAttribute.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Authorization; + +namespace DotNetCore.AspNetCore; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public sealed class EnumAuthorizeAttribute : AuthorizeAttribute +{ + public EnumAuthorizeAttribute(params object[] roles) => Roles = string.Join(",", roles.Select(role => Enum.GetName(role.GetType(), role))); +} diff --git a/source/AspNetCore/DotNetCore.AspNetCore.csproj b/source/AspNetCore/DotNetCore.AspNetCore.csproj new file mode 100644 index 00000000..ad4fa8f2 --- /dev/null +++ b/source/AspNetCore/DotNetCore.AspNetCore.csproj @@ -0,0 +1,18 @@ + + + DotNetCore.AspNetCore + DotNetCore.AspNetCore + DotNetCore.AspNetCore + https://github.com/rafaelfgx/DotNetCore/tree/main/source/AspNetCore + AspNetCore, Attribute, Attributes, IApplicationBuilder, ApplicationBuilder, IHostBuilder, HostBuilder, HttpRequest, IServiceCollection, ServiceCollection, Result, Results, IActionResult, ActionResult + DotNetCore.AspNetCore + + + + + + + + + + diff --git a/source/AspNetCore/Extensions/ApplicationBuilderExtensions.cs b/source/AspNetCore/Extensions/ApplicationBuilderExtensions.cs new file mode 100644 index 00000000..69ad9ee2 --- /dev/null +++ b/source/AspNetCore/Extensions/ApplicationBuilderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace DotNetCore.AspNetCore; + +public static class ApplicationBuilderExtensions +{ + public static void UseCorsAllowAny(this IApplicationBuilder application) => application.UseCors("AllowAny"); + + public static void UseException(this IApplicationBuilder application) + { + var environment = application.ApplicationServices.GetRequiredService(); + + if (environment.IsDevelopment()) application.UseDeveloperExceptionPage(); + } + + public static void UseLocalization(this IApplicationBuilder application, params string[] cultures) => application.UseRequestLocalization(options => options.AddSupportedCultures(cultures).AddSupportedUICultures(cultures).SetDefaultCulture(cultures.First())); +} diff --git a/source/AspNetCore/Extensions/BinaryFileExtensions.cs b/source/AspNetCore/Extensions/BinaryFileExtensions.cs new file mode 100644 index 00000000..ff60d3d3 --- /dev/null +++ b/source/AspNetCore/Extensions/BinaryFileExtensions.cs @@ -0,0 +1,21 @@ +using DotNetCore.Objects; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; + +namespace DotNetCore.AspNetCore; + +public static class BinaryFileExtensions +{ + public static IActionResult FileResult(this Task binaryFile) + { + if (binaryFile is null) return new NotFoundResult(); + + var file = binaryFile.Result; + + if (file is null) return new NotFoundResult(); + + new FileExtensionContentTypeProvider().TryGetContentType(file.ContentType, out var contentType); + + return contentType is null ? new NotFoundResult() : new FileContentResult(file.Bytes, contentType) { FileDownloadName = file.Name }; + } +} diff --git a/source/AspNetCore/Extensions/HttpRequestExtensions.cs b/source/AspNetCore/Extensions/HttpRequestExtensions.cs new file mode 100644 index 00000000..f223b9fc --- /dev/null +++ b/source/AspNetCore/Extensions/HttpRequestExtensions.cs @@ -0,0 +1,23 @@ +using DotNetCore.Objects; +using Microsoft.AspNetCore.Http; + +namespace DotNetCore.AspNetCore; + +public static class HttpRequestExtensions +{ + public static IList Files(this HttpRequest request) + { + var files = new List(); + + foreach (var file in request.Form.Files) + { + using var memoryStream = new MemoryStream(); + + file.CopyTo(memoryStream); + + files.Add(new BinaryFile(Guid.NewGuid(), file.Name, memoryStream.ToArray(), file.Length, file.ContentType)); + } + + return files; + } +} diff --git a/source/AspNetCore/Extensions/JsonStringBoolConverter.cs b/source/AspNetCore/Extensions/JsonStringBoolConverter.cs new file mode 100644 index 00000000..66657575 --- /dev/null +++ b/source/AspNetCore/Extensions/JsonStringBoolConverter.cs @@ -0,0 +1,11 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DotNetCore.AspNetCore; + +public class JsonStringBoolConverter : JsonConverter +{ + public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => bool.TryParse(reader.GetString(), out var result) && result; + + public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) => writer.WriteBooleanValue(value); +} diff --git a/source/AspNetCore/Extensions/ResultExtensions.cs b/source/AspNetCore/Extensions/ResultExtensions.cs new file mode 100644 index 00000000..263e9e20 --- /dev/null +++ b/source/AspNetCore/Extensions/ResultExtensions.cs @@ -0,0 +1,19 @@ +using DotNetCore.Results; +using Microsoft.AspNetCore.Mvc; + +namespace DotNetCore.AspNetCore; + +public static class ResultExtensions +{ + public static IActionResult ApiResult(this Result result) => new ObjectResult(result.HasMessage ? result.Message : result.Value) { StatusCode = (int)result.Status }; + + public static IActionResult ApiResult(this Task> result) => ApiResult(result.Result); + + public static IActionResult ApiResult(this Result result) => new ObjectResult(result.Message) { StatusCode = (int)result.Status }; + + public static IActionResult ApiResult(this Task result) => ApiResult(result.Result); + + public static IActionResult ApiResult(this T result) => new ObjectResult(result); + + public static IActionResult ApiResult(this Task result) => ApiResult(result.Result); +} diff --git a/source/AspNetCore/Extensions/ServiceCollectionExtensions.cs b/source/AspNetCore/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..71438628 --- /dev/null +++ b/source/AspNetCore/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; +using System.Text.Json.Serialization; + +namespace DotNetCore.AspNetCore; + +public static class ServiceCollectionExtensions +{ + public static IMvcBuilder AddAuthorizationPolicy(this IMvcBuilder builder) => builder.AddMvcOptions(options => options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()))); + + public static IServiceCollection AddCorsAllowAny(this IServiceCollection services) => services.AddCors(options => options.AddPolicy("AllowAny", policy => policy.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod())); + + public static IServiceCollection AddFileExtensionContentTypeProvider(this IServiceCollection services) => services.AddSingleton(); + + public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder) => builder.AddJsonOptions(options => + { + options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull; + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + options.JsonSerializerOptions.Converters.Add(new JsonStringBoolConverter()); + }); + + public static void AddSwaggerDefault(this IServiceCollection services) => services.AddSwaggerGen(ConfigureSwaggerGenOptions); + + public static IServiceCollection ConfigureFormOptionsMaxLengthLimit(this IServiceCollection services) => services.Configure(options => + { + options.ValueLengthLimit = int.MaxValue; + options.MultipartBodyLengthLimit = int.MaxValue; + }); + + private static void ConfigureSwaggerGenOptions(this SwaggerGenOptions options) + { + options.AddSecurityDefinition(JwtBearerDefaults.AuthenticationScheme, new OpenApiSecurityScheme + { + Type = SecuritySchemeType.ApiKey, + In = ParameterLocation.Header, + Name = "Authorization" + }); + + options.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = JwtBearerDefaults.AuthenticationScheme + }, + In = ParameterLocation.Header, + Scheme = SecuritySchemeType.OAuth2.ToString(), + Name = JwtBearerDefaults.AuthenticationScheme + }, + new List() + } + }); + } +} diff --git a/source/AspNetCore/readme.md b/source/AspNetCore/readme.md new file mode 100644 index 00000000..eedd7d74 --- /dev/null +++ b/source/AspNetCore/readme.md @@ -0,0 +1,86 @@ +# DotNetCore.AspNetCore + +## Attributes + +### EnumAuthorizeAttribute + +```cs +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] +public sealed class EnumAuthorizeAttribute : AuthorizeAttribute +{ + public EnumAuthorizeAttribute(params object[] roles) { } +} +``` + +## Extensions + +### ApplicationBuilderExtensions + +```cs +public static class ApplicationBuilderExtensions +{ + public static void UseCorsAllowAny(this IApplicationBuilder application) { } + + public static void UseException(this IApplicationBuilder application) { } + + public static void UseLocalization(this IApplicationBuilder application, params string[] cultures) { } + + public static void ConfigureFormOptions(this IServiceCollection services) { } +} +``` + +### BinaryFileExtensions + +```cs +public static class BinaryFileExtensions +{ + public static IActionResult FileResult(this Task binaryFile) { } +} +``` + +### HttpRequestExtensions + +```cs +public static class HttpRequestExtensions +{ + public static IList Files(this HttpRequest request) { } +} +``` + +### ResultExtensions + +```cs +public static class ResultExtensions +{ + public static IActionResult ApiResult(this Result result) { } + + public static IActionResult ApiResult(this Task> result) { } + + public static IActionResult ApiResult(this Result result) { } + + public static IActionResult ApiResult(this Task result) { } + + public static IActionResult ApiResult(this T result) { } + + public static IActionResult ApiResult(this Task result) { } +} +``` + +### ServiceCollectionExtensions + +```cs +public static class ServiceCollectionExtensions +{ + public static IMvcBuilder AddAuthorizationPolicy(this IMvcBuilder builder) { } + + public static IServiceCollection AddCorsAllowAny(this IServiceCollection services) { } + + public static IServiceCollection AddFileExtensionContentTypeProvider(this IServiceCollection services) { } + + public static IMvcBuilder AddJsonOptions(this IMvcBuilder builder) { } + + public static void AddSwaggerDefault(this IServiceCollection services) { } + + public static IServiceCollection ConfigureFormOptionsMaxLengthLimit(this IServiceCollection services) { } +} +``` diff --git a/source/Directory.Build.props b/source/Directory.Build.props new file mode 100644 index 00000000..66c19caf --- /dev/null +++ b/source/Directory.Build.props @@ -0,0 +1,23 @@ + + + true + true + true + false + enable + true + latest + en + MIT + readme.md + true + git + https://github.com/rafaelfgx/DotNetCore + snupkg + net9.0 + 19.7.0 + + + + + diff --git a/source/Directory.Packages.props b/source/Directory.Packages.props new file mode 100644 index 00000000..857cec75 --- /dev/null +++ b/source/Directory.Packages.props @@ -0,0 +1,34 @@ + + + true + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/source/Domain/DotNetCore.Domain.csproj b/source/Domain/DotNetCore.Domain.csproj new file mode 100644 index 00000000..d9e9cc7c --- /dev/null +++ b/source/Domain/DotNetCore.Domain.csproj @@ -0,0 +1,10 @@ + + + DotNetCore.Domain + DotNetCore.Domain + DotNetCore.Domain + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Domain + Domain, DDD, DomainDrivenDesign, Entity, Event, ValueObject + DotNetCore.Domain + + diff --git a/source/Domain/Entity.cs b/source/Domain/Entity.cs new file mode 100644 index 00000000..c8c0c1b0 --- /dev/null +++ b/source/Domain/Entity.cs @@ -0,0 +1,14 @@ +namespace DotNetCore.Domain; + +public abstract class Entity +{ + public long Id { get; init; } + + public static bool operator !=(Entity left, Entity right) => !(left == right); + + public static bool operator ==(Entity left, Entity right) => left?.Equals(right) == true; + + public override bool Equals(object obj) => obj is Entity entity && (ReferenceEquals(this, entity) || Id == entity.Id); + + public override int GetHashCode() => Id.GetHashCode(); +} diff --git a/source/Domain/Event.cs b/source/Domain/Event.cs new file mode 100644 index 00000000..f9db1243 --- /dev/null +++ b/source/Domain/Event.cs @@ -0,0 +1,8 @@ +namespace DotNetCore.Domain; + +public abstract class Event +{ + public Guid Id { get; } = Guid.NewGuid(); + + public DateTime DateTime { get; } = DateTime.UtcNow; +} diff --git a/source/Domain/readme.md b/source/Domain/readme.md new file mode 100644 index 00000000..563f8bed --- /dev/null +++ b/source/Domain/readme.md @@ -0,0 +1,29 @@ +# DotNetCore.Domain + +## Entity + +```cs +public abstract class Entity +{ + public long Id { get; init; } + + public static bool operator !=(Entity left, Entity right) { } + + public static bool operator ==(Entity left, Entity right) { } + + public override bool Equals(object obj) { } + + public override int GetHashCode() { } +} +``` + +## Event + +```cs +public abstract class Event +{ + public Guid Id { get; } = Guid.NewGuid(); + + public DateTime DateTime { get; } = DateTime.UtcNow; +} +``` diff --git a/source/DotNetCore.sln b/source/DotNetCore.sln new file mode 100644 index 00000000..839a1b40 --- /dev/null +++ b/source/DotNetCore.sln @@ -0,0 +1,115 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32616.157 +MinimumVisualStudioVersion = 17.2.32616.157 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.AspNetCore", "AspNetCore\DotNetCore.AspNetCore.csproj", "{9B2E66F2-CD32-4A9B-BA3E-B887F5442D72}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Domain", "Domain\DotNetCore.Domain.csproj", "{D766DABA-B946-46E9-AB9D-669C88C2A09E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.EntityFrameworkCore", "EntityFrameworkCore\DotNetCore.EntityFrameworkCore.csproj", "{B7D1B18B-4986-4023-ADDF-8778D56017E0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Extensions", "Extensions\DotNetCore.Extensions.csproj", "{831B7F04-AACC-45EF-B767-E0F6AD50A824}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.IoC", "IoC\DotNetCore.IoC.csproj", "{38D5FB7D-C85B-44C5-925A-0741690F7EA9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Logging", "Logging\DotNetCore.Logging.csproj", "{2E0C3E42-4ED9-422B-8868-61A9F4C8CE75}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Mapping", "Mapping\DotNetCore.Mapping.csproj", "{49F742BC-CE56-410F-9B46-65900F0C012B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Mediator", "Mediator\DotNetCore.Mediator.csproj", "{01EB312A-E67D-4E9C-A159-16945153494E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.MongoDB", "MongoDB\DotNetCore.MongoDB.csproj", "{F1B9AE78-77EA-481C-92FE-562C1C529C38}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Objects", "Objects\DotNetCore.Objects.csproj", "{04D0F3BD-E7F5-4A5B-8B88-DE780CCC5AD7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.RabbitMQ", "RabbitMQ\DotNetCore.RabbitMQ.csproj", "{7977E46D-F4E4-4B48-9573-532064247265}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Repositories", "Repositories\DotNetCore.Repositories.csproj", "{43F95A55-0C4A-4AB5-A4EF-CC94F5CE55C7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Results", "Results\DotNetCore.Results.csproj", "{99249542-E7AF-45F1-AD1E-16398B3127BF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Security", "Security\DotNetCore.Security.csproj", "{FCD0B4F0-32DD-44B9-8899-556F5DAE8AA6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Services", "Services\DotNetCore.Services.csproj", "{CA2254AE-87E5-4402-816A-E3DE1F024693}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetCore.Validation", "Validation\DotNetCore.Validation.csproj", "{FF02D406-2CA7-4D5A-979A-3694E2882C8C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9B2E66F2-CD32-4A9B-BA3E-B887F5442D72}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B2E66F2-CD32-4A9B-BA3E-B887F5442D72}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B2E66F2-CD32-4A9B-BA3E-B887F5442D72}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B2E66F2-CD32-4A9B-BA3E-B887F5442D72}.Release|Any CPU.Build.0 = Release|Any CPU + {D766DABA-B946-46E9-AB9D-669C88C2A09E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D766DABA-B946-46E9-AB9D-669C88C2A09E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D766DABA-B946-46E9-AB9D-669C88C2A09E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D766DABA-B946-46E9-AB9D-669C88C2A09E}.Release|Any CPU.Build.0 = Release|Any CPU + {B7D1B18B-4986-4023-ADDF-8778D56017E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7D1B18B-4986-4023-ADDF-8778D56017E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7D1B18B-4986-4023-ADDF-8778D56017E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7D1B18B-4986-4023-ADDF-8778D56017E0}.Release|Any CPU.Build.0 = Release|Any CPU + {831B7F04-AACC-45EF-B767-E0F6AD50A824}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {831B7F04-AACC-45EF-B767-E0F6AD50A824}.Debug|Any CPU.Build.0 = Debug|Any CPU + {831B7F04-AACC-45EF-B767-E0F6AD50A824}.Release|Any CPU.ActiveCfg = Release|Any CPU + {831B7F04-AACC-45EF-B767-E0F6AD50A824}.Release|Any CPU.Build.0 = Release|Any CPU + {38D5FB7D-C85B-44C5-925A-0741690F7EA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38D5FB7D-C85B-44C5-925A-0741690F7EA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38D5FB7D-C85B-44C5-925A-0741690F7EA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38D5FB7D-C85B-44C5-925A-0741690F7EA9}.Release|Any CPU.Build.0 = Release|Any CPU + {2E0C3E42-4ED9-422B-8868-61A9F4C8CE75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2E0C3E42-4ED9-422B-8868-61A9F4C8CE75}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2E0C3E42-4ED9-422B-8868-61A9F4C8CE75}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2E0C3E42-4ED9-422B-8868-61A9F4C8CE75}.Release|Any CPU.Build.0 = Release|Any CPU + {49F742BC-CE56-410F-9B46-65900F0C012B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {49F742BC-CE56-410F-9B46-65900F0C012B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {49F742BC-CE56-410F-9B46-65900F0C012B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {49F742BC-CE56-410F-9B46-65900F0C012B}.Release|Any CPU.Build.0 = Release|Any CPU + {01EB312A-E67D-4E9C-A159-16945153494E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {01EB312A-E67D-4E9C-A159-16945153494E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {01EB312A-E67D-4E9C-A159-16945153494E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {01EB312A-E67D-4E9C-A159-16945153494E}.Release|Any CPU.Build.0 = Release|Any CPU + {F1B9AE78-77EA-481C-92FE-562C1C529C38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1B9AE78-77EA-481C-92FE-562C1C529C38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1B9AE78-77EA-481C-92FE-562C1C529C38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1B9AE78-77EA-481C-92FE-562C1C529C38}.Release|Any CPU.Build.0 = Release|Any CPU + {04D0F3BD-E7F5-4A5B-8B88-DE780CCC5AD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {04D0F3BD-E7F5-4A5B-8B88-DE780CCC5AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {04D0F3BD-E7F5-4A5B-8B88-DE780CCC5AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {04D0F3BD-E7F5-4A5B-8B88-DE780CCC5AD7}.Release|Any CPU.Build.0 = Release|Any CPU + {7977E46D-F4E4-4B48-9573-532064247265}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7977E46D-F4E4-4B48-9573-532064247265}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7977E46D-F4E4-4B48-9573-532064247265}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7977E46D-F4E4-4B48-9573-532064247265}.Release|Any CPU.Build.0 = Release|Any CPU + {43F95A55-0C4A-4AB5-A4EF-CC94F5CE55C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43F95A55-0C4A-4AB5-A4EF-CC94F5CE55C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43F95A55-0C4A-4AB5-A4EF-CC94F5CE55C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43F95A55-0C4A-4AB5-A4EF-CC94F5CE55C7}.Release|Any CPU.Build.0 = Release|Any CPU + {99249542-E7AF-45F1-AD1E-16398B3127BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99249542-E7AF-45F1-AD1E-16398B3127BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99249542-E7AF-45F1-AD1E-16398B3127BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99249542-E7AF-45F1-AD1E-16398B3127BF}.Release|Any CPU.Build.0 = Release|Any CPU + {FCD0B4F0-32DD-44B9-8899-556F5DAE8AA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FCD0B4F0-32DD-44B9-8899-556F5DAE8AA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FCD0B4F0-32DD-44B9-8899-556F5DAE8AA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FCD0B4F0-32DD-44B9-8899-556F5DAE8AA6}.Release|Any CPU.Build.0 = Release|Any CPU + {CA2254AE-87E5-4402-816A-E3DE1F024693}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA2254AE-87E5-4402-816A-E3DE1F024693}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA2254AE-87E5-4402-816A-E3DE1F024693}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA2254AE-87E5-4402-816A-E3DE1F024693}.Release|Any CPU.Build.0 = Release|Any CPU + {FF02D406-2CA7-4D5A-979A-3694E2882C8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF02D406-2CA7-4D5A-979A-3694E2882C8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF02D406-2CA7-4D5A-979A-3694E2882C8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF02D406-2CA7-4D5A-979A-3694E2882C8C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {76E7371D-C80A-4102-9FF2-21F7EA09705E} + EndGlobalSection +EndGlobal diff --git a/source/EntityFrameworkCore/DotNetCore.EntityFrameworkCore.csproj b/source/EntityFrameworkCore/DotNetCore.EntityFrameworkCore.csproj new file mode 100644 index 00000000..1228a105 --- /dev/null +++ b/source/EntityFrameworkCore/DotNetCore.EntityFrameworkCore.csproj @@ -0,0 +1,19 @@ + + + DotNetCore.EntityFrameworkCore + DotNetCore.EntityFrameworkCore + DotNetCore.EntityFrameworkCore + https://github.com/rafaelfgx/DotNetCore/tree/main/source/EntityFrameworkCore + ORM, ObjectRelationalMapping, EntityFramework, EntityFrameworkCore, Context, Repository, GenericRepository, BaseRepository, RepositoryBase, CommandRepository, QueryRepository, CQRS, Command, Query + DotNetCore.EntityFrameworkCore + + + + + + + + + + + diff --git a/source/EntityFrameworkCore/Extensions.cs b/source/EntityFrameworkCore/Extensions.cs new file mode 100644 index 00000000..ee60fb43 --- /dev/null +++ b/source/EntityFrameworkCore/Extensions.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace DotNetCore.EntityFrameworkCore; + +public static class Extensions +{ + public static void AddContext(this IServiceCollection services, Action options) where T : DbContext + { + services.AddDbContextPool(options); + + services.BuildServiceProvider().GetRequiredService().Database.Migrate(); + + services.AddScoped>(); + } + + public static void AddContextMemory(this IServiceCollection services) where T : DbContext + { + services.AddDbContextPool(options => options.UseInMemoryDatabase(typeof(T).Name)); + + services.BuildServiceProvider().GetRequiredService().Database.EnsureCreated(); + + services.AddScoped>(); + } + + public static DbSet CommandSet(this DbContext context) where T : class => context.DetectChangesLazyLoading(true).Set(); + + public static DbContext DetectChangesLazyLoading(this DbContext context, bool enabled) + { + context.ChangeTracker.AutoDetectChangesEnabled = enabled; + context.ChangeTracker.LazyLoadingEnabled = enabled; + context.ChangeTracker.QueryTrackingBehavior = enabled ? QueryTrackingBehavior.TrackAll : QueryTrackingBehavior.NoTracking; + + return context; + } + + public static IQueryable QuerySet(this DbContext context) where T : class => context.DetectChangesLazyLoading(false).Set().AsNoTracking(); + + public static object[] PrimaryKeyValues(this DbContext context, object entity) => context.Model.FindEntityType(typeof(T))?.FindPrimaryKey()?.Properties.Select(property => entity.GetType().GetProperty(property.Name)?.GetValue(entity, default)).ToArray(); +} diff --git a/source/EntityFrameworkCore/Repository/EFCommandRepository.cs b/source/EntityFrameworkCore/Repository/EFCommandRepository.cs new file mode 100644 index 00000000..beebbc3b --- /dev/null +++ b/source/EntityFrameworkCore/Repository/EFCommandRepository.cs @@ -0,0 +1,87 @@ +using DotNetCore.Repositories; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; + +namespace DotNetCore.EntityFrameworkCore; + +public class EFCommandRepository(DbContext context) : ICommandRepository where T : class +{ + private DbSet Set => context.CommandSet(); + + public void Add(T item) => Set.Add(item); + + public Task AddAsync(T item) => Set.AddAsync(item).AsTask(); + + public void AddRange(IEnumerable items) => Set.AddRange(items); + + public Task AddRangeAsync(IEnumerable items) => Set.AddRangeAsync(items); + + public void Delete(object key) + { + var item = Set.Find(key); + + if (item is null) return; + + Set.Remove(item); + } + + public void Delete(Expression> where) + { + var items = Set.Where(where); + + if (!items.Any()) return; + + Set.RemoveRange(items); + } + + public Task DeleteAsync(object key) => Task.Run(() => Delete(key)); + + public Task DeleteAsync(Expression> where) => Task.Run(() => Delete(where)); + + public void Update(T item) + { + var primaryKeyValues = context.PrimaryKeyValues(item); + + var entity = Set.Find(primaryKeyValues); + + if (entity is null) return; + + context.Entry(entity).State = EntityState.Detached; + + context.Update(item); + } + + public Task UpdateAsync(T item) => Task.Run(() => Update(item)); + + public void UpdatePartial(object item) + { + var primaryKeyValues = context.PrimaryKeyValues(item); + + var entity = Set.Find(primaryKeyValues); + + if (entity is null) return; + + var entry = context.Entry(entity); + + entry.CurrentValues.SetValues(item); + + foreach (var navigation in entry.Metadata.GetNavigations()) + { + if (navigation.IsOnDependent || navigation.IsCollection || !navigation.ForeignKey.IsOwnership) continue; + + var property = item.GetType().GetProperty(navigation.Name); + + if (property is null) continue; + + var value = property.GetValue(item, default); + + entry.Reference(navigation.Name).TargetEntry?.CurrentValues.SetValues(value!); + } + } + + public Task UpdatePartialAsync(object item) => Task.Run(() => UpdatePartial(item)); + + public void UpdateRange(IEnumerable items) => Set.UpdateRange(items); + + public Task UpdateRangeAsync(IEnumerable items) => Task.Run(() => UpdateRange(items)); +} diff --git a/source/EntityFrameworkCore/Repository/EFQueryRepository.cs b/source/EntityFrameworkCore/Repository/EFQueryRepository.cs new file mode 100644 index 00000000..76eaedba --- /dev/null +++ b/source/EntityFrameworkCore/Repository/EFQueryRepository.cs @@ -0,0 +1,34 @@ +using DotNetCore.Repositories; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; + +namespace DotNetCore.EntityFrameworkCore; + +public class EFQueryRepository(DbContext context) : IQueryRepository where T : class +{ + public IQueryable Queryable => context.QuerySet(); + + public bool Any() => Queryable.Any(); + + public bool Any(Expression> where) => Queryable.Any(where); + + public Task AnyAsync() => Queryable.AnyAsync(); + + public Task AnyAsync(Expression> where) => Queryable.AnyAsync(where); + + public long Count() => Queryable.LongCount(); + + public long Count(Expression> where) => Queryable.LongCount(where); + + public Task CountAsync() => Queryable.LongCountAsync(); + + public Task CountAsync(Expression> where) => Queryable.LongCountAsync(where); + + public T Get(object key) => context.DetectChangesLazyLoading(false).Set().Find(key); + + public Task GetAsync(object key) => context.DetectChangesLazyLoading(false).Set().FindAsync(key).AsTask(); + + public IEnumerable List() => Queryable.ToList(); + + public async Task> ListAsync() => await Queryable.ToListAsync().ConfigureAwait(false); +} diff --git a/source/EntityFrameworkCore/Repository/EFRepository.cs b/source/EntityFrameworkCore/Repository/EFRepository.cs new file mode 100644 index 00000000..4ceb169b --- /dev/null +++ b/source/EntityFrameworkCore/Repository/EFRepository.cs @@ -0,0 +1,6 @@ +using DotNetCore.Repositories; +using Microsoft.EntityFrameworkCore; + +namespace DotNetCore.EntityFrameworkCore; + +public class EFRepository(DbContext context) : Repository(new EFCommandRepository(context), new EFQueryRepository(context)) where T : class; diff --git a/source/EntityFrameworkCore/UnitOfWork/IUnitOfWork.cs b/source/EntityFrameworkCore/UnitOfWork/IUnitOfWork.cs new file mode 100644 index 00000000..184e4b95 --- /dev/null +++ b/source/EntityFrameworkCore/UnitOfWork/IUnitOfWork.cs @@ -0,0 +1,6 @@ +namespace DotNetCore.EntityFrameworkCore; + +public interface IUnitOfWork +{ + Task SaveChangesAsync(); +} diff --git a/source/EntityFrameworkCore/UnitOfWork/UnitOfWork.cs b/source/EntityFrameworkCore/UnitOfWork/UnitOfWork.cs new file mode 100644 index 00000000..74838286 --- /dev/null +++ b/source/EntityFrameworkCore/UnitOfWork/UnitOfWork.cs @@ -0,0 +1,8 @@ +using Microsoft.EntityFrameworkCore; + +namespace DotNetCore.EntityFrameworkCore; + +public sealed class UnitOfWork(TDbContext context) : IUnitOfWork where TDbContext : DbContext +{ + public Task SaveChangesAsync() => context.SaveChangesAsync(); +} diff --git a/source/EntityFrameworkCore/readme.md b/source/EntityFrameworkCore/readme.md new file mode 100644 index 00000000..43a3647c --- /dev/null +++ b/source/EntityFrameworkCore/readme.md @@ -0,0 +1,119 @@ +# DotNetCore.EntityFrameworkCore + +## Extensions + +```cs +public static class Extensions +{ + public static void AddContext(this IServiceCollection services, Action options) where T : DbContext { } + + public static void AddContextMemory(this IServiceCollection services) where T : DbContext { } + + public static DbSet CommandSet(this DbContext context) where T : class { } + + public static DbContext DetectChangesLazyLoading(this DbContext context, bool enabled) { } + + public static IQueryable QuerySet(this DbContext context) where T : class { } +} +``` + +### EFCommandRepository + +```cs +public class EFCommandRepository : ICommandRepository where T : class +{ + public EFCommandRepository(DbContext context) { } + + public void Add(T item) { } + + public Task AddAsync(T item) { } + + public void AddRange(IEnumerable items) { } + + public Task AddRangeAsync(IEnumerable items) { } + + public void Delete(object key) { } + + public void Delete(Expression> where) { } + + public Task DeleteAsync(object key) { } + + public Task DeleteAsync(Expression> where) { } + + public void Update(T item) { } + + public Task UpdateAsync(T item) { } + + public void UpdatePartial(object item) { } + + public Task UpdatePartialAsync(object item) { } + + public void UpdateRange(IEnumerable items) { } + + public Task UpdateRangeAsync(IEnumerable items) { } +} +``` + +### EFQueryRepository + +```cs +public class EFQueryRepository : IQueryRepository where T : class +{ + public EFQueryRepository(DbContext context) { } + + public IQueryable Queryable { get; }; + + public bool Any() { } + + public bool Any(Expression> where) { } + + public Task AnyAsync() { } + + public Task AnyAsync(Expression> where) { } + + public long Count() { } + + public long Count(Expression> where) { } + + public Task CountAsync() { } + + public Task CountAsync(Expression> where) { } + + public T Get(object key) { } + + public Task GetAsync(object key) { } + + public IEnumerable List() { } + + public async Task> ListAsync() { } +} +``` + +### EFRepository + +```cs +public class EFRepository : Repository where T : class +{ + public EFRepository(DbContext context) : base(new EFCommandRepository(context), new EFQueryRepository(context)) { } +} +``` + +### IUnitOfWork + +```cs +public interface IUnitOfWork +{ + Task SaveChangesAsync(); +} +``` + +### UnitOfWork + +```cs +public sealed class UnitOfWork : IUnitOfWork where TDbContext : DbContext +{ + public UnitOfWork(TDbContext context) { } + + public Task SaveChangesAsync() { } +} +``` diff --git a/source/Extensions/AssemblyExtensions.cs b/source/Extensions/AssemblyExtensions.cs new file mode 100644 index 00000000..a02b2713 --- /dev/null +++ b/source/Extensions/AssemblyExtensions.cs @@ -0,0 +1,8 @@ +using System.Reflection; + +namespace DotNetCore.Extensions; + +public static class AssemblyExtensions +{ + public static FileInfo FileInfo(this Assembly assembly) => new(assembly.Location); +} diff --git a/source/Extensions/ByteExtensions.cs b/source/Extensions/ByteExtensions.cs new file mode 100644 index 00000000..894e9bf5 --- /dev/null +++ b/source/Extensions/ByteExtensions.cs @@ -0,0 +1,36 @@ +using System.IO.Compression; +using System.Text; +using System.Text.Json; + +namespace DotNetCore.Extensions; + +public static class ByteExtensions +{ + public static byte[] Compress(this byte[] bytes) + { + if (bytes is null) return []; + + using var output = new MemoryStream(); + + using var stream = new BrotliStream(output, CompressionMode.Compress); + + stream.Write(bytes, 0, bytes.Length); + + return output.ToArray(); + } + + public static byte[] Decompress(this byte[] bytes) + { + using var input = new MemoryStream(bytes); + + using var stream = new BrotliStream(input, CompressionMode.Decompress); + + using var output = new MemoryStream(); + + stream.CopyTo(output); + + return output.ToArray(); + } + + public static T Object(this byte[] bytes) => JsonSerializer.Deserialize(Encoding.Default.GetString(bytes)); +} diff --git a/source/Extensions/ClaimsPrincipalExtensions.cs b/source/Extensions/ClaimsPrincipalExtensions.cs new file mode 100644 index 00000000..a60f392a --- /dev/null +++ b/source/Extensions/ClaimsPrincipalExtensions.cs @@ -0,0 +1,20 @@ +using System.Security.Claims; + +namespace DotNetCore.Extensions; + +public static class ClaimsPrincipalExtensions +{ + public static Claim Claim(this ClaimsPrincipal claimsPrincipal, string claimType) => claimsPrincipal?.FindFirst(claimType); + + public static IEnumerable ClaimRoles(this ClaimsPrincipal claimsPrincipal) => claimsPrincipal?.Claims("role"); + + public static IEnumerable Claims(this ClaimsPrincipal claimsPrincipal, string claimType) => claimsPrincipal?.FindAll(claimType).Select(x => x.Value).ToList(); + + public static string ClaimSub(this ClaimsPrincipal claimsPrincipal) => claimsPrincipal?.Claim("sub")?.Value; + + public static long Id(this ClaimsPrincipal claimsPrincipal) => long.TryParse(claimsPrincipal.ClaimSub(), out var value) ? value : 0; + + public static IEnumerable Roles(this ClaimsPrincipal claimsPrincipal) where T : Enum => claimsPrincipal.ClaimRoles().Select(value => (T)Enum.Parse(typeof(T), value)).ToList(); + + public static T RolesFlag(this ClaimsPrincipal claimsPrincipal) where T : Enum => (T)Enum.Parse(typeof(T), claimsPrincipal.Roles().Sum(value => Convert.ToInt64(value)).ToString(), true); +} diff --git a/source/Extensions/DateTimeExtensions.cs b/source/Extensions/DateTimeExtensions.cs new file mode 100644 index 00000000..d19c7724 --- /dev/null +++ b/source/Extensions/DateTimeExtensions.cs @@ -0,0 +1,28 @@ +namespace DotNetCore.Extensions; + +public static class DateTimeExtensions +{ + public static List<(DateTime, DateTime)> Chunks(this DateTime startDate, DateTime endDate, int days) + { + endDate = endDate.Date.AddHours(23).AddMinutes(59).AddSeconds(59); + + var chunks = new List<(DateTime, DateTime)>(); + + var currentDate = startDate; + + while (currentDate <= endDate) + { + var chunkStartDate = currentDate; + + var chunkEndDate = currentDate.AddDays(days).AddHours(23).AddMinutes(59).AddSeconds(59); + + if (chunkEndDate > endDate) chunkEndDate = endDate; + + chunks.Add((chunkStartDate, chunkEndDate)); + + currentDate = chunkEndDate.AddSeconds(1); + } + + return chunks; + } +} diff --git a/source/Extensions/DirectoryInfoExtensions.cs b/source/Extensions/DirectoryInfoExtensions.cs new file mode 100644 index 00000000..129d3655 --- /dev/null +++ b/source/Extensions/DirectoryInfoExtensions.cs @@ -0,0 +1,6 @@ +namespace DotNetCore.Extensions; + +public static class DirectoryInfoExtensions +{ + public static FileInfo GetFile(this DirectoryInfo directoryInfo, string name) => directoryInfo?.GetFiles(string.Concat(name, ".", "*")).SingleOrDefault(); +} diff --git a/source/Extensions/DotNetCore.Extensions.csproj b/source/Extensions/DotNetCore.Extensions.csproj new file mode 100644 index 00000000..b277aed0 --- /dev/null +++ b/source/Extensions/DotNetCore.Extensions.csproj @@ -0,0 +1,10 @@ + + + DotNetCore.Extensions + DotNetCore.Extensions + DotNetCore.Extensions + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Extensions + Extension, Extensions, Byte, Claim, ClaimsPrincipal, DirectoryInfo, Enum, Object, Queryable, String + DotNetCore.Extensions + + diff --git a/source/Extensions/EnumExtensions.cs b/source/Extensions/EnumExtensions.cs new file mode 100644 index 00000000..2e0bbfa6 --- /dev/null +++ b/source/Extensions/EnumExtensions.cs @@ -0,0 +1,28 @@ +using System.ComponentModel; + +namespace DotNetCore.Extensions; + +public static class EnumExtensions +{ + public static string GetDescription(this Enum value) + { + if (value is null) return default; + + var attribute = value.GetAttribute(); + + return attribute is null ? value.ToString() : attribute.Description; + } + + public static string[] ToArray(this Enum value) => value?.ToString().Split(", "); + + private static T GetAttribute(this Enum value) where T : Attribute + { + if (value is null) return default; + + var member = value.GetType().GetMember(value.ToString()); + + var attributes = member[0].GetCustomAttributes(typeof(T), false); + + return (T)attributes[0]; + } +} diff --git a/source/Extensions/ObjectExtensions.cs b/source/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..5eeab11f --- /dev/null +++ b/source/Extensions/ObjectExtensions.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DotNetCore.Extensions; + +public static class ObjectExtensions +{ + public static byte[] Bytes(this object obj) => Encoding.Default.GetBytes(JsonSerializer.Serialize(obj)); + + public static Dictionary Dictionary(this object obj) + { + if (obj is null) return default; + + var dictionary = new Dictionary(); + + foreach (var property in obj.GetType().GetProperties()) + { + dictionary[property.Name] = property.GetValue(obj); + } + + return dictionary; + } + + public static IEnumerable GetPropertiesWithAttribute(this object obj) where T : Attribute => obj.GetType().GetProperties().Where(property => Attribute.IsDefined(property, typeof(T))); + + public static string Serialize(this object obj) => JsonSerializer.Serialize(obj, new JsonSerializerOptions(JsonSerializerDefaults.Web) { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }); + + public static void SetProperty(this object obj, string name, object value) => obj.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase)?.SetValue(obj, value); +} diff --git a/source/Extensions/QueryableExtensions.cs b/source/Extensions/QueryableExtensions.cs new file mode 100644 index 00000000..afd9587c --- /dev/null +++ b/source/Extensions/QueryableExtensions.cs @@ -0,0 +1,90 @@ +using System.Linq.Expressions; + +namespace DotNetCore.Extensions; + +public static class QueryableExtensions +{ + public static IQueryable Filter(this IQueryable queryable, string property, object value) => queryable.Filter(property, string.Empty, value); + + public static IQueryable Filter(this IQueryable queryable, string property, string comparison, object value) + { + if (string.IsNullOrWhiteSpace(property) || value is null || string.IsNullOrWhiteSpace(value.ToString())) return queryable; + + var parameter = Expression.Parameter(typeof(T)); + + var left = Create(property, parameter); + + try + { + var propertyInfo = typeof(T).GetProperty(property); + + if (propertyInfo is null) return queryable; + + var type = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType; + + value = Change(value, type); + } + catch + { + return Enumerable.Empty().AsQueryable(); + } + + var right = Expression.Constant(value, left.Type); + + var body = Create(left, comparison, right); + + var expression = Expression.Lambda>(body, parameter); + + return queryable.Where(expression); + } + + public static IQueryable Order(this IQueryable queryable, string property, bool ascending) + { + if (queryable is null || string.IsNullOrWhiteSpace(property)) return queryable; + + var parameter = Expression.Parameter(typeof(T)); + + var body = Create(property, parameter); + + dynamic expression = Expression.Lambda(body, parameter); + + return ascending ? Queryable.OrderBy(queryable, expression) : Queryable.OrderByDescending(queryable, expression); + } + + public static IQueryable Page(this IQueryable queryable, int index, int size) => queryable is null || index <= 0 || size <= 0 ? queryable : queryable.Skip((index - 1) * size).Take(size); + + private static object Change(object value, Type type) + { + if (type.BaseType != typeof(Enum)) return Convert.ChangeType(value, type); + + var stringValue = value.ToString(); + + if (stringValue is null) return default; + + value = Enum.Parse(type, stringValue); + + return Convert.ChangeType(value, type); + } + + private static Expression Create(string property, Expression parameter) => property.Split('.').Aggregate(parameter, Expression.Property); + + private static Expression Create(Expression left, string comparison, Expression right) + { + if (string.IsNullOrWhiteSpace(comparison) && left.Type == typeof(string)) + { + return Expression.Call(left, nameof(string.Contains), Type.EmptyTypes, right); + } + + var type = comparison switch + { + "<" => ExpressionType.LessThan, + "<=" => ExpressionType.LessThanOrEqual, + ">" => ExpressionType.GreaterThan, + ">=" => ExpressionType.GreaterThanOrEqual, + "!=" => ExpressionType.NotEqual, + _ => ExpressionType.Equal + }; + + return Expression.MakeBinary(type, left, right); + } +} diff --git a/source/Extensions/StreamExtensions.cs b/source/Extensions/StreamExtensions.cs new file mode 100644 index 00000000..b447b28a --- /dev/null +++ b/source/Extensions/StreamExtensions.cs @@ -0,0 +1,13 @@ +namespace DotNetCore.Extensions; + +public static class StreamExtensions +{ + public static string ToBase64String(this Stream stream) + { + using var memoryStream = new MemoryStream(); + + stream.CopyTo(memoryStream); + + return Convert.ToBase64String(memoryStream.ToArray()); + } +} diff --git a/source/Extensions/StringExtensions.cs b/source/Extensions/StringExtensions.cs new file mode 100644 index 00000000..adf03906 --- /dev/null +++ b/source/Extensions/StringExtensions.cs @@ -0,0 +1,48 @@ +using System.Net.Mail; +using System.Numerics; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; + +namespace DotNetCore.Extensions; + +public static class StringExtensions +{ + public static T Deserialize(this string value) where T : class + { + return string.IsNullOrWhiteSpace(value) + ? default + : JsonSerializer.Deserialize(value, new JsonSerializerOptions(JsonSerializerDefaults.Web) + { + NumberHandling = JsonNumberHandling.AllowReadingFromString, + PropertyNameCaseInsensitive = true + }); + } + + public static string ExtractNumbers(this string value) => string.IsNullOrWhiteSpace(value) ? value : Regex.Replace(value, "[^0-9]", string.Empty); + + public static bool IsDate(this string value) => !string.IsNullOrWhiteSpace(value) && DateOnly.TryParseExact(value, "dd/MM/yyyy", out _); + + public static bool IsEmail(this string value) => !string.IsNullOrWhiteSpace(value) && MailAddress.TryCreate(value, out _); + + public static bool IsInteger(this string value) => !string.IsNullOrWhiteSpace(value) && BigInteger.TryParse(value, out _); + + public static bool IsLogin(this string value) => !string.IsNullOrWhiteSpace(value) && new Regex("^[a-z0-9._-]{10,50}$").IsMatch(value); + + public static bool IsNumber(this string value) => !string.IsNullOrWhiteSpace(value) && decimal.TryParse(value, out _); + + public static bool IsPassword(this string value) => !string.IsNullOrWhiteSpace(value) && new Regex(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^a-zA-Z\d\s]).{10,50}$").IsMatch(value); + + public static bool IsTime(this string value) => !string.IsNullOrWhiteSpace(value) && TimeOnly.TryParseExact(value, "HH:mm:ss", out _); + + public static bool IsUrl(this string value) => !string.IsNullOrWhiteSpace(value) && Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out _); + + public static string RemoveAccents(this string value) => string.IsNullOrWhiteSpace(value) ? value : Encoding.ASCII.GetString(Encoding.GetEncoding("Cyrillic").GetBytes(value)); + + public static string RemoveDuplicateSpaces(this string value) => string.IsNullOrWhiteSpace(value) ? value : Regex.Replace(value, @"\s+", " "); + + public static string RemoveSpecialCharacters(this string value) => string.IsNullOrWhiteSpace(value) ? value : Regex.Replace(value, "[^0-9a-zA-Z ]+", string.Empty); + + public static string Sanitize(this string value) => string.IsNullOrWhiteSpace(value) ? value : value.RemoveSpecialCharacters().RemoveDuplicateSpaces().RemoveAccents(); +} diff --git a/source/Extensions/readme.md b/source/Extensions/readme.md new file mode 100644 index 00000000..3eb23296 --- /dev/null +++ b/source/Extensions/readme.md @@ -0,0 +1,149 @@ +# DotNetCore.Extensions + +## AssemblyExtensions + +```cs +public static class AssemblyExtensions +{ + public static FileInfo FileInfo(this Assembly assembly) { } +} +``` + +## ByteExtensions + +```cs +public static class ByteExtensions +{ + public static byte[] Compress(this byte[] bytes) { } + + public static byte[] Decompress(this byte[] bytes) { } + + public static T Object(this byte[] bytes) { } +} +``` + +## ClaimsPrincipalExtensions + +```cs +public static class ClaimsPrincipalExtensions +{ + public static Claim Claim(this ClaimsPrincipal claimsPrincipal, string claimType) { } + + public static IEnumerable ClaimRoles(this ClaimsPrincipal claimsPrincipal) { } + + public static IEnumerable Claims(this ClaimsPrincipal claimsPrincipal, string claimType) { } + + public static string ClaimSub(this ClaimsPrincipal claimsPrincipal) { } + + public static long Id(this ClaimsPrincipal claimsPrincipal) { } + + public static IEnumerable Roles(this ClaimsPrincipal claimsPrincipal) where T : Enum { } + + public static T RolesFlag(this ClaimsPrincipal claimsPrincipal) where T : Enum { } +} +``` + +## DateTimeExtensions + +```cs +public static class DateTimeExtensions +{ + public static List<(DateTime, DateTime)> Chunks(this DateTime startDate, DateTime endDate, int days) { } +} +``` + +## DirectoryInfoExtensions + +```cs +public static class DirectoryInfoExtensions +{ + public static FileInfo GetFile(this DirectoryInfo directoryInfo, string name) { } +} +``` + +## EnumExtensions + +```cs +public static class EnumExtensions +{ + public static string GetDescription(this Enum value) { } + + public static string[] ToArray(this Enum value) { } +} +``` + +## ObjectExtensions + +```cs +public static class ObjectExtensions +{ + public static byte[] Bytes(this object obj) { } + + public static Dictionary Dictionary(this object obj) { } + + public static IEnumerable GetPropertiesWithAttribute(this object obj) where T : Attribute { } + + public static string Serialize(this object obj) { } + + public static void SetProperty(this object obj, string name, object value) { } +} +``` + +## QueryableExtensions + +```cs +public static class QueryableExtensions +{ + public static IQueryable Filter(this IQueryable queryable, string property, object value) { } + + public static IQueryable Filter(this IQueryable queryable, string property, string comparison, object value) { } + + public static IQueryable Order(this IQueryable queryable, string property, bool ascending) { } + + public static IQueryable Page(this IQueryable queryable, int index, int size) { } +} +``` + +## StreamExtensions + +```cs +public static class StreamExtensions +{ + public static string ToBase64String(this Stream stream) { } +} +``` + +## StringExtensions + +```cs +public static class StringExtensions +{ + public static T Deserialize(this string value) where T : class { } + + public static string ExtractNumbers(this string value) { } + + public static bool IsDate(this string value) { } + + public static bool IsEmail(this string value) { } + + public static bool IsInteger(this string value) { } + + public static bool IsLogin(this string value) { } + + public static bool IsNumber(this string value) { } + + public static bool IsPassword(this string value) { } + + public static bool IsTime(this string value) { } + + public static bool IsUrl(this string value) { } + + public static string RemoveAccents(this string value) { } + + public static string RemoveDuplicateSpaces(this string value) { } + + public static string RemoveSpecialCharacters(this string value) { } + + public static string Sanitize(this string value) { } +} +``` diff --git a/source/IoC/DotNetCore.IoC.csproj b/source/IoC/DotNetCore.IoC.csproj new file mode 100644 index 00000000..2c5f49d9 --- /dev/null +++ b/source/IoC/DotNetCore.IoC.csproj @@ -0,0 +1,18 @@ + + + DotNetCore.IoC + DotNetCore.IoC + DotNetCore.IoC + https://github.com/rafaelfgx/DotNetCore/tree/main/source/IoC + DI, DependencyInjection, IoC, InversionOfControl + DotNetCore.IoC + + + + + + + + + + diff --git a/source/IoC/Extensions.cs b/source/IoC/Extensions.cs new file mode 100644 index 00000000..6c97895c --- /dev/null +++ b/source/IoC/Extensions.cs @@ -0,0 +1,39 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyModel; +using Scrutor; +using System.Reflection; + +namespace DotNetCore.IoC; + +public static class Extensions +{ + public static T AddAppSettings(this IServiceCollection services) where T : class + { + var appSettings = new ConfigurationBuilder().Configuration().Get(); + + services.AddSingleton(appSettings); + + return appSettings; + } + + public static T AddAppSettings(this IServiceCollection services, string section) where T : class + { + var appSettings = new ConfigurationBuilder().Configuration().GetSection(section).Get(); + + services.AddSingleton(appSettings); + + return appSettings; + } + + public static void AddClassesMatchingInterfaces(this IServiceCollection services, string @namespace) + { + var assemblies = DependencyContext.Default.GetDefaultAssemblyNames().Where(assembly => assembly.FullName.StartsWith(@namespace)).Select(Assembly.Load); + + services.Scan(scan => scan.FromAssemblies(assemblies).AddClasses().UsingRegistrationStrategy(RegistrationStrategy.Skip).AsMatchingInterface().WithScopedLifetime()); + } + + public static IConfigurationRoot Configuration(this IConfigurationBuilder configuration) => configuration.AddJsonFile("AppSettings.json", false, true).AddEnvironmentVariables().Build(); + + public static string GetConnectionString(this IServiceCollection services, string name) => services.BuildServiceProvider().GetRequiredService().GetConnectionString(name); +} diff --git a/source/IoC/readme.md b/source/IoC/readme.md new file mode 100644 index 00000000..f4ba0dc6 --- /dev/null +++ b/source/IoC/readme.md @@ -0,0 +1,18 @@ +# DotNetCore.IoC + +## Extensions + +```cs +public static class Extensions +{ + public static T AddAppSettings(this IServiceCollection services) where T : class { } + + public static T AddAppSettings(this IServiceCollection services, string section) where T : class { } + + public static void AddClassesMatchingInterfaces(this IServiceCollection services, string @namespace) { } + + public static IConfigurationRoot Configuration(this IConfigurationBuilder configuration) { } + + public static string GetConnectionString(this IServiceCollection services, string name) { } +} +``` diff --git a/source/Logging/DotNetCore.Logging.csproj b/source/Logging/DotNetCore.Logging.csproj new file mode 100644 index 00000000..9c93494a --- /dev/null +++ b/source/Logging/DotNetCore.Logging.csproj @@ -0,0 +1,21 @@ + + + DotNetCore.Logging + DotNetCore.Logging + DotNetCore.Logging + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Logging + Log, Logging, Logger + DotNetCore.Logging + + + + + + + + + + + + + diff --git a/source/Logging/Extensions.cs b/source/Logging/Extensions.cs new file mode 100644 index 00000000..ff3d5c8c --- /dev/null +++ b/source/Logging/Extensions.cs @@ -0,0 +1,31 @@ +using DotNetCore.IoC; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Serilog; + +namespace DotNetCore.Logging; + +public static class Extensions +{ + public static void Log(this Microsoft.Extensions.Logging.ILogger logger, HttpResponseMessage response) => logger.Log + ( + response.IsSuccessStatusCode ? LogLevel.Information : LogLevel.Error, + "{Url} {Method} {Request} {Response}", + response.RequestMessage?.RequestUri, + response.RequestMessage?.Method, + response.RequestMessage?.Content?.ReadAsStringAsync().Result, + response.Content.ReadAsStringAsync().Result + ); + + public static IHostBuilder UseSerilog(this IHostBuilder builder) + { + var configuration = new ConfigurationBuilder().Configuration(); + + Serilog.Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger(); + + SerilogHostBuilderExtensions.UseSerilog(builder); + + return builder; + } +} diff --git a/source/Logging/readme.md b/source/Logging/readme.md new file mode 100644 index 00000000..115fbdc8 --- /dev/null +++ b/source/Logging/readme.md @@ -0,0 +1,12 @@ +# DotNetCore.Logging + +## Extensions + +```cs +public static class Extensions +{ + public static void Log(this ILogger logger, HttpResponseMessage response) { } + + public static IHostBuilder UseSerilog(this IHostBuilder builder) { } +} +``` diff --git a/source/Mapping/DotNetCore.Mapping.csproj b/source/Mapping/DotNetCore.Mapping.csproj new file mode 100644 index 00000000..c15f5825 --- /dev/null +++ b/source/Mapping/DotNetCore.Mapping.csproj @@ -0,0 +1,13 @@ + + + DotNetCore.Mapping + DotNetCore.Mapping + DotNetCore.Mapping + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Mapping + Map, Mapping, Mapper, Clone, Merge, ObjectMap, ObjectMapping, ObjectMapper, ObjectClone, ObjectMerge + DotNetCore.Mapping + + + + + diff --git a/source/Mapping/Extensions.cs b/source/Mapping/Extensions.cs new file mode 100644 index 00000000..9f0a9dd3 --- /dev/null +++ b/source/Mapping/Extensions.cs @@ -0,0 +1,16 @@ +using AgileObjects.AgileMapper; + +namespace DotNetCore.Mapping; + +public static class Extensions +{ + public static TSource Clone(this TSource source) => source is null ? default : Mapper.DeepClone(source); + + public static TDestination Map(this TSource source) => source is null ? default : Mapper.Map(source).ToANew(); + + public static TDestination Map(this object source) => source is null ? default : Mapper.Map(source).ToANew(); + + public static TDestination Map(this TSource source, TDestination destination) => Mapper.Map(source).OnTo(destination); + + public static IQueryable Project(this IQueryable queryable) => queryable.Project().To(); +} diff --git a/source/Mapping/readme.md b/source/Mapping/readme.md new file mode 100644 index 00000000..8ef1cc00 --- /dev/null +++ b/source/Mapping/readme.md @@ -0,0 +1,18 @@ +# DotNetCore.Mapping + +## Extensions + +```cs +public static class Extensions +{ + public static TSource Clone(this TSource source) { } + + public static TDestination Map(this TSource source) { } + + public static TDestination Map(this object source) { } + + public static TDestination Map(this TSource source, TDestination destination) { } + + public static IQueryable Project(this IQueryable queryable) { } +} +``` diff --git a/source/Mediator/DotNetCore.Mediator.csproj b/source/Mediator/DotNetCore.Mediator.csproj new file mode 100644 index 00000000..00f0bf50 --- /dev/null +++ b/source/Mediator/DotNetCore.Mediator.csproj @@ -0,0 +1,18 @@ + + + DotNetCore.Mediator + DotNetCore.Mediator + DotNetCore.Mediator + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Mediator + Mediator, MediatorPattern, Pattern, CQRS, Command, CommandHandler, Query, QueryHandler, Handler, Request, Response + DotNetCore.Mediator + + + + + + + + + + diff --git a/source/Mediator/Extensions.cs b/source/Mediator/Extensions.cs new file mode 100644 index 00000000..192723f7 --- /dev/null +++ b/source/Mediator/Extensions.cs @@ -0,0 +1,28 @@ +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyModel; +using System.Reflection; + +namespace DotNetCore.Mediator; + +public static class Extensions +{ + public static void AddMediator(this IServiceCollection services, string @namespace) + { + services.AddScoped(); + + var assemblies = DependencyContext.Default!.GetDefaultAssemblyNames().Where(assembly => assembly.FullName.StartsWith(@namespace)).Select(Assembly.Load); + + var types = assemblies.SelectMany(assembly => assembly.GetTypes()).ToList(); + + types.Where(type => type.GetInterfaces().Any(IsHandler)).ToList().ForEach(type => type.GetInterfaces().Where(IsHandler).ToList().ForEach(@interface => services.AddScoped(@interface, type))); + + types.Where(type => IsType(type.BaseType, typeof(AbstractValidator<>))).ToList().ForEach(type => services.AddSingleton(type.BaseType!, type)); + + return; + + static bool IsHandler(Type type) => IsType(type, typeof(IHandler<>)) || IsType(type, typeof(IHandler<,>)); + + static bool IsType(Type type, MemberInfo memberInfo) => type is not null && type.IsGenericType && type.GetGenericTypeDefinition() == memberInfo; + } +} diff --git a/source/Mediator/IHandler.cs b/source/Mediator/IHandler.cs new file mode 100644 index 00000000..d1808587 --- /dev/null +++ b/source/Mediator/IHandler.cs @@ -0,0 +1,13 @@ +using DotNetCore.Results; + +namespace DotNetCore.Mediator; + +public interface IHandler +{ + Task HandleAsync(TRequest request); +} + +public interface IHandler +{ + Task> HandleAsync(TRequest request); +} diff --git a/source/Mediator/IMediator.cs b/source/Mediator/IMediator.cs new file mode 100644 index 00000000..80963db0 --- /dev/null +++ b/source/Mediator/IMediator.cs @@ -0,0 +1,10 @@ +using DotNetCore.Results; + +namespace DotNetCore.Mediator; + +public interface IMediator +{ + Task HandleAsync(TRequest request); + + Task> HandleAsync(TRequest request); +} diff --git a/source/Mediator/Mediator.cs b/source/Mediator/Mediator.cs new file mode 100644 index 00000000..d6cdc9b4 --- /dev/null +++ b/source/Mediator/Mediator.cs @@ -0,0 +1,42 @@ +using DotNetCore.Results; +using FluentValidation; +using Microsoft.Extensions.DependencyInjection; +using System.Net; + +namespace DotNetCore.Mediator; + +public sealed class Mediator(IServiceProvider serviceProvider) : IMediator +{ + public async Task HandleAsync(TRequest request) + { + var (valid, message) = Validate(request); + + if (!valid) return new Result(HttpStatusCode.BadRequest, message); + + var handler = serviceProvider.GetRequiredService>(); + + return await handler.HandleAsync(request).ConfigureAwait(false); + } + + public async Task> HandleAsync(TRequest request) + { + var (valid, message) = Validate(request); + + if (!valid) return new Result(HttpStatusCode.BadRequest, message); + + var handler = serviceProvider.GetRequiredService>(); + + return await handler.HandleAsync(request).ConfigureAwait(false); + } + + private Tuple Validate(TRequest request) + { + var validator = serviceProvider.GetService>(); + + if (validator is null) return new Tuple(true, default); + + var validation = validator.Validate(request); + + return new Tuple(validation.IsValid, validation.ToString()); + } +} diff --git a/source/Mediator/readme.md b/source/Mediator/readme.md new file mode 100644 index 00000000..8b84afb8 --- /dev/null +++ b/source/Mediator/readme.md @@ -0,0 +1,63 @@ +# DotNetCore.Mediator + +The smallest, simplest and fastest implementation of the mediator pattern. + +## ASP.NET Core Startup Example + +```cs +public sealed class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddMediator("Namespace"); + } +} +``` + +## ASP.NET Core Controller Example + +```cs +public sealed class Controller : ControllerBase +{ + private readonly IMediator _mediator; + + public Controller(IMediator mediator) + { + _mediator = mediator; + } +} +``` + +## Mediator + +```cs +public interface IMediator +{ + Task HandleAsync(TRequest request); + + Task> HandleAsync(TRequest request); +} +``` + +```cs +public sealed class Mediator : IMediator +{ + public async Task HandleAsync(TRequest request) { } + + public async Task> HandleAsync(TRequest request) { } +} +``` + +```cs +public interface IHandler +{ + Task HandleAsync(TRequest request); +} +``` + +```cs +public interface IHandler +{ + Task> HandleAsync(TRequest request); +} +``` diff --git a/source/MongoDB/Context/IMongoContext.cs b/source/MongoDB/Context/IMongoContext.cs new file mode 100644 index 00000000..847d12df --- /dev/null +++ b/source/MongoDB/Context/IMongoContext.cs @@ -0,0 +1,8 @@ +using MongoDB.Driver; + +namespace DotNetCore.MongoDB; + +public interface IMongoContext +{ + IMongoDatabase Database { get; } +} diff --git a/source/MongoDB/Context/MongoContext.cs b/source/MongoDB/Context/MongoContext.cs new file mode 100644 index 00000000..65925b59 --- /dev/null +++ b/source/MongoDB/Context/MongoContext.cs @@ -0,0 +1,8 @@ +using MongoDB.Driver; + +namespace DotNetCore.MongoDB; + +public abstract class MongoContext(string connectionString) : IMongoContext +{ + public IMongoDatabase Database { get; } = new MongoClient(connectionString).GetDatabase(new MongoUrl(connectionString).DatabaseName); +} diff --git a/source/MongoDB/Document/Document.cs b/source/MongoDB/Document/Document.cs new file mode 100644 index 00000000..66323f2a --- /dev/null +++ b/source/MongoDB/Document/Document.cs @@ -0,0 +1,12 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace DotNetCore.MongoDB; + +public abstract class Document : IDocument +{ + [BsonExtraElements] + public BsonDocument ExtraElements { get; set; } + + public ObjectId Id { get; set; } +} diff --git a/source/MongoDB/Document/IDocument.cs b/source/MongoDB/Document/IDocument.cs new file mode 100644 index 00000000..9393844e --- /dev/null +++ b/source/MongoDB/Document/IDocument.cs @@ -0,0 +1,8 @@ +using MongoDB.Bson; + +namespace DotNetCore.MongoDB; + +public interface IDocument +{ + ObjectId Id { get; set; } +} diff --git a/source/MongoDB/DotNetCore.MongoDB.csproj b/source/MongoDB/DotNetCore.MongoDB.csproj new file mode 100644 index 00000000..ce4791ed --- /dev/null +++ b/source/MongoDB/DotNetCore.MongoDB.csproj @@ -0,0 +1,16 @@ + + + DotNetCore.MongoDB + DotNetCore.MongoDB + DotNetCore.MongoDB + https://github.com/rafaelfgx/DotNetCore/tree/main/source/MongoDB + ORM, ObjectRelationalMapping, Mongo MongoDB, Context, Repository, GenericRepository, BaseRepository, RepositoryBase, CommandRepository, QueryRepository, CQRS, Command, Query + DotNetCore.MongoDB + + + + + + + + diff --git a/source/MongoDB/Filters.cs b/source/MongoDB/Filters.cs new file mode 100644 index 00000000..9a492dcc --- /dev/null +++ b/source/MongoDB/Filters.cs @@ -0,0 +1,8 @@ +using MongoDB.Driver; + +namespace DotNetCore.MongoDB; + +public static class Filters +{ + public static FilterDefinition Id(object value) => Builders.Filter.Eq(nameof(Id), value); +} diff --git a/source/MongoDB/Repository/MongoCommandRepository.cs b/source/MongoDB/Repository/MongoCommandRepository.cs new file mode 100644 index 00000000..e2b50ba3 --- /dev/null +++ b/source/MongoDB/Repository/MongoCommandRepository.cs @@ -0,0 +1,42 @@ +using DotNetCore.Repositories; +using MongoDB.Driver; +using System.Linq.Expressions; + +namespace DotNetCore.MongoDB; + +public class MongoCommandRepository(IMongoContext context) : ICommandRepository where T : class +{ + private readonly IMongoCollection _collection = context.Database.GetCollection(typeof(T).Name); + + public void Add(T item) => _collection.InsertOne(item); + + public Task AddAsync(T item) => _collection.InsertOneAsync(item); + + public void AddRange(IEnumerable items) => _collection.InsertMany(items); + + public Task AddRangeAsync(IEnumerable items) => _collection.InsertManyAsync(items); + + public void Delete(object key) => _collection.DeleteOne(Filters.Id(key)); + + public void Delete(Expression> where) => _collection.DeleteMany(where); + + public Task DeleteAsync(object key) => _collection.DeleteOneAsync(Filters.Id(key)); + + public Task DeleteAsync(Expression> where) => _collection.DeleteManyAsync(where); + + public void Update(T item) => _collection.ReplaceOne(Filters.Id(GetKey(item)), item); + + public Task UpdateAsync(T item) => _collection.ReplaceOneAsync(Filters.Id(GetKey(item)), item); + + public void UpdatePartial(object item) => _collection.ReplaceOne(Filters.Id(GetKey(item)), item as T); + + public Task UpdatePartialAsync(object item) => _collection.ReplaceOneAsync(Filters.Id(GetKey(item)), item as T); + + public void UpdateRange(IEnumerable items) => _collection.BulkWrite(WriteModels(items)); + + public Task UpdateRangeAsync(IEnumerable items) => _collection.BulkWriteAsync(WriteModels(items)); + + private static IEnumerable> WriteModels(IEnumerable items) => items.Select(item => new ReplaceOneModel(Filters.Id(GetKey(item)), item) { IsUpsert = true }); + + private static object GetKey(object item) => item.GetType().GetProperty("Id")?.GetValue(item, default); +} diff --git a/source/MongoDB/Repository/MongoQueryRepository.cs b/source/MongoDB/Repository/MongoQueryRepository.cs new file mode 100644 index 00000000..5817e6fd --- /dev/null +++ b/source/MongoDB/Repository/MongoQueryRepository.cs @@ -0,0 +1,37 @@ +using DotNetCore.Repositories; +using MongoDB.Driver; +using MongoDB.Driver.Linq; +using System.Linq.Expressions; + +namespace DotNetCore.MongoDB; + +public class MongoQueryRepository(IMongoContext context) : IQueryRepository where T : class +{ + private readonly IMongoCollection _collection = context.Database.GetCollection(typeof(T).Name); + + public IQueryable Queryable => _collection.AsQueryable(); + + public bool Any() => Queryable.Any(); + + public bool Any(Expression> where) => Queryable.Where(where).Any(); + + public Task AnyAsync() => Queryable.AnyAsync(); + + public Task AnyAsync(Expression> where) => Queryable.Where(where).AnyAsync(); + + public long Count() => Queryable.LongCount(); + + public long Count(Expression> where) => Queryable.Where(where).LongCount(); + + public Task CountAsync() => Queryable.LongCountAsync(); + + public Task CountAsync(Expression> where) => Queryable.Where(where).LongCountAsync(); + + public T Get(object key) => _collection.Find(Filters.Id(key)).SingleOrDefault(); + + public Task GetAsync(object key) => _collection.Find(Filters.Id(key)).SingleOrDefaultAsync(); + + public IEnumerable List() => Queryable.ToList(); + + public async Task> ListAsync() => await Queryable.ToListAsync().ConfigureAwait(false); +} diff --git a/source/MongoDB/Repository/MongoRepository.cs b/source/MongoDB/Repository/MongoRepository.cs new file mode 100644 index 00000000..aedbf0a8 --- /dev/null +++ b/source/MongoDB/Repository/MongoRepository.cs @@ -0,0 +1,5 @@ +using DotNetCore.Repositories; + +namespace DotNetCore.MongoDB; + +public class MongoRepository(IMongoContext context) : Repository(new MongoCommandRepository(context), new MongoQueryRepository(context)) where T : class; diff --git a/source/MongoDB/readme.md b/source/MongoDB/readme.md new file mode 100644 index 00000000..d4e271af --- /dev/null +++ b/source/MongoDB/readme.md @@ -0,0 +1,127 @@ +# DotNetCore.MongoDB + +## Context + +### IMongoContext + +```cs +public interface IMongoContext +{ + IMongoDatabase Database { get; } +} +``` + +### MongoContext + +```cs +public abstract class MongoContext : IMongoContext +{ + public MongoContext(string connectionString) { } + + public IMongoDatabase Database { get; } +} +``` + +## Document + +### IDocument + +```cs +public interface IDocument +{ + ObjectId Id { get; set; } +} +``` + +### Document + +```cs +public abstract class Document : IDocument +{ + [BsonExtraElements] + public BsonDocument ExtraElements { get; set; } + + public ObjectId Id { get; set; } +} +``` + +## MongoCommandRepository + +```cs +public class MongoCommandRepository : ICommandRepository where T : class +{ + public MongoCommandRepository(IMongoContext context) { } + + public void Add(T item) { } + + public Task AddAsync(T item) { } + + public void AddRange(IEnumerable items) { } + + public Task AddRangeAsync(IEnumerable items) { } + + public void Delete(object key) { } + + public void Delete(Expression> where) { } + + public Task DeleteAsync(object key) { } + + public Task DeleteAsync(Expression> where) { } + + public void Update(T item) { } + + public Task UpdateAsync(T item) { } + + public void UpdatePartial(object item) { } + + public Task UpdatePartialAsync(object item) { } + + public void UpdateRange(IEnumerable items) { } + + public Task UpdateRangeAsync(IEnumerable items) { } +} +``` + +## MongoQueryRepository + +```cs +public class MongoQueryRepository : IQueryRepository where T : class +{ + public MongoQueryRepository(IMongoContext context) { } + + public IQueryable Queryable { get; }; + + public bool Any() { } + + public bool Any(Expression> where) { } + + public Task AnyAsync() { } + + public Task AnyAsync(Expression> where) { } + + public long Count() { } + + public long Count(Expression> where) { } + + public Task CountAsync() { } + + public Task CountAsync(Expression> where) { } + + public T Get(object key) { } + + public Task GetAsync(object key) { } + + public IEnumerable List() { } + + public async Task> ListAsync() { } +} +``` + +## MongoRepository + +```cs +public class MongoRepository : Repository where T : class +{ + public MongoRepository(IMongoContext context) : base(new MongoCommandRepository(context), new MongoQueryRepository(context)) { } +} +``` diff --git a/source/Objects/DotNetCore.Objects.csproj b/source/Objects/DotNetCore.Objects.csproj new file mode 100644 index 00000000..7029d927 --- /dev/null +++ b/source/Objects/DotNetCore.Objects.csproj @@ -0,0 +1,13 @@ + + + DotNetCore.Objects + DotNetCore.Objects + DotNetCore.Objects + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Objects + Object, DTO, DataTransferObject + DotNetCore.Objects + + + + + diff --git a/source/Objects/File/BinaryFile.cs b/source/Objects/File/BinaryFile.cs new file mode 100644 index 00000000..7b1fd4e9 --- /dev/null +++ b/source/Objects/File/BinaryFile.cs @@ -0,0 +1,44 @@ +using DotNetCore.Extensions; + +namespace DotNetCore.Objects; + +public class BinaryFile(Guid id, string name, byte[] bytes, long length, string contentType) +{ + public Guid Id { get; } = id; + + public string Name { get; } = name; + + public byte[] Bytes { get; private set; } = bytes; + + public long Length { get; } = length; + + public string ContentType { get; } = contentType; + + public static async Task ReadAsync(string directory, Guid id) + { + if (!Directory.Exists(directory) || id == Guid.Empty) return null; + + var file = new DirectoryInfo(directory).GetFile(id.ToString()); + + if (file is null) return null; + + var bytes = await File.ReadAllBytesAsync(file.FullName).ConfigureAwait(false); + + return new BinaryFile(id, file.Name, bytes, file.Length, file.Extension); + } + + public async Task SaveAsync(string directory) + { + if (string.IsNullOrWhiteSpace(directory) || string.IsNullOrWhiteSpace(Name) || Bytes is null || Bytes.LongLength == 0) return; + + Directory.CreateDirectory(directory); + + var name = string.Concat(Id, Path.GetExtension(Name)); + + var path = Path.Combine(directory, name); + + await File.WriteAllBytesAsync(path, Bytes).ConfigureAwait(false); + + Bytes = default; + } +} diff --git a/source/Objects/File/BinaryFileExtensions.cs b/source/Objects/File/BinaryFileExtensions.cs new file mode 100644 index 00000000..2bc240ef --- /dev/null +++ b/source/Objects/File/BinaryFileExtensions.cs @@ -0,0 +1,15 @@ +namespace DotNetCore.Objects; + +public static class BinaryFileExtensions +{ + public static async Task> SaveAsync(this IEnumerable files, string directory) + { + if (string.IsNullOrWhiteSpace(directory)) return null; + + var binaryFiles = files as BinaryFile[] ?? files.ToArray(); + + await Task.WhenAll(binaryFiles.Select(file => file.SaveAsync(directory))); + + return binaryFiles; + } +} diff --git a/source/Objects/Grid/Filter.cs b/source/Objects/Grid/Filter.cs new file mode 100644 index 00000000..b64d4140 --- /dev/null +++ b/source/Objects/Grid/Filter.cs @@ -0,0 +1,10 @@ +namespace DotNetCore.Objects; + +public record Filter +{ + public string Property { get; set; } + + public string Comparison { get; set; } + + public string Value { get; set; } +} diff --git a/source/Objects/Grid/Filters.cs b/source/Objects/Grid/Filters.cs new file mode 100644 index 00000000..e72e5ce7 --- /dev/null +++ b/source/Objects/Grid/Filters.cs @@ -0,0 +1,3 @@ +namespace DotNetCore.Objects; + +public sealed class Filters : List; diff --git a/source/Objects/Grid/Grid.cs b/source/Objects/Grid/Grid.cs new file mode 100644 index 00000000..405149f2 --- /dev/null +++ b/source/Objects/Grid/Grid.cs @@ -0,0 +1,35 @@ +using DotNetCore.Extensions; + +namespace DotNetCore.Objects; + +public record Grid +{ + public Grid(IQueryable queryable, GridParameters parameters) + { + Parameters = parameters; + + if (queryable is null || parameters is null) return; + + queryable = Filter(queryable, parameters.Filters); + + Count = queryable.LongCount(); + + queryable = Order(queryable, parameters.Order); + + queryable = Page(queryable, parameters.Page); + + List = queryable.AsEnumerable(); + } + + public long Count { get; } + + public IEnumerable List { get; } + + public GridParameters Parameters { get; } + + private static IQueryable Filter(IQueryable queryable, Filters filters) => filters is null ? queryable : filters.Aggregate(queryable, (current, filter) => current.Filter(filter.Property, filter.Comparison, filter.Value)); + + private static IQueryable Order(IQueryable queryable, Order order) => order is null ? queryable : queryable.Order(order.Property, order.Ascending); + + private static IQueryable Page(IQueryable queryable, Page page) => page is null ? queryable : queryable.Page(page.Index, page.Size); +} diff --git a/source/Objects/Grid/GridExtensions.cs b/source/Objects/Grid/GridExtensions.cs new file mode 100644 index 00000000..78b7432f --- /dev/null +++ b/source/Objects/Grid/GridExtensions.cs @@ -0,0 +1,13 @@ +namespace DotNetCore.Objects; + +public static class GridExtensions +{ + public static Grid Grid(this IQueryable queryable, GridParameters parameters) + { + var grid = new Grid(queryable, parameters); + + return grid.List.Any() ? grid : default; + } + + public static Task> GridAsync(this IQueryable queryable, GridParameters parameters) => Task.FromResult(Grid(queryable, parameters)); +} diff --git a/source/Objects/Grid/GridParameters.cs b/source/Objects/Grid/GridParameters.cs new file mode 100644 index 00000000..7da9f8ef --- /dev/null +++ b/source/Objects/Grid/GridParameters.cs @@ -0,0 +1,10 @@ +namespace DotNetCore.Objects; + +public record GridParameters +{ + public Filters Filters { get; set; } + + public Order Order { get; set; } + + public Page Page { get; set; } +} diff --git a/source/Objects/Grid/Order.cs b/source/Objects/Grid/Order.cs new file mode 100644 index 00000000..d06c7cc3 --- /dev/null +++ b/source/Objects/Grid/Order.cs @@ -0,0 +1,8 @@ +namespace DotNetCore.Objects; + +public record Order +{ + public bool Ascending { get; set; } = true; + + public string Property { get; set; } +} diff --git a/source/Objects/Grid/Page.cs b/source/Objects/Grid/Page.cs new file mode 100644 index 00000000..fdb9aaf8 --- /dev/null +++ b/source/Objects/Grid/Page.cs @@ -0,0 +1,8 @@ +namespace DotNetCore.Objects; + +public record Page +{ + public int Index { get; set; } = 1; + + public int Size { get; set; } +} diff --git a/source/Objects/readme.md b/source/Objects/readme.md new file mode 100644 index 00000000..921003d8 --- /dev/null +++ b/source/Objects/readme.md @@ -0,0 +1,113 @@ +# DotNetCore.Objects + +## BinaryFile + +```cs +public class BinaryFile +{ + public BinaryFile(Guid id, string name, byte[] bytes, long length, string contentType) { } + + public Guid Id { get; } + + public string Name { get; } + + public byte[] Bytes { get; private set; } + + public long Length { get; } + + public string ContentType { get; } + + public static async Task ReadAsync(string directory, Guid id) { } + + public async Task SaveAsync(string directory) { } +} +``` + +## BinaryFileExtensions + +```cs +public static class BinaryFileExtensions +{ + public static async Task> SaveAsync(this IEnumerable files, string directory) { } +} +``` + +## Filter + +```cs +public record Filter +{ + public string Property { get; set; } + + public string Comparison { get; set; } + + public string Value { get; set; } +} +``` + +## Filters + +```cs +public class Filters : List { } +``` + +## Order + +```cs +public record Order +{ + public bool Ascending { get; set; } = true; + + public string Property { get; set; } +} +``` + +## Page + +```cs +public record Page +{ + public int Index { get; set; } = 1; + + public int Size { get; set; } +} +``` + +## GridParameters + +```cs +public record GridParameters +{ + public Filters Filters { get; set; } + + public Order Order { get; set; } + + public Page Page { get; set; } +} +``` + +## Grid + +```cs +public record Grid +{ + public Grid(IQueryable queryable, GridParameters parameters) { } + + public long Count { get; } + + public IEnumerable List { get; } + + public GridParameters Parameters { get; } +} +``` + +## GridExtensions + +```cs +public static class GridExtensions +{ + public static Grid Grid(this IQueryable queryable, GridParameters parameters) { } + + public static Task> GridAsync(this IQueryable queryable, GridParameters parameters) { } +} +``` diff --git a/source/RabbitMQ/Connection.cs b/source/RabbitMQ/Connection.cs new file mode 100644 index 00000000..0a83d3a1 --- /dev/null +++ b/source/RabbitMQ/Connection.cs @@ -0,0 +1,3 @@ +namespace DotNetCore.RabbitMQ; + +public sealed record Connection(string HostName, int Port, string UserName, string Password); diff --git a/source/RabbitMQ/DotNetCore.RabbitMQ.csproj b/source/RabbitMQ/DotNetCore.RabbitMQ.csproj new file mode 100644 index 00000000..9359ebd1 --- /dev/null +++ b/source/RabbitMQ/DotNetCore.RabbitMQ.csproj @@ -0,0 +1,16 @@ + + + DotNetCore.RabbitMQ + DotNetCore.RabbitMQ + DotNetCore.RabbitMQ + https://github.com/rafaelfgx/DotNetCore/tree/main/source/RabbitMQ + RabbitMQ, Queue, Broker, MessageBroker + DotNetCore.RabbitMQ + + + + + + + + diff --git a/source/RabbitMQ/IQueue.cs b/source/RabbitMQ/IQueue.cs new file mode 100644 index 00000000..f29ef279 --- /dev/null +++ b/source/RabbitMQ/IQueue.cs @@ -0,0 +1,8 @@ +namespace DotNetCore.RabbitMQ; + +public interface IQueue +{ + Task PublishAsync(T obj); + + Task SubscribeAsync(Func action); +} diff --git a/source/RabbitMQ/Queue.cs b/source/RabbitMQ/Queue.cs new file mode 100644 index 00000000..218a5aec --- /dev/null +++ b/source/RabbitMQ/Queue.cs @@ -0,0 +1,53 @@ +using DotNetCore.Extensions; +using RabbitMQ.Client; +using RabbitMQ.Client.Events; + +namespace DotNetCore.RabbitMQ; + +public abstract class Queue(Connection connection) : IQueue +{ + private readonly ConnectionFactory _connectionFactory = new() + { + HostName = connection.HostName, + Port = connection.Port, + UserName = connection.UserName, + Password = connection.Password + }; + + public async Task PublishAsync(T obj) + { + await using var connection = await _connectionFactory.CreateConnectionAsync(); + + await using var channel = await connection.CreateChannelAsync(); + + await QueueDeclareAsync(channel); + + await channel.BasicPublishAsync(exchange: string.Empty, routingKey: typeof(T).Name, body: obj.Bytes()); + } + + public async Task SubscribeAsync(Func action) + { + await using var connection = await _connectionFactory.CreateConnectionAsync(); + + await using var channel = await connection.CreateChannelAsync(); + + await QueueDeclareAsync(channel); + + var consumer = new AsyncEventingBasicConsumer(channel); + + consumer.ReceivedAsync += (_, args) => action(args.Body.ToArray().Object()); + + await channel.BasicConsumeAsync(queue: typeof(T).Name, autoAck: true, consumer: consumer); + + var autoResetEvent = new AutoResetEvent(false); + + Console.CancelKeyPress += (_, args) => { autoResetEvent.Set(); args.Cancel = true; }; + + autoResetEvent.WaitOne(); + } + + private static async Task QueueDeclareAsync(IChannel channel) + { + await channel.QueueDeclareAsync(queue: typeof(T).Name, durable: true, exclusive: false, autoDelete: false); + } +} diff --git a/source/RabbitMQ/readme.md b/source/RabbitMQ/readme.md new file mode 100644 index 00000000..1e9c7d64 --- /dev/null +++ b/source/RabbitMQ/readme.md @@ -0,0 +1,70 @@ +# DotNetCore.RabbitMQ + +## Connection + +```cs +public sealed record Connection(string HostName, int Port, string UserName, string Password); +``` + +## IQueue + +```cs +public interface IQueue +{ + Task PublishAsync(T obj); + + Task SubscribeAsync(Func action); +} +``` + +## Queue + +```cs +public abstract class Queue : IQueue +{ + protected Queue(Connection connection) { } + + public async Task PublishAsync(T obj) { } + + public async Task SubscribeAsync(Func action) { } +} +``` + +## Example + +### Message + +```cs +public sealed record Product(string Name); +``` + +### Queue + +```cs +public interface IProductQueue : IQueue { } +``` + +```cs +public class ProductQueue : Queue, IProductQueue +{ + public ProductQueue(Connection connection) : base(connection) { } +} +``` + +### Publisher + +```cs +var product = new Product("Product"); + +IProductQueue productQueue = new ProductQueue(new Connection("localhost", 5672, "admin", "P4ssW0rd!")); + +productQueue.PublishAsync(product).Wait(); +``` + +### Subscriber + +```cs +IProductQueue productQueue = new ProductQueue(new Connection("localhost", 5672, "admin", "P4ssW0rd!")); + +productQueue.SubscribeAsync(product => Handle(product)).Wait(); +``` diff --git a/source/Repositories/DotNetCore.Repositories.csproj b/source/Repositories/DotNetCore.Repositories.csproj new file mode 100644 index 00000000..1369095a --- /dev/null +++ b/source/Repositories/DotNetCore.Repositories.csproj @@ -0,0 +1,10 @@ + + + DotNetCore.Repositories + DotNetCore.Repositories + DotNetCore.Repositories + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Repositories + Repository, GenericRepository, BaseRepository, RepositoryBase, CommandRepository, QueryRepository, CQRS, Command, Query + DotNetCore.Repositories + + diff --git a/source/Repositories/ICommandRepository.cs b/source/Repositories/ICommandRepository.cs new file mode 100644 index 00000000..6adbb9b1 --- /dev/null +++ b/source/Repositories/ICommandRepository.cs @@ -0,0 +1,34 @@ +using System.Linq.Expressions; + +namespace DotNetCore.Repositories; + +public interface ICommandRepository where T : class +{ + void Add(T item); + + Task AddAsync(T item); + + void AddRange(IEnumerable items); + + Task AddRangeAsync(IEnumerable items); + + void Delete(object key); + + void Delete(Expression> where); + + Task DeleteAsync(object key); + + Task DeleteAsync(Expression> where); + + void Update(T item); + + Task UpdateAsync(T item); + + void UpdatePartial(object item); + + Task UpdatePartialAsync(object item); + + void UpdateRange(IEnumerable items); + + Task UpdateRangeAsync(IEnumerable items); +} diff --git a/source/Repositories/IQueryRepository.cs b/source/Repositories/IQueryRepository.cs new file mode 100644 index 00000000..6235eeb2 --- /dev/null +++ b/source/Repositories/IQueryRepository.cs @@ -0,0 +1,32 @@ +using System.Linq.Expressions; + +namespace DotNetCore.Repositories; + +public interface IQueryRepository where T : class +{ + IQueryable Queryable { get; } + + bool Any(); + + bool Any(Expression> where); + + Task AnyAsync(); + + Task AnyAsync(Expression> where); + + long Count(); + + long Count(Expression> where); + + Task CountAsync(); + + Task CountAsync(Expression> where); + + T Get(object key); + + Task GetAsync(object key); + + IEnumerable List(); + + Task> ListAsync(); +} diff --git a/source/Repositories/IRepository.cs b/source/Repositories/IRepository.cs new file mode 100644 index 00000000..45c43308 --- /dev/null +++ b/source/Repositories/IRepository.cs @@ -0,0 +1,3 @@ +namespace DotNetCore.Repositories; + +public interface IRepository : ICommandRepository, IQueryRepository where T : class; diff --git a/source/Repositories/Repository.cs b/source/Repositories/Repository.cs new file mode 100644 index 00000000..7457d08a --- /dev/null +++ b/source/Repositories/Repository.cs @@ -0,0 +1,60 @@ +using System.Linq.Expressions; + +namespace DotNetCore.Repositories; + +public abstract class Repository(ICommandRepository commandRepository, IQueryRepository queryRepository) : IRepository where T : class +{ + public IQueryable Queryable => queryRepository.Queryable; + + public void Add(T item) => commandRepository.Add(item); + + public Task AddAsync(T item) => commandRepository.AddAsync(item); + + public void AddRange(IEnumerable items) => commandRepository.AddRange(items); + + public Task AddRangeAsync(IEnumerable items) => commandRepository.AddRangeAsync(items); + + public bool Any() => queryRepository.Any(); + + public bool Any(Expression> where) => queryRepository.Any(where); + + public Task AnyAsync() => queryRepository.AnyAsync(); + + public Task AnyAsync(Expression> where) => queryRepository.AnyAsync(where); + + public long Count() => queryRepository.Count(); + + public long Count(Expression> where) => queryRepository.Count(where); + + public Task CountAsync() => queryRepository.CountAsync(); + + public Task CountAsync(Expression> where) => queryRepository.CountAsync(where); + + public void Delete(object key) => commandRepository.Delete(key); + + public void Delete(Expression> where) => commandRepository.Delete(where); + + public Task DeleteAsync(object key) => commandRepository.DeleteAsync(key); + + public Task DeleteAsync(Expression> where) => commandRepository.DeleteAsync(where); + + public T Get(object key) => queryRepository.Get(key); + + public Task GetAsync(object key) => queryRepository.GetAsync(key); + + public IEnumerable List() => queryRepository.List(); + + public Task> ListAsync() => queryRepository.ListAsync(); + + public void Update(T item) => commandRepository.Update(item); + + public Task UpdateAsync(T item) => commandRepository.UpdateAsync(item); + + public void UpdatePartial(object item) => commandRepository.UpdatePartial(item); + + public Task UpdatePartialAsync(object item) => commandRepository.UpdatePartialAsync(item); + + public void UpdateRange(IEnumerable items) => commandRepository.UpdateRange(items); + + public Task UpdateRangeAsync(IEnumerable items) => commandRepository.UpdateRangeAsync(items); +} diff --git a/source/Repositories/readme.md b/source/Repositories/readme.md new file mode 100644 index 00000000..4bdfa54b --- /dev/null +++ b/source/Repositories/readme.md @@ -0,0 +1,140 @@ +# DotNetCore.Repositories + +## Repository + +### ICommandRepository + +```cs +public interface ICommandRepository where T : class +{ + void Add(T item); + + Task AddAsync(T item); + + void AddRange(IEnumerable items); + + Task AddRangeAsync(IEnumerable items); + + void Delete(object key); + + void Delete(Expression> where); + + Task DeleteAsync(object key); + + Task DeleteAsync(Expression> where); + + void Update(T item); + + Task UpdateAsync(T item); + + void UpdatePartial(object item); + + Task UpdatePartialAsync(object item); + + void UpdateRange(IEnumerable items); + + Task UpdateRangeAsync(IEnumerable items); +} +``` + +### IQueryRepository + +```cs +public interface IQueryRepository where T : class +{ + IQueryable Queryable { get; } + + bool Any(); + + bool Any(Expression> where); + + Task AnyAsync(); + + Task AnyAsync(Expression> where); + + long Count(); + + long Count(Expression> where); + + Task CountAsync(); + + Task CountAsync(Expression> where); + + T Get(object key); + + Task GetAsync(object key); + + IEnumerable List(); + + Task> ListAsync(); +} +``` + +### IRepository + +```cs +public interface IRepository : ICommandRepository, IQueryRepository where T : class { } +``` + +### Repository + +```cs +public abstract class Repository : IRepository where T : class +{ + protected Repository(ICommandRepository commandRepository, IQueryRepository queryRepository) { } + + public IQueryable Queryable { get; }; + + public void Add(T item) { } + + public Task AddAsync(T item) { } + + public void AddRange(IEnumerable items) { } + + public Task AddRangeAsync(IEnumerable items) { } + + public bool Any() { } + + public bool Any(Expression> where) { } + + public Task AnyAsync() { } + + public Task AnyAsync(Expression> where) { } + + public long Count() { } + + public long Count(Expression> where) { } + + public Task CountAsync() { } + + public Task CountAsync(Expression> where) { } + + public void Delete(object key) { } + + public void Delete(Expression> where) { } + + public Task DeleteAsync(object key) { } + + public Task DeleteAsync(Expression> where) { } + + public T Get(object key) { } + + public Task GetAsync(object key) { } + + public IEnumerable List() { } + + public Task> ListAsync() { } + + public void Update(T item) { } + + public Task UpdateAsync(T item) { } + + public void UpdatePartial(object item) { } + + public Task UpdatePartialAsync(object item) { } + + public void UpdateRange(IEnumerable items) { } + + public Task UpdateRangeAsync(IEnumerable items) { } +} +``` diff --git a/source/Results/DotNetCore.Results.csproj b/source/Results/DotNetCore.Results.csproj new file mode 100644 index 00000000..3e03390f --- /dev/null +++ b/source/Results/DotNetCore.Results.csproj @@ -0,0 +1,10 @@ + + + DotNetCore.Results + DotNetCore.Results + DotNetCore.Results + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Results + Result, ResultPattern, Pattern + DotNetCore.Results + + diff --git a/source/Results/Result.cs b/source/Results/Result.cs new file mode 100644 index 00000000..88a05f95 --- /dev/null +++ b/source/Results/Result.cs @@ -0,0 +1,29 @@ +using System.Net; + +namespace DotNetCore.Results; + +public record Result(HttpStatusCode Status) +{ + public Result(HttpStatusCode status, string message) : this(status) => Message = message; + + public string Message { get; } + + public bool HasMessage => !string.IsNullOrWhiteSpace(Message); + + public Result Convert() => new(Status, Message); +} + +public sealed record Result : Result +{ + public Result(HttpStatusCode status) : base(status) { } + + public Result(HttpStatusCode status, T value) : base(status) { Value = value; } + + public Result(HttpStatusCode status, string message) : base(status, message) { } + + public T Value { get; } + + public bool HasValue => Value is not null; + + public Result Convert() => new(Status, Message); +} diff --git a/source/Results/readme.md b/source/Results/readme.md new file mode 100644 index 00000000..a8299c6f --- /dev/null +++ b/source/Results/readme.md @@ -0,0 +1,35 @@ +# DotNetCore.Results + +## Result + +```cs +public record Result(HttpStatusCode Status) +{ + public Result(HttpStatusCode status, string message) { } + + public string Message { get; } + + public bool HasMessage { } + + public Result Convert() { } +} +``` + +## Result + +```cs +public sealed record Result : Result +{ + public Result(HttpStatusCode status) { } + + public Result(HttpStatusCode status, T value) { } + + public Result(HttpStatusCode status, string message) { } + + public T Value { get; } + + public bool HasValue { } + + public Result Convert() { } +} +``` diff --git a/source/Security/Cryptography/CryptographyService.cs b/source/Security/Cryptography/CryptographyService.cs new file mode 100644 index 00000000..f3f39998 --- /dev/null +++ b/source/Security/Cryptography/CryptographyService.cs @@ -0,0 +1,39 @@ +using System.Security.Cryptography; +using System.Text; + +namespace DotNetCore.Security; + +public class CryptographyService(string key) : ICryptographyService +{ + public string Decrypt(string value, string salt) + { + using var algorithm = Algorithm(salt); + + return Encoding.Default.GetString(Transform(Convert.FromBase64String(value), algorithm.CreateDecryptor())); + } + + public string Encrypt(string value, string salt) + { + using var algorithm = Algorithm(salt); + + return Convert.ToBase64String(Transform(Encoding.Default.GetBytes(value), algorithm.CreateEncryptor())); + } + + private static byte[] Transform(byte[] bytes, ICryptoTransform cryptoTransform) + { + using (cryptoTransform) { return cryptoTransform.TransformFinalBlock(bytes, 0, bytes.Length); } + } + + private SymmetricAlgorithm Algorithm(string salt) + { + using var key1 = KeyGenerator.Generate(key, salt); + + var algorithm = Aes.Create(); + + algorithm.Key = key1.GetBytes(algorithm.KeySize / 8); + + algorithm.IV = key1.GetBytes(algorithm.BlockSize / 8); + + return algorithm; + } +} diff --git a/source/Security/Cryptography/ICryptographyService.cs b/source/Security/Cryptography/ICryptographyService.cs new file mode 100644 index 00000000..db5bc1df --- /dev/null +++ b/source/Security/Cryptography/ICryptographyService.cs @@ -0,0 +1,8 @@ +namespace DotNetCore.Security; + +public interface ICryptographyService +{ + string Decrypt(string value, string salt); + + string Encrypt(string value, string salt); +} diff --git a/source/Security/DotNetCore.Security.csproj b/source/Security/DotNetCore.Security.csproj new file mode 100644 index 00000000..de8a507f --- /dev/null +++ b/source/Security/DotNetCore.Security.csproj @@ -0,0 +1,13 @@ + + + DotNetCore.Security + DotNetCore.Security + DotNetCore.Security + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Security + Security, Cryptography, Hash, JWT + DotNetCore.Security + + + + + diff --git a/source/Security/Extensions.cs b/source/Security/Extensions.cs new file mode 100644 index 00000000..5e57cbb8 --- /dev/null +++ b/source/Security/Extensions.cs @@ -0,0 +1,10 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace DotNetCore.Security; + +public static class Extensions +{ + public static void AddCryptographyService(this IServiceCollection services, string key) => services.AddSingleton(_ => new CryptographyService(key)); + + public static void AddHashService(this IServiceCollection services) => services.AddSingleton(); +} diff --git a/source/Security/Hash/HashService.cs b/source/Security/Hash/HashService.cs new file mode 100644 index 00000000..dee4e56b --- /dev/null +++ b/source/Security/Hash/HashService.cs @@ -0,0 +1,13 @@ +namespace DotNetCore.Security; + +public class HashService : IHashService +{ + public string Create(string value, string salt) + { + using var key = KeyGenerator.Generate(value, salt); + + return Convert.ToBase64String(key.GetBytes(512)); + } + + public bool Validate(string hash, string value, string salt) => hash == Create(value, salt); +} diff --git a/source/Security/Hash/IHashService.cs b/source/Security/Hash/IHashService.cs new file mode 100644 index 00000000..b69f6f52 --- /dev/null +++ b/source/Security/Hash/IHashService.cs @@ -0,0 +1,8 @@ +namespace DotNetCore.Security; + +public interface IHashService +{ + string Create(string value, string salt); + + bool Validate(string hash, string value, string salt); +} diff --git a/source/Security/KeyGenerator.cs b/source/Security/KeyGenerator.cs new file mode 100644 index 00000000..11a9edf7 --- /dev/null +++ b/source/Security/KeyGenerator.cs @@ -0,0 +1,9 @@ +using System.Security.Cryptography; +using System.Text; + +namespace DotNetCore.Security; + +public static class KeyGenerator +{ + public static DeriveBytes Generate(string password, string salt) => new Rfc2898DeriveBytes(password, Encoding.Default.GetBytes(salt), 10000, HashAlgorithmName.SHA512); +} diff --git a/source/Security/readme.md b/source/Security/readme.md new file mode 100644 index 00000000..5fd51853 --- /dev/null +++ b/source/Security/readme.md @@ -0,0 +1,62 @@ +# DotNetCore.Security + +## Cryptography + +### ICryptographyService + +```cs +public interface ICryptographyService +{ + string Decrypt(string value, string salt); + + string Encrypt(string value, string salt); +} +``` + +### CryptographyService + +```cs +public class CryptographyService : ICryptographyService +{ + public CryptographyService(string key) { } + + public string Decrypt(string value, string salt) { } + + public string Encrypt(string value, string salt) { } +} +``` + +## Hash + +### IHashService + +```cs +public interface IHashService +{ + string Create(string value, string salt); + + bool Validate(string hash, string value, string salt); +} +``` + +### HashService + +```cs +public class HashService : IHashService +{ + public string Create(string value, string salt) { } + + public bool Validate(string hash, string value, string salt) { } +} +``` + +## Extensions + +```cs +public static class Extensions +{ + public static void AddCryptographyService(this IServiceCollection services, string key) { } + + public static void AddHashService(this IServiceCollection services) { } +} +``` diff --git a/source/Services/Csv/CsvService.cs b/source/Services/Csv/CsvService.cs new file mode 100644 index 00000000..6eb438fc --- /dev/null +++ b/source/Services/Csv/CsvService.cs @@ -0,0 +1,77 @@ +using System.Globalization; + +namespace DotNetCore.Services; + +public class CsvService : ICsvService +{ + public async Task> ReadAsync(string path, char separator = ',') where T : new() + { + if (!File.Exists(path)) return []; + + var lines = await File.ReadAllLinesAsync(path); + + if (lines.Length < 2) return []; + + var headers = lines.First().Split(separator); + + if (!headers.Any()) return []; + + var items = new List(); + + foreach (var line in lines.Skip(1)) + { + var values = line.Split(separator); + + var item = new T(); + + for (var i = 0; i < headers.Length; i++) + { + var property = item.GetType().GetProperty(headers[i]); + + if (property is null) continue; + + var type = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType; + + var value = Convert.ChangeType(values[i], type, CultureInfo.InvariantCulture); + + property.SetValue(item, value); + } + + items.Add(item); + } + + return items; + } + + public async Task WriteAsync(IEnumerable items, string path, char separator = ',') + { + using var stream = await WriteAsync(items); + + await File.WriteAllBytesAsync(path, stream.ToArray()); + } + + public async Task WriteAsync(IEnumerable items, char separator = ',') + { + var enumerable = items.ToList(); + + if (!enumerable.Any()) return new MemoryStream(); + + var properties = typeof(T).GetProperties(); + + var memoryStream = new MemoryStream(); + + await using var streamWriter = new StreamWriter(memoryStream); + + await streamWriter.WriteLineAsync(string.Join(separator, properties.Select(property => property.Name))); + + enumerable.ForEach(item => streamWriter.WriteLine(string.Join(separator, properties.Select(property => property.GetValue(item))))); + + await streamWriter.FlushAsync(); + + await memoryStream.FlushAsync(); + + memoryStream.Position = 0; + + return memoryStream; + } +} diff --git a/source/Services/Csv/ICsvService.cs b/source/Services/Csv/ICsvService.cs new file mode 100644 index 00000000..d317bc3b --- /dev/null +++ b/source/Services/Csv/ICsvService.cs @@ -0,0 +1,10 @@ +namespace DotNetCore.Services; + +public interface ICsvService +{ + Task> ReadAsync(string path, char separator = ',') where T : new(); + + Task WriteAsync(IEnumerable items, string path, char separator = ','); + + Task WriteAsync(IEnumerable items, char separator = ','); +} diff --git a/source/Services/DotNetCore.Services.csproj b/source/Services/DotNetCore.Services.csproj new file mode 100644 index 00000000..be196ae8 --- /dev/null +++ b/source/Services/DotNetCore.Services.csproj @@ -0,0 +1,15 @@ + + + DotNetCore.Services + DotNetCore.Services + DotNetCore.Services + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Services + Services + DotNetCore.Services + + + + + + + diff --git a/source/Services/Extensions.cs b/source/Services/Extensions.cs new file mode 100644 index 00000000..82b5c02b --- /dev/null +++ b/source/Services/Extensions.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; + +namespace DotNetCore.Services; + +public static class Extensions +{ + public static void AddCsvService(this IServiceCollection services) => services.AddSingleton(); + + public static void AddFileCache(this IServiceCollection services) => services.AddSingleton(); + + public static void AddJsonStringLocalizer(this IServiceCollection services) => services.AddSingleton(); +} diff --git a/source/Services/FileCache/FileCache.cs b/source/Services/FileCache/FileCache.cs new file mode 100644 index 00000000..b54ea660 --- /dev/null +++ b/source/Services/FileCache/FileCache.cs @@ -0,0 +1,60 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DotNetCore.Services; + +public sealed class FileCache : IFileCache +{ + private readonly Lock _lock = new(); + + public void Clear(string file) => File.Delete(file); + + public T Set(string file, TimeSpan expiration, T value) + { + lock (_lock) + { + var content = new FileCacheContent(value, DateTime.UtcNow.Add(expiration)); + + var options = new JsonSerializerOptions { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; + + File.WriteAllText(file, JsonSerializer.Serialize(content, options)); + + return value; + } + } + + public bool TryGetValue(string file, out T value) + { + lock (_lock) + { + if (!File.Exists(file)) + { + value = default; + + return false; + } + + var content = JsonSerializer.Deserialize>(File.ReadAllText(file)); + + if (content is null) + { + value = default; + + return false; + } + + if (DateTime.UtcNow >= content.Expiration) + { + File.Delete(file); + + value = default; + + return false; + } + + value = content.Value; + + return true; + } + } +} diff --git a/source/Services/FileCache/FileCacheContent.cs b/source/Services/FileCache/FileCacheContent.cs new file mode 100644 index 00000000..809e322f --- /dev/null +++ b/source/Services/FileCache/FileCacheContent.cs @@ -0,0 +1,3 @@ +namespace DotNetCore.Services; + +public sealed record FileCacheContent(T Value, DateTime Expiration); diff --git a/source/Services/FileCache/IFileCache.cs b/source/Services/FileCache/IFileCache.cs new file mode 100644 index 00000000..e3f3ecfb --- /dev/null +++ b/source/Services/FileCache/IFileCache.cs @@ -0,0 +1,10 @@ +namespace DotNetCore.Services; + +public interface IFileCache +{ + void Clear(string file); + + T Set(string file, TimeSpan expiration, T value); + + bool TryGetValue(string file, out T value); +} diff --git a/source/Services/Http/HttpOptions.cs b/source/Services/Http/HttpOptions.cs new file mode 100644 index 00000000..3fce7969 --- /dev/null +++ b/source/Services/Http/HttpOptions.cs @@ -0,0 +1,16 @@ +using System.Net.Http.Headers; + +namespace DotNetCore.Services; + +public sealed record HttpOptions +{ + public string BaseAddress { get; set; } + + public AuthenticationHeaderValue Authentication { get; set; } + + public int TimeoutSeconds { get; set; } = 5; + + public int RetryCount { get; set; } + + public int RetrySeconds { get; set; } +} diff --git a/source/Services/Http/HttpService.cs b/source/Services/Http/HttpService.cs new file mode 100644 index 00000000..001b18ed --- /dev/null +++ b/source/Services/Http/HttpService.cs @@ -0,0 +1,66 @@ +using Polly; +using Polly.Retry; +using System.Net; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Net.Mime; + +namespace DotNetCore.Services; + +public abstract class HttpService : IHttpService +{ + private readonly HttpClient _client; + + private readonly AsyncRetryPolicy _retryPolicy; + + protected HttpService(HttpOptions options) + { + _retryPolicy = Policy.Handle().OrResult(response => !response.IsSuccessStatusCode).WaitAndRetryAsync(options.RetryCount, _ => TimeSpan.FromSeconds(options.RetrySeconds)); + + _client = new HttpClient { Timeout = TimeSpan.FromSeconds(options.TimeoutSeconds) }; + + _client.DefaultRequestHeaders.Clear(); + + _client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(nameof(HttpService), string.Empty)); + + _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json)); + + if (options.BaseAddress is not null) _client.BaseAddress = new Uri(options.BaseAddress); + + if (options.Authentication is not null) _client.DefaultRequestHeaders.Authorization = options.Authentication; + } + + public async Task DeleteAsync(string uri) + { + return (await _retryPolicy.ExecuteAsync(() => _client.DeleteAsync(uri))).StatusCode; + } + + public async Task> GetAsync(string uri) + { + var response = await _retryPolicy.ExecuteAsync(() => _client.GetAsync(uri)); + + return new Tuple(response.StatusCode, await response.Content.ReadFromJsonAsync()); + } + + public async Task PatchAsync(string uri, object value) + { + return (await _retryPolicy.ExecuteAsync(() => _client.PatchAsJsonAsync(uri, value))).StatusCode; + } + + public async Task PostAsync(string uri, object value) + { + return (await _retryPolicy.ExecuteAsync(() => _client.PostAsJsonAsync(uri, value))).StatusCode; + } + + public async Task> PostAsync(string uri, object value) + { + var response = await _retryPolicy.ExecuteAsync(() => _client.PostAsJsonAsync(uri, value)); + + return new Tuple(response.StatusCode, await response.Content.ReadFromJsonAsync()); + } + + public async Task PutAsync(string uri, object value) + { + return (await _retryPolicy.ExecuteAsync(() => _client.PutAsJsonAsync(uri, value))).StatusCode; + } +} diff --git a/source/Services/Http/IHttpService.cs b/source/Services/Http/IHttpService.cs new file mode 100644 index 00000000..ee2324a9 --- /dev/null +++ b/source/Services/Http/IHttpService.cs @@ -0,0 +1,18 @@ +using System.Net; + +namespace DotNetCore.Services; + +public interface IHttpService +{ + Task DeleteAsync(string uri); + + Task> GetAsync(string uri); + + Task PatchAsync(string uri, object value); + + Task PostAsync(string uri, object value); + + Task> PostAsync(string uri, object value); + + Task PutAsync(string uri, object value); +} diff --git a/source/Services/JsonStringLocalizer.cs b/source/Services/JsonStringLocalizer.cs new file mode 100644 index 00000000..e9f7c265 --- /dev/null +++ b/source/Services/JsonStringLocalizer.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Localization; +using System.Collections.Concurrent; +using System.Globalization; +using System.Text.Json; + +namespace DotNetCore.Services; + +public class JsonStringLocalizer : IStringLocalizer +{ + private static ConcurrentDictionary> Strings => JsonSerializer.Deserialize>>(File.ReadAllText("AppStrings.json")); + + public LocalizedString this[string name] => Get(name); + + public LocalizedString this[string name, params object[] arguments] => Get(name, arguments); + + public IEnumerable GetAllStrings(bool includeParentCultures) => Strings.Keys.Select(key => new LocalizedString(key, key)); + + private static LocalizedString Get(string name, params object[] arguments) + { + var localizedString = new LocalizedString(name, name, true, nameof(JsonStringLocalizer)); + + return string.IsNullOrWhiteSpace(name) || !Strings.TryGetValue(name, out var dictionary) + ? localizedString + : !dictionary.TryGetValue(CultureInfo.CurrentCulture.Name, out var value) + ? localizedString + : new LocalizedString(name, string.Format(value, arguments), false, nameof(JsonStringLocalizer)); + } +} diff --git a/source/Services/readme.md b/source/Services/readme.md new file mode 100644 index 00000000..c50f1c8a --- /dev/null +++ b/source/Services/readme.md @@ -0,0 +1,218 @@ +# DotNetCore.Services + +## CsvService + +### ICsvService + +```cs +public interface ICsvService +{ + Task> ReadAsync(string path, char separator = ',') where T : new(); + + Task WriteAsync(IEnumerable items, string path, char separator = ','); + + Task WriteAsync(IEnumerable items, char separator = ','); +} +``` + +### CsvService + +```cs +public class CsvService : ICsvService +{ + public async Task> ReadAsync(string path, char separator = ',') where T : new() { } + + public async Task WriteAsync(IEnumerable items, string path, char separator = ',') { } + + public async Task WriteAsync(IEnumerable items, char separator = ',') { } +} +``` + +### Example + +```cs +public sealed record Person +{ + public int Id { get; set; } + + public string Name { get; set; } +} +``` + +```cs +public static void Main() +{ + var people = new List(); + + var stream = WriteAsync(people).Result; + + WriteAsync(people, "People.csv").Wait(); + + people = ReadAsync("People.csv").Result; +} +``` + +## FileCache + +### IFileCache + +```cs +public interface IFileCache +{ + void Clear(string file); + + T Set(string file, TimeSpan expiration, T value); + + bool TryGetValue(string file, out T value); +} +``` + +### FileCache + +```cs +public sealed class FileCache : IFileCache +{ + public void Clear(string file) { } + + public T Set(string file, TimeSpan expiration, T value) { } + + public bool TryGetValue(string file, out T value) { } +} +``` + +## Http + +### HttpOptions + +```cs +public sealed record HttpOptions +{ + public string BaseAddress { get; set; } + + public AuthenticationHeaderValue Authentication { get; set; } + + public int TimeoutSeconds { get; set; } = 5; + + public int RetryCount { get; set; } + + public int RetrySeconds { get; set; } +} +``` + +### IHttpService + +```cs +public interface IHttpService +{ + Task DeleteAsync(string uri); + + Task> GetAsync(string uri); + + Task PatchAsync(string uri, object value); + + Task PostAsync(string uri, object value); + + Task> PostAsync(string uri, object value); + + Task PutAsync(string uri, object value); +} +``` + +### HttpService + +```cs +public abstract class HttpService : IHttpService +{ + public async Task DeleteAsync(string uri) { } + + public async Task> GetAsync(string uri) { } + + public async Task PatchAsync(string uri, object value) { } + + public async Task PostAsync(string uri, object value) { } + + public async Task> PostAsync(string uri, object value) { } + + public async Task PutAsync(string uri, object value) { } +} +``` + +### Example + +```cs +public interface ITestHttpService : IHttpService { } +``` + +```cs +public sealed record TestHttpService(HttpOptions options) : HttpService(options), ITestHttpService; +``` + +```cs +public sealed record Todo(int Id, string Title); +``` + +```cs +public class Program +{ + public static void Main() + { + var baseAddress = "https://jsonplaceholder.typicode.com"; + + var options = new HttpOptions { BaseAddress = baseAddress }; + + ITestHttpService httpService = new TestHttpService(options); + + var services = new ServiceCollection(); + + services.AddScoped(provider => httpService); + + httpService = services.BuildServiceProvider().GetRequiredService(); + + var deleteError = httpService.DeleteAsync("todo/1").Result; + + var deleteSuccess = httpService.DeleteAsync("todos/1").Result; + + var listError = httpService.GetAsync>("todo").Result; + + var listSuccess = httpService.GetAsync>("todos").Result; + + var getError = httpService.GetAsync("todo/1").Result; + + var getSuccess = httpService.GetAsync("todos/1").Result; + + var postError = httpService.PostAsync("todo", default).Result; + + var postSuccess = httpService.PostAsync("todos", new { Title = "Title" }).Result; + + var postResultError = httpService.PostAsync("todo", default).Result; + + var postResultSuccess = httpService.PostAsync("todos", new { Title = "Title" }).Result; + + var putError = httpService.PutAsync("todo/1", default).Result; + + var putSuccess = httpService.PutAsync("todos/1", new { Title = "Title" }).Result; + } +} +``` + +## JsonStringLocalizer + +```cs +public class JsonStringLocalizer : IStringLocalizer +{ + public JsonStringLocalizer(string path) { } +} +``` + +## Extensions + +```cs +public static class Extensions +{ + public static void AddCsvService(this IServiceCollection services) { } + + public static void AddFileCache(this IServiceCollection services) { } + + public static void AddJsonStringLocalizer(this IServiceCollection services) { } +} +``` diff --git a/source/Validation/DotNetCore.Validation.csproj b/source/Validation/DotNetCore.Validation.csproj new file mode 100644 index 00000000..9c91bd69 --- /dev/null +++ b/source/Validation/DotNetCore.Validation.csproj @@ -0,0 +1,13 @@ + + + DotNetCore.Validation + DotNetCore.Validation + DotNetCore.Validation + https://github.com/rafaelfgx/DotNetCore/tree/main/source/Validation + Validation, Validator + DotNetCore.Validation + + + + + \ No newline at end of file diff --git a/source/Validation/Extensions.cs b/source/Validation/Extensions.cs new file mode 100644 index 00000000..488f30e4 --- /dev/null +++ b/source/Validation/Extensions.cs @@ -0,0 +1,15 @@ +using FluentValidation; + +namespace DotNetCore.Validation; + +public static class Extensions +{ + public static Tuple Validation(this IValidator validator, T instance) + { + if (instance is null) return new Tuple(false, default); + + var result = validator.Validate(instance); + + return new Tuple(result.IsValid, result.ToString()); + } +} diff --git a/source/Validation/readme.md b/source/Validation/readme.md new file mode 100644 index 00000000..7d622938 --- /dev/null +++ b/source/Validation/readme.md @@ -0,0 +1,10 @@ +# DotNetCore.Validation + +## Extensions + +```cs +public static class Extensions +{ + public static Tuple Validation(this IValidator validator, T instance) { } +} +``` diff --git a/source/global.json b/source/global.json new file mode 100644 index 00000000..3e31c807 --- /dev/null +++ b/source/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.100", + "rollForward": "latestMajor" + } +}