diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5860940..35bb743a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,9 +30,9 @@ jobs: name: ubuntu-latest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: 'Cache: .nuke/temp, ~/.nuget/packages' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | .nuke/temp diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 32821b42..58770454 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -1,22 +1,74 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/build", - "title": "Build Schema", + "properties": { + "CI": { + "type": "boolean" + }, + "Configuration": { + "type": "string", + "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", + "enum": [ + "Debug", + "Release" + ] + }, + "NUGETAPIKEY": { + "type": "string", + "default": "Secrets must be entered via 'nuke :secrets [profile]'" + }, + "NuGetApiUrl": { + "type": "string" + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + } + }, "definitions": { - "build": { - "type": "object", + "Host": { + "type": "string", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string", + "enum": [ + "Clean", + "Compile", + "Pack", + "Print", + "Print_Net_SDK", + "Push", + "Restore" + ] + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "NukeBuild": { "properties": { - "CI": { - "type": "boolean" - }, - "Configuration": { - "type": "string", - "description": "Configuration to build - Default is 'Debug' (local) or 'Release' (server)", - "enum": [ - "Debug", - "Release" - ] - }, "Continue": { "type": "boolean", "description": "Indicates to continue a previously failed build attempt" @@ -26,37 +78,13 @@ "description": "Shows the help text for this build assembly" }, "Host": { - "type": "string", "description": "Host for execution. Default is 'automatic'", - "enum": [ - "AppVeyor", - "AzurePipelines", - "Bamboo", - "Bitbucket", - "Bitrise", - "GitHubActions", - "GitLab", - "Jenkins", - "Rider", - "SpaceAutomation", - "TeamCity", - "Terminal", - "TravisCI", - "VisualStudio", - "VSCode" - ] + "$ref": "#/definitions/Host" }, "NoLogo": { "type": "boolean", "description": "Disables displaying the NUKE logo" }, - "NUGETAPIKEY": { - "type": "string", - "default": "Secrets must be entered via 'nuke :secrets [profile]'" - }, - "NuGetApiUrl": { - "type": "string" - }, "Partition": { "type": "string", "description": "Partition to use on CI" @@ -80,49 +108,22 @@ "type": "array", "description": "List of targets to be skipped. Empty list skips all dependencies", "items": { - "type": "string", - "enum": [ - "Clean", - "Compile", - "Pack", - "Print", - "Print_Net_SDK", - "Push", - "Restore" - ] + "$ref": "#/definitions/ExecutableTarget" } }, - "Solution": { - "type": "string", - "description": "Path to a solution file that is automatically loaded" - }, "Target": { "type": "array", "description": "List of targets to be invoked. Default is '{default_target}'", "items": { - "type": "string", - "enum": [ - "Clean", - "Compile", - "Pack", - "Print", - "Print_Net_SDK", - "Push", - "Restore" - ] + "$ref": "#/definitions/ExecutableTarget" } }, "Verbosity": { - "type": "string", "description": "Logging verbosity during build execution. Default is 'Normal'", - "enum": [ - "Minimal", - "Normal", - "Quiet", - "Verbose" - ] + "$ref": "#/definitions/Verbosity" } } } - } + }, + "$ref": "#/definitions/NukeBuild" } diff --git a/Build/Build.cs b/Build/Build.cs index cbde9cd6..87fee437 100644 --- a/Build/Build.cs +++ b/Build/Build.cs @@ -135,7 +135,7 @@ protected override void OnBuildInitialized() { Log.Information("Generating NuGet packages for projects in solution"); int commitNum = 0; - string NuGetVersionCustom = "2.0.0.885"; + string NuGetVersionCustom = "2.1.0"; //if it's not a tagged release - append the commit number to the package version @@ -483,6 +483,168 @@ protected override void OnBuildInitialized() .SetOutputDirectory(Directory_NuGet) .EnableNoBuild() .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.Caching") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.Caching").Path.ToString()) + .SetPackageTags("RCommon Caching abstractions") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.Persistence.Caching") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.Persistence.Caching").Path.ToString()) + .SetPackageTags("RCommon Persistence Caching Data Cache") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.Persistence.Caching.MemoryCache") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.Persistence.Caching.MemoryCache").Path.ToString()) + .SetPackageTags("RCommon Persistence Caching Memory InMemory") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.Persistence.Caching.RedisCache") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.Persistence.Caching.RedisCache").Path.ToString()) + .SetPackageTags("RCommon Persistence Caching Redis Cache StackExchange") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.RedisCache") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.RedisCache").Path.ToString()) + .SetPackageTags("RCommon Caching Redis Cache StackExchange") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.MemoryCache") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.MemoryCache").Path.ToString()) + .SetPackageTags("RCommon Caching Memory Cache InMemory Distributed Memory") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.Json") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.Json").Path.ToString()) + .SetPackageTags("RCommon Json serialization abstractions") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.JsonNet") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.JsonNet").Path.ToString()) + .SetPackageTags("RCommon Newtonsoft Json.NET serilization ") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); + DotNetTasks + .DotNetPack(_ => _ + .SetPackageId("RCommon.SystemTextJson") + .SetProject(projects.FirstOrDefault(x => x.Name == "RCommon.SystemTextJson").Path.ToString()) + .SetPackageTags("RCommon System.Text.Json serialization") + .SetDescription("A cohesive set of infrastructure libraries for dotnet that utilizes abstractions for event handling persistence unit of work mediator distributed messaging event bus CQRS email and more") + .SetConfiguration(Configuration) + .SetCopyright(Copyright) + .SetAuthors("Jason Webb") + .SetPackageIconUrl("https://avatars.githubusercontent.com/u/96881178?s=200&v=4") + .SetRepositoryUrl("https://github.com/RCommon-Team/RCommon") + .SetPackageProjectUrl("https://rcommon.com") + .SetPackageLicenseUrl("https://licenses.nuget.org/Apache-2.0") + .SetVersion(NuGetVersionCustom) + .SetNoDependencies(true) + .SetOutputDirectory(Directory_NuGet) + .EnableNoBuild() + .EnableNoRestore()); }); diff --git a/Build/Build.csproj b/Build/Build.csproj index 22b49174..e2ec3fd9 100644 --- a/Build/Build.csproj +++ b/Build/Build.csproj @@ -14,8 +14,8 @@ - - + + diff --git a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/ITestApplicationService.cs b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/ITestApplicationService.cs index 67131c6e..969c1c70 100644 --- a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/ITestApplicationService.cs +++ b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/ITestApplicationService.cs @@ -1,4 +1,4 @@ -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Models.ExecutionResults; namespace Examples.ApplicationServices.CQRS { diff --git a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/Program.cs b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/Program.cs index 3a9745f1..3c176004 100644 --- a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/Program.cs +++ b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/Program.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Hosting; using RCommon; using RCommon.ApplicationServices; -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Caching; using RCommon.FluentValidation; using System.Diagnostics; using System.Reflection; @@ -32,6 +32,7 @@ // Or this way which uses a little magic but is simple cqrs.AddCommandHandlers((typeof(Program).GetTypeInfo().Assembly)); cqrs.AddQueryHandlers((typeof(Program).GetTypeInfo().Assembly)); + cqrs.AddMemoryCachingForHandlers(); }) .WithValidation(validation => { diff --git a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestApplicationService.cs b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestApplicationService.cs index f0102cab..758f4fb9 100644 --- a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestApplicationService.cs +++ b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestApplicationService.cs @@ -1,6 +1,6 @@ using RCommon.ApplicationServices.Commands; -using RCommon.ApplicationServices.ExecutionResults; using RCommon.ApplicationServices.Queries; +using RCommon.Models.ExecutionResults; using System; using System.Collections.Generic; using System.Linq; diff --git a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommand.cs b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommand.cs index 7df5d764..e3e3dd2e 100644 --- a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommand.cs +++ b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommand.cs @@ -1,5 +1,5 @@ -using RCommon.ApplicationServices.Commands; -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Models.Commands; +using RCommon.Models.ExecutionResults; using System; using System.Collections.Generic; using System.Linq; diff --git a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommandHandler.cs b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommandHandler.cs index 22433823..03a96122 100644 --- a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommandHandler.cs +++ b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestCommandHandler.cs @@ -1,6 +1,6 @@ using RCommon; using RCommon.ApplicationServices.Commands; -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Models.ExecutionResults; using System; using System.Collections.Generic; using System.Linq; diff --git a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestQuery.cs b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestQuery.cs index 497134ce..cd621cec 100644 --- a/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestQuery.cs +++ b/Examples/ApplicationServices/Examples.ApplicationServices.CQRS/TestQuery.cs @@ -1,4 +1,4 @@ -using RCommon.ApplicationServices.Queries; +using RCommon.Models.Queries; using System; using System.Collections.Generic; using System.Linq; diff --git a/Examples/Caching/Examples.Caching.MemoryCaching/ConfigurationContainer.cs b/Examples/Caching/Examples.Caching.MemoryCaching/ConfigurationContainer.cs new file mode 100644 index 00000000..78baccdc --- /dev/null +++ b/Examples/Caching/Examples.Caching.MemoryCaching/ConfigurationContainer.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Caching.MemoryCaching +{ + internal static class ConfigurationContainer + { + public static IConfiguration Configuration { get; set; } + } +} diff --git a/Examples/Caching/Examples.Caching.MemoryCaching/Examples.Caching.MemoryCaching.csproj b/Examples/Caching/Examples.Caching.MemoryCaching/Examples.Caching.MemoryCaching.csproj new file mode 100644 index 00000000..50f73b04 --- /dev/null +++ b/Examples/Caching/Examples.Caching.MemoryCaching/Examples.Caching.MemoryCaching.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/Examples/Caching/Examples.Caching.MemoryCaching/ITestApplicationService.cs b/Examples/Caching/Examples.Caching.MemoryCaching/ITestApplicationService.cs new file mode 100644 index 00000000..f836e032 --- /dev/null +++ b/Examples/Caching/Examples.Caching.MemoryCaching/ITestApplicationService.cs @@ -0,0 +1,11 @@ + +namespace Examples.Caching.MemoryCaching +{ + public interface ITestApplicationService + { + TestDto GetDistributedMemoryCache(string key); + TestDto GetMemoryCache(string key); + void SetDistributedMemoryCache(string key, Type type, object data); + void SetMemoryCache(string key, TestDto data); + } +} \ No newline at end of file diff --git a/Examples/Caching/Examples.Caching.MemoryCaching/Program.cs b/Examples/Caching/Examples.Caching.MemoryCaching/Program.cs new file mode 100644 index 00000000..e59be1e8 --- /dev/null +++ b/Examples/Caching/Examples.Caching.MemoryCaching/Program.cs @@ -0,0 +1,69 @@ +using Examples.Caching.MemoryCaching; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using RCommon; +using RCommon.Caching; +using RCommon.Json; +using RCommon.JsonNet; +using RCommon.MemoryCache; +using System.Diagnostics; +using System.Reflection; + +try +{ + var host = Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, builder) => + { + + ConfigurationContainer.Configuration = builder + .Build(); + }) + .ConfigureServices(services => + { + // Configure RCommon + services.AddRCommon() + .WithJsonSerialization() // Distributed memory caching requires serialization + .WithMemoryCaching(cache => + { + cache.Configure(x => + { + x.ExpirationScanFrequency = TimeSpan.FromMinutes(1); + }); + cache.CacheDynamicallyCompiledExpressions(); + }) + .WithDistributedCaching(cache => + { + cache.Configure(x => + { + x.ExpirationScanFrequency = TimeSpan.FromMinutes(1); + }); + }); + + services.AddTransient(); + + }).Build(); + + Console.WriteLine("Example Starting"); + var appService = host.Services.GetRequiredService(); + + // In Memory Cache + appService.SetMemoryCache("test-key", new TestDto("test data 1")); + var testData1 = appService.GetMemoryCache("test-key"); + + // In Memory Distributed Cache + appService.SetDistributedMemoryCache("test-key", typeof(TestDto), new TestDto("test data 2")); + var testData2 = appService.GetDistributedMemoryCache("test-key"); + + Console.WriteLine(testData1.Message); + Console.WriteLine(testData2.Message); + + Console.WriteLine("Example Complete"); + Console.ReadLine(); +} +catch (Exception ex) +{ + Console.WriteLine(ex.ToString()); + +} + diff --git a/Examples/Caching/Examples.Caching.MemoryCaching/TestApplicationService.cs b/Examples/Caching/Examples.Caching.MemoryCaching/TestApplicationService.cs new file mode 100644 index 00000000..de3218ab --- /dev/null +++ b/Examples/Caching/Examples.Caching.MemoryCaching/TestApplicationService.cs @@ -0,0 +1,46 @@ +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Caching.MemoryCaching +{ + public class TestApplicationService : ITestApplicationService + { + private readonly IMemoryCache _memoryCache; + private readonly IDistributedCache _distributedCache; + private readonly IJsonSerializer _serializer; + + public TestApplicationService(IMemoryCache memoryCache, IDistributedCache distributedCache, IJsonSerializer serializer) + { + _memoryCache = memoryCache; + _distributedCache = distributedCache; + _serializer = serializer; + } + + public void SetMemoryCache(string key, TestDto data) + { + _memoryCache.Set(key, data); + } + + public TestDto GetMemoryCache(string key) + { + return _memoryCache.Get(key); + } + + public void SetDistributedMemoryCache(string key, Type type, object data) + { + _distributedCache.Set(key, Encoding.UTF8.GetBytes(_serializer.Serialize(data, type))); + } + + public TestDto GetDistributedMemoryCache(string key) + { + var cache = _distributedCache.Get(key); + return _serializer.Deserialize(Encoding.UTF8.GetString(cache)); + } + } +} diff --git a/Examples/Caching/Examples.Caching.MemoryCaching/TestDto.cs b/Examples/Caching/Examples.Caching.MemoryCaching/TestDto.cs new file mode 100644 index 00000000..ddec0def --- /dev/null +++ b/Examples/Caching/Examples.Caching.MemoryCaching/TestDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Caching.MemoryCaching +{ + public record TestDto + { + public TestDto(string message) + { + Message = message; + } + + public string Message { get; } + } +} diff --git a/Examples/Caching/Examples.Caching.PersistenceCaching/Examples.Caching.PersistenceCaching.csproj b/Examples/Caching/Examples.Caching.PersistenceCaching/Examples.Caching.PersistenceCaching.csproj new file mode 100644 index 00000000..1f78af89 --- /dev/null +++ b/Examples/Caching/Examples.Caching.PersistenceCaching/Examples.Caching.PersistenceCaching.csproj @@ -0,0 +1,24 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + Always + + + + diff --git a/Examples/Caching/Examples.Caching.PersistenceCaching/ITestApplicationService.cs b/Examples/Caching/Examples.Caching.PersistenceCaching/ITestApplicationService.cs new file mode 100644 index 00000000..0a45dfb3 --- /dev/null +++ b/Examples/Caching/Examples.Caching.PersistenceCaching/ITestApplicationService.cs @@ -0,0 +1,9 @@ +using RCommon.TestBase.Entities; + +namespace Examples.Caching.PersistenceCaching +{ + public interface ITestApplicationService + { + Task> GetCustomers(object cacheKey); + } +} \ No newline at end of file diff --git a/Examples/Caching/Examples.Caching.PersistenceCaching/Program.cs b/Examples/Caching/Examples.Caching.PersistenceCaching/Program.cs new file mode 100644 index 00000000..5f166ccd --- /dev/null +++ b/Examples/Caching/Examples.Caching.PersistenceCaching/Program.cs @@ -0,0 +1,82 @@ + +using Examples.Caching.PersistenceCaching; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using RCommon; +using RCommon.Caching; +using RCommon.MemoryCache; +using RCommon.Persistence; +using RCommon.Persistence.Caching; +using RCommon.Persistence.Caching.MemoryCache; +using RCommon.Persistence.EFCore; +using RCommon.Persistence.Transactions; +using RCommon.TestBase; +using RCommon.TestBase.Data; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; + +try +{ +var host = Host.CreateDefaultBuilder(args) + .ConfigureServices(services => + { + var builder = new ConfigurationBuilder() + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); + var config = builder.Build(); + services.AddSingleton(config); + + // Configure RCommon + services.AddRCommon() + .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to Linq2Db without impact to domain service up through the stack + { + // Add all the DbContexts here + ef.AddDbContext("TestDbContext", ef => + { + ef.UseSqlServer(config.GetConnectionString("TestDbContext")); + }); + ef.SetDefaultDataStore(dataStore => + { + dataStore.DefaultDataStoreName = "TestDbContext"; + }); + ef.AddInMemoryPersistenceCaching(); // This gives us access to the caching repository interfaces/implementations + }) + .WithMemoryCaching(cache => + { + cache.Configure(x => + { + x.ExpirationScanFrequency = TimeSpan.FromMinutes(1); + }); + }); + + services.AddTransient(); + + }).Build(); + + Console.WriteLine("Seeding Data"); + var repo = new TestRepository(host.Services); + repo.Prepare_Can_Find_Async_With_Expression(); + + Console.WriteLine("Example Starting"); + var appService = host.Services.GetRequiredService(); + + Console.WriteLine("Hitting the database w/ a query"); + var customers = await appService.GetCustomers("my-test-key"); + Console.WriteLine(customers); + + Console.WriteLine("Hitting the cache"); + customers = await appService.GetCustomers("my-test-key"); + Console.WriteLine(customers); + + Console.WriteLine("Example Complete"); + repo.CleanUpSeedData(); + Console.ReadLine(); +} +catch (Exception ex) +{ + Console.WriteLine(ex.ToString()); + +} + diff --git a/Examples/Caching/Examples.Caching.PersistenceCaching/TestApplicationService.cs b/Examples/Caching/Examples.Caching.PersistenceCaching/TestApplicationService.cs new file mode 100644 index 00000000..bb1b0d90 --- /dev/null +++ b/Examples/Caching/Examples.Caching.PersistenceCaching/TestApplicationService.cs @@ -0,0 +1,29 @@ +using RCommon.Persistence.Crud; +using RCommon.TestBase.Entities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using RCommon.Persistence.Caching.Crud; + +namespace Examples.Caching.PersistenceCaching +{ + public class TestApplicationService : ITestApplicationService + { + private readonly ICachingGraphRepository _customerRepository; + + public TestApplicationService(ICachingGraphRepository customerRepository) + { + _customerRepository = customerRepository; + _customerRepository.DataStoreName = "TestDbContext"; + } + + public async Task> GetCustomers(object cacheKey) + { + return await _customerRepository.FindAsync(cacheKey, x => x.LastName == "Potter"); + } + + + } +} diff --git a/Examples/Caching/Examples.Caching.PersistenceCaching/appsettings.json b/Examples/Caching/Examples.Caching.PersistenceCaching/appsettings.json new file mode 100644 index 00000000..9b9d4863 --- /dev/null +++ b/Examples/Caching/Examples.Caching.PersistenceCaching/appsettings.json @@ -0,0 +1,18 @@ +{ + "ConnectionStrings": { + "TestDbContext": "Server=(localdb)\\mssqllocaldb;Database=RCommon_TestDatabase;Trusted_Connection=True;MultipleActiveResultSets=true", + "TestDbConnection": "Server=(localdb)\\mssqllocaldb;Database=RCommon_TestDatabase;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Serilog": { + "MinimumLevel": { + "Default": "Debug" + } + } +} diff --git a/Examples/Caching/Examples.Caching.RedisCaching/ConfigurationContainer.cs b/Examples/Caching/Examples.Caching.RedisCaching/ConfigurationContainer.cs new file mode 100644 index 00000000..be07627b --- /dev/null +++ b/Examples/Caching/Examples.Caching.RedisCaching/ConfigurationContainer.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Caching.RedisCaching +{ + internal static class ConfigurationContainer + { + public static IConfiguration Configuration { get; set; } + } +} diff --git a/Examples/Caching/Examples.Caching.RedisCaching/Examples.Caching.RedisCaching.csproj b/Examples/Caching/Examples.Caching.RedisCaching/Examples.Caching.RedisCaching.csproj new file mode 100644 index 00000000..8949b36f --- /dev/null +++ b/Examples/Caching/Examples.Caching.RedisCaching/Examples.Caching.RedisCaching.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + diff --git a/Examples/Caching/Examples.Caching.RedisCaching/ITestApplicationService.cs b/Examples/Caching/Examples.Caching.RedisCaching/ITestApplicationService.cs new file mode 100644 index 00000000..3712102f --- /dev/null +++ b/Examples/Caching/Examples.Caching.RedisCaching/ITestApplicationService.cs @@ -0,0 +1,9 @@ + +namespace Examples.Caching.RedisCaching +{ + public interface ITestApplicationService + { + TestDto GetDistributedMemoryCache(string key); + void SetDistributedMemoryCache(string key, Type type, object data); + } +} \ No newline at end of file diff --git a/Examples/Caching/Examples.Caching.RedisCaching/Program.cs b/Examples/Caching/Examples.Caching.RedisCaching/Program.cs new file mode 100644 index 00000000..3f885746 --- /dev/null +++ b/Examples/Caching/Examples.Caching.RedisCaching/Program.cs @@ -0,0 +1,58 @@ +using Examples.Caching.RedisCaching; +using RCommon.Json; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using RCommon; +using RCommon.Caching; +using RCommon.JsonNet; +using RCommon.RedisCache; +using System.Diagnostics; +using System.Reflection; + +try +{ + var host = Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, builder) => + { + + ConfigurationContainer.Configuration = builder + .Build(); + }) + .ConfigureServices(services => + { + // Configure RCommon + services.AddRCommon() + .WithJsonSerialization() + .WithDistributedCaching(cache => + { + cache.Configure(redis => + { + // Redis Configuration + }); + + }); + + services.AddTransient(); + + }).Build(); + + Console.WriteLine("Example Starting"); + var appService = host.Services.GetRequiredService(); + + // In Memory Distributed Cache + appService.SetDistributedMemoryCache("test-key", typeof(TestDto), new TestDto("test data 1")); + var testData1 = appService.GetDistributedMemoryCache("test-key"); + + Console.WriteLine(testData1.Message); + + Console.WriteLine("Example Complete"); + Console.ReadLine(); +} +catch (Exception ex) +{ + Console.WriteLine(ex.ToString()); + +} + + diff --git a/Examples/Caching/Examples.Caching.RedisCaching/TestApplicationService.cs b/Examples/Caching/Examples.Caching.RedisCaching/TestApplicationService.cs new file mode 100644 index 00000000..4c5fa5ef --- /dev/null +++ b/Examples/Caching/Examples.Caching.RedisCaching/TestApplicationService.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.Caching.Distributed; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Caching.RedisCaching +{ + public class TestApplicationService : ITestApplicationService + { + private readonly IDistributedCache _distributedCache; + private readonly IJsonSerializer _serializer; + + public TestApplicationService(IDistributedCache distributedCache, IJsonSerializer serializer) + { + _distributedCache = distributedCache; + _serializer = serializer; + } + + public void SetDistributedMemoryCache(string key, Type type, object data) + { + _distributedCache.Set(key, Encoding.UTF8.GetBytes(_serializer.Serialize(data, type))); + } + + public TestDto GetDistributedMemoryCache(string key) + { + var cache = _distributedCache.Get(key); + return _serializer.Deserialize(Encoding.UTF8.GetString(cache)); + } + } +} diff --git a/Examples/Caching/Examples.Caching.RedisCaching/TestDto.cs b/Examples/Caching/Examples.Caching.RedisCaching/TestDto.cs new file mode 100644 index 00000000..1a980228 --- /dev/null +++ b/Examples/Caching/Examples.Caching.RedisCaching/TestDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Caching.RedisCaching +{ + public record TestDto + { + public TestDto(string message) + { + Message = message; + } + + public string Message { get; } + } +} diff --git a/Examples/CleanWithCQRS/HR.LeaveManagement.API/HR.LeaveManagement.API.csproj b/Examples/CleanWithCQRS/HR.LeaveManagement.API/HR.LeaveManagement.API.csproj index b8ae9a7e..63b8ff10 100644 --- a/Examples/CleanWithCQRS/HR.LeaveManagement.API/HR.LeaveManagement.API.csproj +++ b/Examples/CleanWithCQRS/HR.LeaveManagement.API/HR.LeaveManagement.API.csproj @@ -8,7 +8,7 @@ - + diff --git a/Examples/CleanWithCQRS/HR.LeaveManagement.API/Program.cs b/Examples/CleanWithCQRS/HR.LeaveManagement.API/Program.cs index 4dd50d09..ffdba45d 100644 --- a/Examples/CleanWithCQRS/HR.LeaveManagement.API/Program.cs +++ b/Examples/CleanWithCQRS/HR.LeaveManagement.API/Program.cs @@ -79,7 +79,7 @@ mediator.AddLoggingToRequestPipeline(); mediator.AddUnitOfWorkToRequestPipeline(); }) - .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to NHibernate without impact to domain service up through the stack + .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to NHibernate without impact to domain service up through the stack { // Add all the DbContexts here ef.AddDbContext(DataStoreNamesConst.LeaveManagement, options => @@ -91,7 +91,8 @@ { dataStore.DefaultDataStoreName = DataStoreNamesConst.LeaveManagement; }); - }, unitOfWork => + }) + .WithUnitOfWork(unitOfWork => { unitOfWork.SetOptions(options => { diff --git a/Examples/CleanWithCQRS/HR.LeaveManagement.Application.UnitTests/HR.LeaveManagement.Application.UnitTests.csproj b/Examples/CleanWithCQRS/HR.LeaveManagement.Application.UnitTests/HR.LeaveManagement.Application.UnitTests.csproj index eeb327b9..065e1b9f 100644 --- a/Examples/CleanWithCQRS/HR.LeaveManagement.Application.UnitTests/HR.LeaveManagement.Application.UnitTests.csproj +++ b/Examples/CleanWithCQRS/HR.LeaveManagement.Application.UnitTests/HR.LeaveManagement.Application.UnitTests.csproj @@ -9,19 +9,19 @@ - - + + - - - - - + + + + + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Examples/CleanWithCQRS/HR.LeaveManagement.Application/HR.LeaveManagement.Application.csproj b/Examples/CleanWithCQRS/HR.LeaveManagement.Application/HR.LeaveManagement.Application.csproj index ea2308b5..0eedb610 100644 --- a/Examples/CleanWithCQRS/HR.LeaveManagement.Application/HR.LeaveManagement.Application.csproj +++ b/Examples/CleanWithCQRS/HR.LeaveManagement.Application/HR.LeaveManagement.Application.csproj @@ -10,7 +10,7 @@ - + diff --git a/Examples/CleanWithCQRS/HR.LeaveManagement.Identity/HR.LeaveManagement.Identity.csproj b/Examples/CleanWithCQRS/HR.LeaveManagement.Identity/HR.LeaveManagement.Identity.csproj index facd1685..37b5c5ea 100644 --- a/Examples/CleanWithCQRS/HR.LeaveManagement.Identity/HR.LeaveManagement.Identity.csproj +++ b/Examples/CleanWithCQRS/HR.LeaveManagement.Identity/HR.LeaveManagement.Identity.csproj @@ -7,12 +7,12 @@ - - + + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Examples/CleanWithCQRS/HR.LeaveManagement.MVC/HR.LeaveManagement.MVC.csproj b/Examples/CleanWithCQRS/HR.LeaveManagement.MVC/HR.LeaveManagement.MVC.csproj index 9d28c2e9..19c6af80 100644 --- a/Examples/CleanWithCQRS/HR.LeaveManagement.MVC/HR.LeaveManagement.MVC.csproj +++ b/Examples/CleanWithCQRS/HR.LeaveManagement.MVC/HR.LeaveManagement.MVC.csproj @@ -8,10 +8,10 @@ - + - - + + diff --git a/Examples/CleanWithCQRS/HR.LeaveManagement.Persistence/HR.LeaveManagement.Persistence.csproj b/Examples/CleanWithCQRS/HR.LeaveManagement.Persistence/HR.LeaveManagement.Persistence.csproj index f09b07a7..de60c5bd 100644 --- a/Examples/CleanWithCQRS/HR.LeaveManagement.Persistence/HR.LeaveManagement.Persistence.csproj +++ b/Examples/CleanWithCQRS/HR.LeaveManagement.Persistence/HR.LeaveManagement.Persistence.csproj @@ -7,11 +7,11 @@ - + - + diff --git a/Examples/Examples.sln b/Examples/Examples.sln index 781531d6..22c33805 100644 --- a/Examples/Examples.sln +++ b/Examples/Examples.sln @@ -115,7 +115,39 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.FluentValidation", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Validation", "Validation", "{0F54DCE2-27A5-4D07-B542-6D2A7B50D0EC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Examples.Validation.FluentValidation", "Validation\Examples.Validation.FluentValidation\Examples.Validation.FluentValidation.csproj", "{256821F9-8160-4819-B0A1-B769C5BBBBB6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Validation.FluentValidation", "Validation\Examples.Validation.FluentValidation\Examples.Validation.FluentValidation.csproj", "{256821F9-8160-4819-B0A1-B769C5BBBBB6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Persistence.Caching", "..\Src\RCommon.Persistence.Caching\RCommon.Persistence.Caching.csproj", "{B66429EB-4B5F-42F9-9CD7-7334190A14F4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.JsonNet", "..\Src\RCommon.JsonNet\RCommon.JsonNet.csproj", "{6E3ED0D2-0619-4CB6-BAE2-10488DF061C6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Json", "..\Src\RCommon.Json\RCommon.Json.csproj", "{36386A61-2408-44D7-8C4A-4748F79623FC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Caching", "..\Src\RCommon.Caching\RCommon.Caching.csproj", "{6C91AB35-2238-4EF5-82BF-81B3C3EF7061}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.RedisCache", "..\Src\RCommon.RedisCache\RCommon.RedisCache.csproj", "{10B9612F-DE04-4007-BDDE-B3DEBCEE59EB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Caching", "Caching", "{AE7572F9-5B50-4718-9552-3853C05E1A24}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Caching.MemoryCaching", "Caching\Examples.Caching.MemoryCaching\Examples.Caching.MemoryCaching.csproj", "{A8F70B2D-965E-41E5-A0CC-9F48C673CCFB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Caching.RedisCaching", "Caching\Examples.Caching.RedisCaching\Examples.Caching.RedisCaching.csproj", "{DB2C927A-6C16-4163-A492-5072F11B67D4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Caching.PersistenceCaching", "Caching\Examples.Caching.PersistenceCaching\Examples.Caching.PersistenceCaching.csproj", "{720E1999-9A71-49FD-B527-C07CF4510C9C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Json", "Json", "{A53F8891-29D2-449D-B9BA-062D19EC7834}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Json.JsonNet", "Json\Examples.Json.JsonNet\Examples.Json.JsonNet.csproj", "{5D9C635C-DDC0-4A54-A36D-D0F535638E79}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Examples.Json.SystemTextJson", "Json\Examples.Json.SystemTextJson\Examples.Json.SystemTextJson.csproj", "{8F5C5AD3-8310-47A7-BB30-42C054D032E2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.SystemTextJson", "..\Src\RCommon.SystemTextJson\RCommon.SystemTextJson.csproj", "{6C07FC1A-4339-42AA-AF5F-83570F81A5A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.MemoryCache", "..\Src\RCommon.MemoryCache\RCommon.MemoryCache.csproj", "{F5277287-1776-494B-92EE-7237D1B8949B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RCommon.Persistence.Caching.MemoryCache", "..\Src\RCommon.Persistence.Caching.MemoryCache\RCommon.Persistence.Caching.MemoryCache.csproj", "{B10EBC65-DB29-44AE-8BEB-E4BD170924C3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Persistence.Caching.RedisCache", "..\Src\RCommon.Persistence.Caching.RedisCache\RCommon.Persistence.Caching.RedisCache.csproj", "{924B529F-D036-464B-B1A6-257CD95143B4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -299,6 +331,62 @@ Global {256821F9-8160-4819-B0A1-B769C5BBBBB6}.Debug|Any CPU.Build.0 = Debug|Any CPU {256821F9-8160-4819-B0A1-B769C5BBBBB6}.Release|Any CPU.ActiveCfg = Release|Any CPU {256821F9-8160-4819-B0A1-B769C5BBBBB6}.Release|Any CPU.Build.0 = Release|Any CPU + {B66429EB-4B5F-42F9-9CD7-7334190A14F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B66429EB-4B5F-42F9-9CD7-7334190A14F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B66429EB-4B5F-42F9-9CD7-7334190A14F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B66429EB-4B5F-42F9-9CD7-7334190A14F4}.Release|Any CPU.Build.0 = Release|Any CPU + {6E3ED0D2-0619-4CB6-BAE2-10488DF061C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6E3ED0D2-0619-4CB6-BAE2-10488DF061C6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6E3ED0D2-0619-4CB6-BAE2-10488DF061C6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6E3ED0D2-0619-4CB6-BAE2-10488DF061C6}.Release|Any CPU.Build.0 = Release|Any CPU + {36386A61-2408-44D7-8C4A-4748F79623FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {36386A61-2408-44D7-8C4A-4748F79623FC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {36386A61-2408-44D7-8C4A-4748F79623FC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {36386A61-2408-44D7-8C4A-4748F79623FC}.Release|Any CPU.Build.0 = Release|Any CPU + {6C91AB35-2238-4EF5-82BF-81B3C3EF7061}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C91AB35-2238-4EF5-82BF-81B3C3EF7061}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C91AB35-2238-4EF5-82BF-81B3C3EF7061}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C91AB35-2238-4EF5-82BF-81B3C3EF7061}.Release|Any CPU.Build.0 = Release|Any CPU + {10B9612F-DE04-4007-BDDE-B3DEBCEE59EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10B9612F-DE04-4007-BDDE-B3DEBCEE59EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10B9612F-DE04-4007-BDDE-B3DEBCEE59EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10B9612F-DE04-4007-BDDE-B3DEBCEE59EB}.Release|Any CPU.Build.0 = Release|Any CPU + {A8F70B2D-965E-41E5-A0CC-9F48C673CCFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8F70B2D-965E-41E5-A0CC-9F48C673CCFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8F70B2D-965E-41E5-A0CC-9F48C673CCFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8F70B2D-965E-41E5-A0CC-9F48C673CCFB}.Release|Any CPU.Build.0 = Release|Any CPU + {DB2C927A-6C16-4163-A492-5072F11B67D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB2C927A-6C16-4163-A492-5072F11B67D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB2C927A-6C16-4163-A492-5072F11B67D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB2C927A-6C16-4163-A492-5072F11B67D4}.Release|Any CPU.Build.0 = Release|Any CPU + {720E1999-9A71-49FD-B527-C07CF4510C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {720E1999-9A71-49FD-B527-C07CF4510C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {720E1999-9A71-49FD-B527-C07CF4510C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {720E1999-9A71-49FD-B527-C07CF4510C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {5D9C635C-DDC0-4A54-A36D-D0F535638E79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D9C635C-DDC0-4A54-A36D-D0F535638E79}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D9C635C-DDC0-4A54-A36D-D0F535638E79}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D9C635C-DDC0-4A54-A36D-D0F535638E79}.Release|Any CPU.Build.0 = Release|Any CPU + {8F5C5AD3-8310-47A7-BB30-42C054D032E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F5C5AD3-8310-47A7-BB30-42C054D032E2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F5C5AD3-8310-47A7-BB30-42C054D032E2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F5C5AD3-8310-47A7-BB30-42C054D032E2}.Release|Any CPU.Build.0 = Release|Any CPU + {6C07FC1A-4339-42AA-AF5F-83570F81A5A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C07FC1A-4339-42AA-AF5F-83570F81A5A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C07FC1A-4339-42AA-AF5F-83570F81A5A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C07FC1A-4339-42AA-AF5F-83570F81A5A4}.Release|Any CPU.Build.0 = Release|Any CPU + {F5277287-1776-494B-92EE-7237D1B8949B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5277287-1776-494B-92EE-7237D1B8949B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5277287-1776-494B-92EE-7237D1B8949B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5277287-1776-494B-92EE-7237D1B8949B}.Release|Any CPU.Build.0 = Release|Any CPU + {B10EBC65-DB29-44AE-8BEB-E4BD170924C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B10EBC65-DB29-44AE-8BEB-E4BD170924C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B10EBC65-DB29-44AE-8BEB-E4BD170924C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B10EBC65-DB29-44AE-8BEB-E4BD170924C3}.Release|Any CPU.Build.0 = Release|Any CPU + {924B529F-D036-464B-B1A6-257CD95143B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {924B529F-D036-464B-B1A6-257CD95143B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {924B529F-D036-464B-B1A6-257CD95143B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {924B529F-D036-464B-B1A6-257CD95143B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -348,6 +436,19 @@ Global {BBBCCC2B-2218-4C32-96EE-C2153A23F643} = {3199F749-0082-41D0-91D3-ECED117F8B08} {0F54DCE2-27A5-4D07-B542-6D2A7B50D0EC} = {3234C3BB-1632-4684-838E-9D6D382D4D4D} {256821F9-8160-4819-B0A1-B769C5BBBBB6} = {0F54DCE2-27A5-4D07-B542-6D2A7B50D0EC} + {6E3ED0D2-0619-4CB6-BAE2-10488DF061C6} = {3199F749-0082-41D0-91D3-ECED117F8B08} + {10B9612F-DE04-4007-BDDE-B3DEBCEE59EB} = {3199F749-0082-41D0-91D3-ECED117F8B08} + {AE7572F9-5B50-4718-9552-3853C05E1A24} = {3234C3BB-1632-4684-838E-9D6D382D4D4D} + {A8F70B2D-965E-41E5-A0CC-9F48C673CCFB} = {AE7572F9-5B50-4718-9552-3853C05E1A24} + {DB2C927A-6C16-4163-A492-5072F11B67D4} = {AE7572F9-5B50-4718-9552-3853C05E1A24} + {720E1999-9A71-49FD-B527-C07CF4510C9C} = {AE7572F9-5B50-4718-9552-3853C05E1A24} + {A53F8891-29D2-449D-B9BA-062D19EC7834} = {3234C3BB-1632-4684-838E-9D6D382D4D4D} + {5D9C635C-DDC0-4A54-A36D-D0F535638E79} = {A53F8891-29D2-449D-B9BA-062D19EC7834} + {8F5C5AD3-8310-47A7-BB30-42C054D032E2} = {A53F8891-29D2-449D-B9BA-062D19EC7834} + {6C07FC1A-4339-42AA-AF5F-83570F81A5A4} = {3199F749-0082-41D0-91D3-ECED117F8B08} + {F5277287-1776-494B-92EE-7237D1B8949B} = {3199F749-0082-41D0-91D3-ECED117F8B08} + {B10EBC65-DB29-44AE-8BEB-E4BD170924C3} = {3199F749-0082-41D0-91D3-ECED117F8B08} + {924B529F-D036-464B-B1A6-257CD95143B4} = {3199F749-0082-41D0-91D3-ECED117F8B08} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0B0CD26D-8067-4667-863E-6B0EE7EDAA42} diff --git a/Examples/Json/Examples.Json.JsonNet/ConfigurationContainer.cs b/Examples/Json/Examples.Json.JsonNet/ConfigurationContainer.cs new file mode 100644 index 00000000..d767562e --- /dev/null +++ b/Examples/Json/Examples.Json.JsonNet/ConfigurationContainer.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Json.JsonNet +{ + internal static class ConfigurationContainer + { + public static IConfiguration Configuration { get; set; } + } +} diff --git a/Examples/Json/Examples.Json.JsonNet/Examples.Json.JsonNet.csproj b/Examples/Json/Examples.Json.JsonNet/Examples.Json.JsonNet.csproj new file mode 100644 index 00000000..643e0e9d --- /dev/null +++ b/Examples/Json/Examples.Json.JsonNet/Examples.Json.JsonNet.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Examples/Json/Examples.Json.JsonNet/ITestApplicationService.cs b/Examples/Json/Examples.Json.JsonNet/ITestApplicationService.cs new file mode 100644 index 00000000..37d8fbb6 --- /dev/null +++ b/Examples/Json/Examples.Json.JsonNet/ITestApplicationService.cs @@ -0,0 +1,8 @@ +namespace Examples.Json.JsonNet +{ + public interface ITestApplicationService + { + TestDto Deserialize(string json); + string Serialize(TestDto testDto); + } +} \ No newline at end of file diff --git a/Examples/Json/Examples.Json.JsonNet/Program.cs b/Examples/Json/Examples.Json.JsonNet/Program.cs new file mode 100644 index 00000000..2049ce90 --- /dev/null +++ b/Examples/Json/Examples.Json.JsonNet/Program.cs @@ -0,0 +1,52 @@ +using Examples.Json.JsonNet; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using RCommon; +using RCommon.Json; +using RCommon.JsonNet; +using System.Diagnostics; +using System.Reflection; +using Newtonsoft.Json; + +try +{ + var host = Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, builder) => + { + + ConfigurationContainer.Configuration = builder + .Build(); + }) + .ConfigureServices(services => + { + // Configure RCommon + services.AddRCommon() + .WithJsonSerialization(json => + { + + json.CamelCase = true; + json.Indented = true; + }); + + services.AddTransient(); + + }).Build(); + + Console.WriteLine("Example Starting"); + var appService = host.Services.GetRequiredService(); + string json = appService.Serialize(new TestDto("This is my ")); + TestDto dto = appService.Deserialize("{ // TestDto\r\n \"message\": This is my deserialized message\r\n}"); + + Console.WriteLine(json); + Console.WriteLine(dto.Message); + + Console.WriteLine("Example Complete"); + Console.ReadLine(); +} +catch (Exception ex) +{ + Console.WriteLine(ex.ToString()); + +} + diff --git a/Examples/Json/Examples.Json.JsonNet/TestApplicationService.cs b/Examples/Json/Examples.Json.JsonNet/TestApplicationService.cs new file mode 100644 index 00000000..6761ec21 --- /dev/null +++ b/Examples/Json/Examples.Json.JsonNet/TestApplicationService.cs @@ -0,0 +1,29 @@ +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Json.JsonNet +{ + public class TestApplicationService : ITestApplicationService + { + private readonly IJsonSerializer _serializer; + + public TestApplicationService(IJsonSerializer serializer) + { + _serializer = serializer; + } + + public string Serialize(TestDto testDto) + { + return _serializer.Serialize(testDto); + } + + public TestDto Deserialize(string json) + { + return _serializer.Deserialize(json); + } + } +} diff --git a/Examples/Json/Examples.Json.JsonNet/TestDto.cs b/Examples/Json/Examples.Json.JsonNet/TestDto.cs new file mode 100644 index 00000000..a7d44c13 --- /dev/null +++ b/Examples/Json/Examples.Json.JsonNet/TestDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Json.JsonNet +{ + public class TestDto + { + public TestDto(string message) + { + Message = message; + } + + public string Message { get; set; } + } +} diff --git a/Examples/Json/Examples.Json.SystemTextJson/ConfigurationContainer.cs b/Examples/Json/Examples.Json.SystemTextJson/ConfigurationContainer.cs new file mode 100644 index 00000000..3a899883 --- /dev/null +++ b/Examples/Json/Examples.Json.SystemTextJson/ConfigurationContainer.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Json.SystemTextJson +{ + internal static class ConfigurationContainer + { + public static IConfiguration Configuration { get; set; } + } +} diff --git a/Examples/Json/Examples.Json.SystemTextJson/Examples.Json.SystemTextJson.csproj b/Examples/Json/Examples.Json.SystemTextJson/Examples.Json.SystemTextJson.csproj new file mode 100644 index 00000000..fcba9353 --- /dev/null +++ b/Examples/Json/Examples.Json.SystemTextJson/Examples.Json.SystemTextJson.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Examples/Json/Examples.Json.SystemTextJson/ITestApplicationService.cs b/Examples/Json/Examples.Json.SystemTextJson/ITestApplicationService.cs new file mode 100644 index 00000000..23ad0d5c --- /dev/null +++ b/Examples/Json/Examples.Json.SystemTextJson/ITestApplicationService.cs @@ -0,0 +1,8 @@ +namespace Examples.Json.SystemTextJson +{ + public interface ITestApplicationService + { + TestDto Deserialize(string json); + string Serialize(TestDto testDto); + } +} \ No newline at end of file diff --git a/Examples/Json/Examples.Json.SystemTextJson/Program.cs b/Examples/Json/Examples.Json.SystemTextJson/Program.cs new file mode 100644 index 00000000..c4d21505 --- /dev/null +++ b/Examples/Json/Examples.Json.SystemTextJson/Program.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using RCommon; +using RCommon.Json; +using System.Diagnostics; +using System.Reflection; +using Examples.Json.SystemTextJson; +using RCommon.SystemTextJson; + +try +{ + var host = Host.CreateDefaultBuilder(args) + .ConfigureAppConfiguration((context, builder) => + { + + ConfigurationContainer.Configuration = builder + .Build(); + }) + .ConfigureServices(services => + { + // Configure RCommon + services.AddRCommon() + .WithJsonSerialization(json => + { + json.CamelCase = true; + json.Indented = true; + }); + + services.AddTransient(); + + }).Build(); + + Console.WriteLine("Example Starting"); + var appService = host.Services.GetRequiredService(); + string json = appService.Serialize(new TestDto("This is my ")); + TestDto dto = appService.Deserialize("{ // TestDto\r\n \"message\": This is my deserialized message\r\n}"); + + Console.WriteLine(json); + Console.WriteLine(dto.Message); + + Console.WriteLine("Example Complete"); + Console.ReadLine(); +} +catch (Exception ex) +{ + Console.WriteLine(ex.ToString()); + +} + diff --git a/Examples/Json/Examples.Json.SystemTextJson/TestApplicationService.cs b/Examples/Json/Examples.Json.SystemTextJson/TestApplicationService.cs new file mode 100644 index 00000000..23a40640 --- /dev/null +++ b/Examples/Json/Examples.Json.SystemTextJson/TestApplicationService.cs @@ -0,0 +1,29 @@ +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Json.SystemTextJson +{ + public class TestApplicationService : ITestApplicationService + { + private readonly IJsonSerializer _serializer; + + public TestApplicationService(IJsonSerializer serializer) + { + _serializer = serializer; + } + + public string Serialize(TestDto testDto) + { + return _serializer.Serialize(testDto); + } + + public TestDto Deserialize(string json) + { + return _serializer.Deserialize(json); + } + } +} diff --git a/Examples/Json/Examples.Json.SystemTextJson/TestDto.cs b/Examples/Json/Examples.Json.SystemTextJson/TestDto.cs new file mode 100644 index 00000000..da413845 --- /dev/null +++ b/Examples/Json/Examples.Json.SystemTextJson/TestDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Examples.Json.SystemTextJson +{ + public class TestDto + { + public TestDto(string message) + { + Message = message; + } + + public string Message { get; set; } + } +} diff --git a/Examples/Mediator/Examples.Mediator.MediatR/Program.cs b/Examples/Mediator/Examples.Mediator.MediatR/Program.cs index 2aaf6d06..d80b9a8b 100644 --- a/Examples/Mediator/Examples.Mediator.MediatR/Program.cs +++ b/Examples/Mediator/Examples.Mediator.MediatR/Program.cs @@ -31,7 +31,7 @@ mediator.AddNotification(); mediator.AddRequest(); mediator.AddRequest(); - + // Additional configurations can be set like below mediator.Configure(config => { diff --git a/Examples/Messaging/Examples.Messaging.Wolverine/Program.cs b/Examples/Messaging/Examples.Messaging.Wolverine/Program.cs index 99e2eceb..b23338bd 100644 --- a/Examples/Messaging/Examples.Messaging.Wolverine/Program.cs +++ b/Examples/Messaging/Examples.Messaging.Wolverine/Program.cs @@ -5,6 +5,7 @@ using RCommon; using RCommon.EventHandling; using RCommon.EventHandling.Producers; +using RCommon.Wolverine; using RCommon.Wolverine.Producers; using System.Diagnostics; using Wolverine; @@ -26,7 +27,7 @@ { // Configure RCommon services.AddRCommon() - .WithEventHandling(eventHandling => + .WithEventHandling(eventHandling => { eventHandling.AddProducer(); eventHandling.AddSubscriber(); diff --git a/Examples/Validation/Examples.Validation.FluentValidation/ITestApplicationService.cs b/Examples/Validation/Examples.Validation.FluentValidation/ITestApplicationService.cs index 83936c12..82c015b3 100644 --- a/Examples/Validation/Examples.Validation.FluentValidation/ITestApplicationService.cs +++ b/Examples/Validation/Examples.Validation.FluentValidation/ITestApplicationService.cs @@ -1,5 +1,4 @@ -using RCommon.ApplicationServices.ExecutionResults; -using RCommon.ApplicationServices.Validation; +using RCommon.ApplicationServices.Validation; namespace Examples.Validation.FluentValidation { diff --git a/Examples/Validation/Examples.Validation.FluentValidation/Program.cs b/Examples/Validation/Examples.Validation.FluentValidation/Program.cs index 373d4f51..0a13a35c 100644 --- a/Examples/Validation/Examples.Validation.FluentValidation/Program.cs +++ b/Examples/Validation/Examples.Validation.FluentValidation/Program.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Hosting; using RCommon; using RCommon.ApplicationServices; -using RCommon.ApplicationServices.ExecutionResults; using RCommon.FluentValidation; using System.Diagnostics; diff --git a/Examples/Validation/Examples.Validation.FluentValidation/TestApplicationService.cs b/Examples/Validation/Examples.Validation.FluentValidation/TestApplicationService.cs index 9005fb27..e0d4cbd3 100644 --- a/Examples/Validation/Examples.Validation.FluentValidation/TestApplicationService.cs +++ b/Examples/Validation/Examples.Validation.FluentValidation/TestApplicationService.cs @@ -1,5 +1,4 @@ using RCommon.ApplicationServices.Commands; -using RCommon.ApplicationServices.ExecutionResults; using RCommon.ApplicationServices.Queries; using RCommon.ApplicationServices.Validation; using System; diff --git a/Site.html b/Site.html new file mode 100644 index 00000000..0de20cf3 --- /dev/null +++ b/Site.html @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + +RCommon | Cohesive .NET Infrastructure + + + + +RCommon | Cohesive .NET Infrastructure + + +
+
+ + +
+
+
+

Overview Overview

+
+
+

+.NET has a growing ecosystems of tools and implement a wide array of design patterns, cloud patterns, and architectural designs. RCommon implements many of these in a cohesive set of libraries that are bootstrapped through a fluent interface to keep the configuration simple. +

+
    +
  • Persistence, Unit of Work
  • +
  • Event Bus/Message Bus
  • +
  • Mediator, Specification, Repository, CQRS, Event Sourcing
  • +
  • MassTransit, Woliverine, FluentValidation, Dapper, Linq2Db, Entity Framework, SendGrid, MediatR, and a whole lot more...
  • +
+
+
+
Deep Analytics
+
+
+
+
+
+
+

Documentation Documentation

+
+
+
AI Insights
+
+
+

+We maintain documentation along with examples for all RCommon libraries on GitBook. This is definitely the first place to go to get an idea of how all the libraries fit together into a cohesive application. We have a comprehensive Clean Architecture example along with more focused examples for most of the key patterns, and tools implemented by RCommon. +

+
+ +
+
+
+
+ + +
+
+
+
    +
  • + + + +
  • +
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/Src/RCommon.ApplicationServices/Commands/CommandBus.cs b/Src/RCommon.ApplicationServices/Commands/CommandBus.cs index d565bce3..208881aa 100644 --- a/Src/RCommon.ApplicationServices/Commands/CommandBus.cs +++ b/Src/RCommon.ApplicationServices/Commands/CommandBus.cs @@ -28,14 +28,13 @@ using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using RCommon.ApplicationServices.Caching; -using RCommon.ApplicationServices.Commands; -using RCommon.ApplicationServices.ExecutionResults; using RCommon.ApplicationServices.Validation; +using RCommon.Caching; +using RCommon.Models.Commands; +using RCommon.Models.ExecutionResults; using RCommon.Reflection; namespace RCommon.ApplicationServices.Commands @@ -44,18 +43,20 @@ public class CommandBus : ICommandBus { private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; - private readonly IMemoryCache _memoryCache; private readonly IValidationService _validationService; private readonly IOptions _validationOptions; + private readonly ICacheService _cacheService; + private readonly CachingOptions _cachingOptions; - public CommandBus(ILogger logger, IServiceProvider serviceProvider, IMemoryCache memoryCache, IValidationService validationService, - IOptions validationOptions) + public CommandBus(ILogger logger, IServiceProvider serviceProvider, IValidationService validationService, + IOptions validationOptions, IOptions cachingOptions, ICommonFactory cacheFactory) { _logger = logger; _serviceProvider = serviceProvider; - _memoryCache = memoryCache; _validationService = validationService; _validationOptions = validationOptions; + _cacheService = cacheFactory.Create(ExpressionCachingStrategy.Default); + _cachingOptions = cachingOptions.Value; } public async Task DispatchCommandAsync(ICommand command, CancellationToken cancellationToken = default) @@ -131,34 +132,37 @@ private class CommandExecutionDetails >.HandleAsync); private CommandExecutionDetails GetCommandExecutionDetails(Type commandType) { - return _memoryCache.GetOrCreate( - CacheKey.With(GetType(), commandType.GetCacheKey()), - e => - { - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1); - var commandInterfaceType = commandType + if (_cachingOptions.CachingEnabled && _cachingOptions.CacheDynamicallyCompiledExpressions) + { + return _cacheService.GetOrCreate(CacheKey.With(GetType(), commandType.GetCacheKey()), () => this.BuildCommandDetails(commandType)); + } + return this.BuildCommandDetails(commandType); + } + + private CommandExecutionDetails BuildCommandDetails(Type commandType) + { + var commandInterfaceType = commandType .GetTypeInfo() .GetInterfaces() .Single(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(ICommand<>)); - var commandTypes = commandInterfaceType.GetTypeInfo().GetGenericArguments(); + var commandTypes = commandInterfaceType.GetTypeInfo().GetGenericArguments(); - var commandHandlerType = typeof(ICommandHandler<,>) - .MakeGenericType(commandTypes[0], commandType); + var commandHandlerType = typeof(ICommandHandler<,>) + .MakeGenericType(commandTypes[0], commandType); - _logger.LogDebug( - "Command {CommandType} is resolved by {CommandHandlerType}", - commandType.PrettyPrint(), - commandHandlerType.PrettyPrint()); + _logger.LogDebug( + "Command {CommandType} is resolved by {CommandHandlerType}", + commandType.PrettyPrint(), + commandHandlerType.PrettyPrint()); - var invokeExecuteAsync = ReflectionHelper.CompileMethodInvocation>( - commandHandlerType, NameOfExecuteCommand); + var invokeExecuteAsync = ReflectionHelper.CompileMethodInvocation>( + commandHandlerType, NameOfExecuteCommand); - return new CommandExecutionDetails - { - CommandHandlerType = commandHandlerType, - Invoker = invokeExecuteAsync - }; - }); + return new CommandExecutionDetails + { + CommandHandlerType = commandHandlerType, + Invoker = invokeExecuteAsync + }; } } } diff --git a/Src/RCommon.ApplicationServices/Commands/ICommandBus.cs b/Src/RCommon.ApplicationServices/Commands/ICommandBus.cs index 79c35640..bddd6257 100644 --- a/Src/RCommon.ApplicationServices/Commands/ICommandBus.cs +++ b/Src/RCommon.ApplicationServices/Commands/ICommandBus.cs @@ -21,7 +21,8 @@ // 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. -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Models.Commands; +using RCommon.Models.ExecutionResults; using System.Threading; using System.Threading.Tasks; diff --git a/Src/RCommon.ApplicationServices/Commands/ICommandHandler.cs b/Src/RCommon.ApplicationServices/Commands/ICommandHandler.cs index 406a8290..28bd7197 100644 --- a/Src/RCommon.ApplicationServices/Commands/ICommandHandler.cs +++ b/Src/RCommon.ApplicationServices/Commands/ICommandHandler.cs @@ -1,4 +1,5 @@ -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Models.Commands; +using RCommon.Models.ExecutionResults; using System; using System.Collections.Generic; using System.Linq; diff --git a/Src/RCommon.ApplicationServices/Commands/ICommandResult.cs b/Src/RCommon.ApplicationServices/Commands/ICommandResult.cs deleted file mode 100644 index 45eeefc2..00000000 --- a/Src/RCommon.ApplicationServices/Commands/ICommandResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -using RCommon.ApplicationServices.ExecutionResults; - -namespace RCommon.ApplicationServices.Commands -{ - public interface ICommandResult where TExecutionResult : IExecutionResult - { - TExecutionResult Result { get; } - } -} diff --git a/Src/RCommon.ApplicationServices/CqrsBuilder.cs b/Src/RCommon.ApplicationServices/CqrsBuilder.cs index da489daa..0af33a80 100644 --- a/Src/RCommon.ApplicationServices/CqrsBuilder.cs +++ b/Src/RCommon.ApplicationServices/CqrsBuilder.cs @@ -1,5 +1,4 @@ -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using RCommon.ApplicationServices.Commands; using RCommon.ApplicationServices.Queries; using RCommon.EventHandling; @@ -23,7 +22,7 @@ protected void RegisterServices(IServiceCollection services) { services.AddTransient(); services.AddTransient(); - services.AddTransient(); // TODO: Allow CQRS configuration to choose caching strategy + } public IServiceCollection Services { get; } diff --git a/Src/RCommon.ApplicationServices/CqrsBuilderExtensions.cs b/Src/RCommon.ApplicationServices/CqrsBuilderExtensions.cs index 29907283..941f98dd 100644 --- a/Src/RCommon.ApplicationServices/CqrsBuilderExtensions.cs +++ b/Src/RCommon.ApplicationServices/CqrsBuilderExtensions.cs @@ -24,8 +24,10 @@ using Microsoft.Extensions.DependencyInjection; using RCommon.ApplicationServices; using RCommon.ApplicationServices.Commands; -using RCommon.ApplicationServices.ExecutionResults; using RCommon.ApplicationServices.Queries; +using RCommon.Models.Commands; +using RCommon.Models.ExecutionResults; +using RCommon.Models.Queries; using System; using System.Collections.Generic; using System.Linq; diff --git a/Src/RCommon.ApplicationServices/CqrsCachingOptions.cs b/Src/RCommon.ApplicationServices/CqrsCachingOptions.cs new file mode 100644 index 00000000..69c8878e --- /dev/null +++ b/Src/RCommon.ApplicationServices/CqrsCachingOptions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.ApplicationServices +{ + public class CqrsCachingOptions + { + public CqrsCachingOptions() + { + this.UseCacheForHandlers = false; + } + + public bool UseCacheForHandlers { get; set; } + } +} diff --git a/Src/RCommon.ApplicationServices/ICqrsBuilderExtensions.cs b/Src/RCommon.ApplicationServices/ICqrsBuilderExtensions.cs new file mode 100644 index 00000000..fd9f4966 --- /dev/null +++ b/Src/RCommon.ApplicationServices/ICqrsBuilderExtensions.cs @@ -0,0 +1,42 @@ +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.ApplicationServices +{ + public static class ICqrsBuilderExtensions + { + public static ICqrsBuilder AddMemoryCachingForHandlers(this ICqrsBuilder builder) + where T : IMemoryCachingBuilder + { + return AddMemoryCachingForHandlers(builder, x => { }); + } + + public static ICqrsBuilder AddMemoryCachingForHandlers(this ICqrsBuilder builder, Action actions) + where T : IMemoryCachingBuilder + { + Guard.IsNotNull(actions, nameof(actions)); + var cachingConfig = (T)Activator.CreateInstance(typeof(T), new object[] { builder }); + actions(cachingConfig); + return builder; + } + + public static ICqrsBuilder AddDistributedCachingForHandlers(this ICqrsBuilder builder) + where T : IDistributedCachingBuilder + { + return AddDistributedCachingForHandlers(builder, x => { }); + } + + public static ICqrsBuilder AddDistributedCachingForHandlers(this ICqrsBuilder builder, Action actions) + where T : IDistributedCachingBuilder + { + Guard.IsNotNull(actions, nameof(actions)); + var cachingConfig = (T)Activator.CreateInstance(typeof(T), new object[] { builder }); + actions(cachingConfig); + return builder; + } + } +} diff --git a/Src/RCommon.ApplicationServices/Queries/IQueryBus.cs b/Src/RCommon.ApplicationServices/Queries/IQueryBus.cs index a1871db9..dac776bd 100644 --- a/Src/RCommon.ApplicationServices/Queries/IQueryBus.cs +++ b/Src/RCommon.ApplicationServices/Queries/IQueryBus.cs @@ -1,4 +1,5 @@ -using System; +using RCommon.Models.Queries; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Src/RCommon.ApplicationServices/Queries/IQueryHandler.cs b/Src/RCommon.ApplicationServices/Queries/IQueryHandler.cs index ccc8c814..a1cde1a7 100644 --- a/Src/RCommon.ApplicationServices/Queries/IQueryHandler.cs +++ b/Src/RCommon.ApplicationServices/Queries/IQueryHandler.cs @@ -1,4 +1,5 @@ -using System; +using RCommon.Models.Queries; +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Src/RCommon.ApplicationServices/Queries/QueryBus.cs b/Src/RCommon.ApplicationServices/Queries/QueryBus.cs index 9bde45a2..868735ba 100644 --- a/Src/RCommon.ApplicationServices/Queries/QueryBus.cs +++ b/Src/RCommon.ApplicationServices/Queries/QueryBus.cs @@ -26,19 +26,19 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using RCommon.ApplicationServices.Caching; using RCommon.ApplicationServices.Validation; +using RCommon.Caching; +using RCommon.Models.Queries; using RCommon.Reflection; namespace RCommon.ApplicationServices.Queries { public class QueryBus : IQueryBus { - private class CacheItem + private class HandlerFuncMapping { public Type QueryHandlerType { get; set; } public Func HandlerFunc { get; set; } @@ -46,18 +46,20 @@ private class CacheItem private readonly ILogger _logger; private readonly IServiceProvider _serviceProvider; - private readonly IMemoryCache _memoryCache; private readonly IValidationService _validationService; private readonly IOptions _validationOptions; + private readonly CachingOptions _cachingOptions; + private readonly ICacheService _cacheService; - public QueryBus(ILogger logger, IServiceProvider serviceProvider, IMemoryCache memoryCache, IValidationService validationService, - IOptions validationOptions) + public QueryBus(ILogger logger, IServiceProvider serviceProvider, IValidationService validationService, + IOptions validationOptions, IOptions cachingOptions, ICommonFactory cacheFactory) { _logger = logger; _serviceProvider = serviceProvider; - _memoryCache = memoryCache; _validationService = validationService; _validationOptions = validationOptions; + _cachingOptions = cachingOptions.Value; + _cacheService = cacheFactory.Create(ExpressionCachingStrategy.Default); } public async Task DispatchQueryAsync(IQuery query, CancellationToken cancellationToken = default) @@ -69,45 +71,50 @@ public async Task DispatchQueryAsync(IQuery query, Ca } var queryType = query.GetType(); - var cacheItem = GetCacheItem(queryType); + var handlerFunc = GetHandlerFuncMapping(queryType); - var queryHandler = (IQueryHandler)_serviceProvider.GetRequiredService(cacheItem.QueryHandlerType); + var queryHandler = (IQueryHandler)_serviceProvider.GetRequiredService(handlerFunc.QueryHandlerType); if (_logger.IsEnabled(LogLevel.Trace)) { _logger.LogTrace( "Executing query {QueryType} ({QueryHandlerType}) by using query handler {QueryHandlerType}", queryType.PrettyPrint(), - cacheItem.QueryHandlerType.PrettyPrint(), + handlerFunc.QueryHandlerType.PrettyPrint(), queryHandler.GetType().PrettyPrint()); } - var task = (Task)cacheItem.HandlerFunc(queryHandler, query, cancellationToken); + var task = (Task)handlerFunc.HandlerFunc(queryHandler, query, cancellationToken); return await task.ConfigureAwait(false); } - private CacheItem GetCacheItem(Type queryType) + private HandlerFuncMapping GetHandlerFuncMapping(Type queryType) { - return _memoryCache.GetOrCreate( - CacheKey.With(GetType(), queryType.GetCacheKey()), - e => - { - e.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(1); - var queryInterfaceType = queryType + if (_cachingOptions.CachingEnabled && _cachingOptions.CacheDynamicallyCompiledExpressions) + { + return _cacheService.GetOrCreate(CacheKey.With(GetType(), queryType.GetCacheKey()), + () => this.BuildHandlerFuncMapping(queryType)); + } + return this.BuildHandlerFuncMapping(queryType); + + } + + private HandlerFuncMapping BuildHandlerFuncMapping(Type queryType) + { + var queryInterfaceType = queryType .GetTypeInfo() .GetInterfaces() .Single(i => i.GetTypeInfo().IsGenericType && i.GetGenericTypeDefinition() == typeof(IQuery<>)); - var queryHandlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, queryInterfaceType.GetTypeInfo().GetGenericArguments()[0]); - var invokeExecuteQueryAsync = ReflectionHelper.CompileMethodInvocation>( - queryHandlerType, - "HandleAsync", - queryType, typeof(CancellationToken)); - return new CacheItem - { - QueryHandlerType = queryHandlerType, - HandlerFunc = invokeExecuteQueryAsync - }; - }); + var queryHandlerType = typeof(IQueryHandler<,>).MakeGenericType(queryType, queryInterfaceType.GetTypeInfo().GetGenericArguments()[0]); + var invokeExecuteQueryAsync = ReflectionHelper.CompileMethodInvocation>( + queryHandlerType, + "HandleAsync", + queryType, typeof(CancellationToken)); + return new HandlerFuncMapping + { + QueryHandlerType = queryHandlerType, + HandlerFunc = invokeExecuteQueryAsync + }; } } } diff --git a/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj b/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj index 958c3c3c..9f2296b2 100644 --- a/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj +++ b/Src/RCommon.ApplicationServices/RCommon.ApplicationServices.csproj @@ -1,31 +1,16 @@  - net6.0;net7.0;net8.0; + net8.0; + - + - - - - - - - - - - - - - - - - - - + + diff --git a/Src/RCommon.Authorization.Web/RCommon.Authorization.Web.csproj b/Src/RCommon.Authorization.Web/RCommon.Authorization.Web.csproj index 68208740..fa7f2292 100644 --- a/Src/RCommon.Authorization.Web/RCommon.Authorization.Web.csproj +++ b/Src/RCommon.Authorization.Web/RCommon.Authorization.Web.csproj @@ -1,11 +1,11 @@  - net6.0;net7.0;net8.0; + net8.0; - + diff --git a/Src/RCommon.ApplicationServices/Caching/CacheKey.cs b/Src/RCommon.Caching/CacheKey.cs similarity index 87% rename from Src/RCommon.ApplicationServices/Caching/CacheKey.cs rename to Src/RCommon.Caching/CacheKey.cs index 2ce5f288..666a793c 100644 --- a/Src/RCommon.ApplicationServices/Caching/CacheKey.cs +++ b/Src/RCommon.Caching/CacheKey.cs @@ -22,16 +22,21 @@ // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. using System; -using Newtonsoft.Json; -using RCommon.Entities.ValueObjects; -namespace RCommon.ApplicationServices.Caching +namespace RCommon.Caching { - [JsonConverter(typeof(SingleValueObjectConverter))] - public class CacheKey : SingleValueObject + public class CacheKey { public const int MaxLength = 256; + public CacheKey(string value) + { + if (string.IsNullOrEmpty(value)) + throw new ArgumentNullException(nameof(value)); + if (value.Length > MaxLength) + throw new ArgumentOutOfRangeException(nameof(value), value, $"Cache keys can maximum be '{MaxLength}' in length"); + } + public static CacheKey With(params string[] keys) { return new CacheKey(string.Join("-", keys)); @@ -41,13 +46,5 @@ public static CacheKey With(Type ownerType, params string[] keys) { return With($"{ownerType.GetCacheKey()}:{string.Join("-", keys)}"); } - - public CacheKey(string value) : base(value) - { - if (string.IsNullOrEmpty(value)) - throw new ArgumentNullException(nameof(value)); - if (value.Length > MaxLength) - throw new ArgumentOutOfRangeException(nameof(value), value, $"Cache keys can maximum be '{MaxLength}' in length"); - } } } diff --git a/Src/RCommon.Caching/CachingBuilderExtensions.cs b/Src/RCommon.Caching/CachingBuilderExtensions.cs new file mode 100644 index 00000000..53788789 --- /dev/null +++ b/Src/RCommon.Caching/CachingBuilderExtensions.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Caching +{ + public static class CachingBuilderExtensions + { + public static IRCommonBuilder WithMemoryCaching(this IRCommonBuilder builder) + where T : IMemoryCachingBuilder + { + return WithMemoryCaching(builder, x => { }); + } + + public static IRCommonBuilder WithMemoryCaching(this IRCommonBuilder builder, Action actions) + where T : IMemoryCachingBuilder + { + Guard.IsNotNull(actions, nameof(actions)); + var cachingConfig = (T)Activator.CreateInstance(typeof(T), new object[] { builder }); + actions(cachingConfig); + return builder; + } + + public static IRCommonBuilder WithDistributedCaching(this IRCommonBuilder builder) + where T : IDistributedCachingBuilder + { + return WithDistributedCaching(builder, x => { }); + } + + public static IRCommonBuilder WithDistributedCaching(this IRCommonBuilder builder, Action actions) + where T : IDistributedCachingBuilder + { + Guard.IsNotNull(actions, nameof(actions)); + var cachingConfig = (T)Activator.CreateInstance(typeof(T), new object[] { builder }); + actions(cachingConfig); + return builder; + } + + } +} diff --git a/Src/RCommon.Caching/ExpressionCachingStrategy.cs b/Src/RCommon.Caching/ExpressionCachingStrategy.cs new file mode 100644 index 00000000..fb708bfe --- /dev/null +++ b/Src/RCommon.Caching/ExpressionCachingStrategy.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Caching +{ + public enum ExpressionCachingStrategy + { + Default + } +} diff --git a/Src/RCommon.Caching/ICacheService.cs b/Src/RCommon.Caching/ICacheService.cs new file mode 100644 index 00000000..c1165c1e --- /dev/null +++ b/Src/RCommon.Caching/ICacheService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Caching +{ + public interface ICacheService + { + TData GetOrCreate(object key, Func data); + Task GetOrCreateAsync(object key, Func data); + } +} diff --git a/Src/RCommon.Caching/IDistributedCachingBuilder.cs b/Src/RCommon.Caching/IDistributedCachingBuilder.cs new file mode 100644 index 00000000..ee5df102 --- /dev/null +++ b/Src/RCommon.Caching/IDistributedCachingBuilder.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Caching +{ + public interface IDistributedCachingBuilder + { + IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.Caching/IMemoryCachingBuilder.cs b/Src/RCommon.Caching/IMemoryCachingBuilder.cs new file mode 100644 index 00000000..cacbefac --- /dev/null +++ b/Src/RCommon.Caching/IMemoryCachingBuilder.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Caching +{ + public interface IMemoryCachingBuilder + { + IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.Caching/RCommon.Caching.csproj b/Src/RCommon.Caching/RCommon.Caching.csproj new file mode 100644 index 00000000..b27e3041 --- /dev/null +++ b/Src/RCommon.Caching/RCommon.Caching.csproj @@ -0,0 +1,11 @@ + + + + net8.0; + + + + + + + diff --git a/Src/RCommon.Core/CachingOptions.cs b/Src/RCommon.Core/CachingOptions.cs new file mode 100644 index 00000000..9c20829e --- /dev/null +++ b/Src/RCommon.Core/CachingOptions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon +{ + public class CachingOptions + { + public CachingOptions() + { + this.CachingEnabled = false; + this.CacheDynamicallyCompiledExpressions = false; + } + + public bool CachingEnabled { get; set; } + public bool CacheDynamicallyCompiledExpressions { get; set; } + } +} diff --git a/Src/RCommon.Core/CommonFactory.cs b/Src/RCommon.Core/CommonFactory.cs index b63b8504..a568b07c 100644 --- a/Src/RCommon.Core/CommonFactory.cs +++ b/Src/RCommon.Core/CommonFactory.cs @@ -6,21 +6,21 @@ namespace RCommon { - public class CommonFactory : ICommonFactory + public class CommonFactory : ICommonFactory { - private readonly Func _initFunc; + private readonly Func _initFunc; - public CommonFactory(Func initFunc) + public CommonFactory(Func initFunc) { _initFunc = initFunc; } - public T Create() + public TResult Create() { return _initFunc(); } - public T Create(Action customize) + public TResult Create(Action customize) { var concreteObject = _initFunc(); customize(concreteObject); diff --git a/Src/RCommon.Core/EventHandling/Producers/InMemoryTransactionalEventRouter.cs b/Src/RCommon.Core/EventHandling/Producers/InMemoryTransactionalEventRouter.cs index 4c47f89e..388c9270 100644 --- a/Src/RCommon.Core/EventHandling/Producers/InMemoryTransactionalEventRouter.cs +++ b/Src/RCommon.Core/EventHandling/Producers/InMemoryTransactionalEventRouter.cs @@ -41,28 +41,23 @@ public async Task RouteEventsAsync(IEnumerable transactional var asyncEvents = transactionalEvents.Where(x => x is IAsyncEvent); var eventProducers = _serviceProvider.GetServices(); - _logger.LogInformation($"{this.GetGenericTypeName()} is routing {syncEvents.Count().ToString()} synchronized transactional events."); - - // Produce the Synchronized Events first - foreach (var @event in syncEvents) + if (syncEvents.Any() && asyncEvents.Any()) { - _logger.LogDebug($"{this.GetGenericTypeName()} is routing event: {@event}"); - foreach (var producer in eventProducers) - { - await producer.ProduceEventAsync(@event); - } - } - - _logger.LogInformation($"{this.GetGenericTypeName()} is routing {asyncEvents.Count().ToString()} asynchronous transactional events."); + // Produce the Synchronized Events first + _logger.LogInformation($"{this.GetGenericTypeName()} is routing {syncEvents.Count().ToString()} synchronized transactional events."); + await this.ProduceSyncEvents(syncEvents, eventProducers).ConfigureAwait(false); - // Produce the Async Events - foreach (var @event in asyncEvents) + // Produce the Async Events + _logger.LogInformation($"{this.GetGenericTypeName()} is routing {asyncEvents.Count().ToString()} asynchronous transactional events."); + await this.ProduceAsyncEvents(asyncEvents, eventProducers).ConfigureAwait(false); + } + else { - foreach (var producer in eventProducers) - { - await producer.ProduceEventAsync(@event).ConfigureAwait(false); - } + // Send as synchronized by default + _logger.LogInformation($"No sync/async events found. {this.GetGenericTypeName()} is routing {syncEvents.Count().ToString()} as synchronized transactional events by default."); + await this.ProduceSyncEvents(transactionalEvents, eventProducers).ConfigureAwait(false); } + } } catch(EventProductionException ex) @@ -80,9 +75,34 @@ public async Task RouteEventsAsync(IEnumerable transactional } } + private async Task ProduceAsyncEvents(IEnumerable asyncEvents, IEnumerable eventProducers) + { + var eventTaskList = new List(); + foreach (var @event in asyncEvents) + { + foreach (var producer in eventProducers) + { + eventTaskList.Add(producer.ProduceEventAsync(@event)); + } + } + await Task.WhenAll(eventTaskList); + } + + private async Task ProduceSyncEvents(IEnumerable syncEvents, IEnumerable eventProducers) + { + foreach (var @event in syncEvents) + { + _logger.LogDebug($"{this.GetGenericTypeName()} is routing event: {@event}"); + foreach (var producer in eventProducers) + { + await producer.ProduceEventAsync(@event).ConfigureAwait(false); + } + } + } + public async Task RouteEventsAsync() { - await this.RouteEventsAsync(this._storedTransactionalEvents); + await this.RouteEventsAsync(this._storedTransactionalEvents).ConfigureAwait(false); this._storedTransactionalEvents.Clear(); } diff --git a/Src/RCommon.Core/EventHandling/Producers/PublishWithEventBusEventProducer.cs b/Src/RCommon.Core/EventHandling/Producers/PublishWithEventBusEventProducer.cs index 486fd91c..95f22239 100644 --- a/Src/RCommon.Core/EventHandling/Producers/PublishWithEventBusEventProducer.cs +++ b/Src/RCommon.Core/EventHandling/Producers/PublishWithEventBusEventProducer.cs @@ -35,7 +35,7 @@ public async Task ProduceEventAsync(T @event, CancellationToken cancellationT } // This should already be using a Scoped publish method - await _eventBus.PublishAsync(@event); + await _eventBus.PublishAsync(@event).ConfigureAwait(false); } catch (Exception ex) { diff --git a/Src/RCommon.Core/Configuration/IRCommonBuilder.cs b/Src/RCommon.Core/IRCommonBuilder.cs similarity index 100% rename from Src/RCommon.Core/Configuration/IRCommonBuilder.cs rename to Src/RCommon.Core/IRCommonBuilder.cs diff --git a/Src/RCommon.Core/RCommon.Core.csproj b/Src/RCommon.Core/RCommon.Core.csproj index d02c45ce..589df676 100644 --- a/Src/RCommon.Core/RCommon.Core.csproj +++ b/Src/RCommon.Core/RCommon.Core.csproj @@ -1,34 +1,15 @@  - net6.0;net7.0;net8.0; + net8.0; $(MSBuildProjectName.Replace(" ", "_").Replace(".Core", "")) - - - - - - - - - - - - - - - - - - - + - diff --git a/Src/RCommon.Core/Configuration/RCommonBuilder.cs b/Src/RCommon.Core/RCommonBuilder.cs similarity index 95% rename from Src/RCommon.Core/Configuration/RCommonBuilder.cs rename to Src/RCommon.Core/RCommonBuilder.cs index 8be11d5a..5c5065b6 100644 --- a/Src/RCommon.Core/Configuration/RCommonBuilder.cs +++ b/Src/RCommon.Core/RCommonBuilder.cs @@ -3,6 +3,7 @@ using System.Reflection; using RCommon.EventHandling; using RCommon.EventHandling.Producers; +using Microsoft.Extensions.Options; namespace RCommon { @@ -21,6 +22,8 @@ public RCommonBuilder(IServiceCollection services) Guard.Against(services == null, "IServiceCollection cannot be null"); Services = services; + this.Services.Configure(x => { x.CachingEnabled = false; }); + // Event Bus services.AddSingleton(sp => { diff --git a/Src/RCommon.Core/Configuration/RCommonBuilderException.cs b/Src/RCommon.Core/RCommonBuilderException.cs similarity index 100% rename from Src/RCommon.Core/Configuration/RCommonBuilderException.cs rename to Src/RCommon.Core/RCommonBuilderException.cs diff --git a/Src/RCommon.Dapper/DapperPersistenceBuilder.cs b/Src/RCommon.Dapper/DapperPersistenceBuilder.cs index 049908a6..0315f678 100644 --- a/Src/RCommon.Dapper/DapperPersistenceBuilder.cs +++ b/Src/RCommon.Dapper/DapperPersistenceBuilder.cs @@ -1,5 +1,4 @@ -using Microsoft.Data.SqlClient; -using RCommon.Persistence.Sql; +using RCommon.Persistence.Sql; using System; using System.Collections.Generic; using System.Data.Common; @@ -31,9 +30,10 @@ public DapperPersistenceBuilder(IServiceCollection services) } + public IServiceCollection Services => _services; public IDapperBuilder AddDbConnection(string dataStoreName, Action options) - where TDbConnection : IRDbConnection + where TDbConnection : RDbConnection { Guard.Against(dataStoreName.IsNullOrEmpty(), "You must set a name for the Data Store"); Guard.Against(options == null, "You must configure the options for the RDbConnection for it to be useful"); @@ -42,7 +42,7 @@ public IDapperBuilder AddDbConnection(string dataStoreName, Actio this._services.TryAddTransient(); this._services.TryAddTransient(Type.GetType(dbContext)); - this._services.Configure(options => options.Register(dataStoreName)); + this._services.Configure(options => options.Register(dataStoreName)); this._services.Configure(options); return this; diff --git a/Src/RCommon.Dapper/IDapperBuilder.cs b/Src/RCommon.Dapper/IDapperBuilder.cs index 13b22740..d4a0161b 100644 --- a/Src/RCommon.Dapper/IDapperBuilder.cs +++ b/Src/RCommon.Dapper/IDapperBuilder.cs @@ -5,6 +5,6 @@ namespace RCommon { public interface IDapperBuilder : IPersistenceBuilder { - IDapperBuilder AddDbConnection(string dataStoreName, Action options) where TDbConnection : IRDbConnection; + IDapperBuilder AddDbConnection(string dataStoreName, Action options) where TDbConnection : RDbConnection; } } diff --git a/Src/RCommon.Dapper/RCommon.Dapper.csproj b/Src/RCommon.Dapper/RCommon.Dapper.csproj index 120e58a9..944a87b8 100644 --- a/Src/RCommon.Dapper/RCommon.Dapper.csproj +++ b/Src/RCommon.Dapper/RCommon.Dapper.csproj @@ -1,29 +1,12 @@  - net6.0;net7.0;net8.0; + net8.0; - - - - - - - - - - - - - - - - - - + diff --git a/Src/RCommon.EfCore/Crud/EFCoreRepository.cs b/Src/RCommon.EfCore/Crud/EFCoreRepository.cs index ea9b7cb1..d037feaf 100644 --- a/Src/RCommon.EfCore/Crud/EFCoreRepository.cs +++ b/Src/RCommon.EfCore/Crud/EFCoreRepository.cs @@ -17,7 +17,6 @@ using System.Threading; using System.Threading.Tasks; using RCommon.Persistence.Crud; -using RCommon.Persistence.Transactions; namespace RCommon.Persistence.EFCore.Crud { diff --git a/Src/RCommon.EfCore/EFCorePerisistenceBuilder.cs b/Src/RCommon.EfCore/EFCorePerisistenceBuilder.cs index e26fcbc1..1dd3f630 100644 --- a/Src/RCommon.EfCore/EFCorePerisistenceBuilder.cs +++ b/Src/RCommon.EfCore/EFCorePerisistenceBuilder.cs @@ -32,22 +32,23 @@ public EFCorePerisistenceBuilder(IServiceCollection services) services.AddTransient(typeof(IGraphRepository<>), typeof(EFCoreRepository<>)); } + public IServiceCollection Services => _services; public IEFCorePersistenceBuilder AddDbContext(string dataStoreName, Action? options = null) where TDbContext : RCommonDbContext { Guard.Against(dataStoreName.IsNullOrEmpty(), "You must set a name for the Data Store"); - this._services.TryAddTransient(); - this._services.Configure(options => options.Register(dataStoreName)); - this._services.AddDbContext(options, ServiceLifetime.Scoped); + _services.TryAddTransient(); + _services.Configure(options => options.Register(dataStoreName)); + _services.AddDbContext(options, ServiceLifetime.Scoped); return this; } public IPersistenceBuilder SetDefaultDataStore(Action options) { - this._services.Configure(options); + _services.Configure(options); return this; } } diff --git a/Src/RCommon.EfCore/RCommon.EFCore.csproj b/Src/RCommon.EfCore/RCommon.EFCore.csproj index 434e0e13..a7c54617 100644 --- a/Src/RCommon.EfCore/RCommon.EFCore.csproj +++ b/Src/RCommon.EfCore/RCommon.EFCore.csproj @@ -2,26 +2,11 @@ enable - net6.0;net7.0;net8.0; + net8.0; - - - - - - - - - - - - - - - - - + + diff --git a/Src/RCommon.Emailing/RCommon.Emailing.csproj b/Src/RCommon.Emailing/RCommon.Emailing.csproj index 8445a5e3..2a1c5994 100644 --- a/Src/RCommon.Emailing/RCommon.Emailing.csproj +++ b/Src/RCommon.Emailing/RCommon.Emailing.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0; + net8.0; enable @@ -9,8 +9,4 @@ - - - - diff --git a/Src/RCommon.Entities/BusinessEntity.cs b/Src/RCommon.Entities/BusinessEntity.cs index 70e2344f..3b2ec906 100644 --- a/Src/RCommon.Entities/BusinessEntity.cs +++ b/Src/RCommon.Entities/BusinessEntity.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using PropertyChanged; using System.ComponentModel; using System.ComponentModel.DataAnnotations.Schema; using RCommon.EventHandling; diff --git a/Src/RCommon.Entities/RCommon.Entities.csproj b/Src/RCommon.Entities/RCommon.Entities.csproj index cc78e268..6a58d35d 100644 --- a/Src/RCommon.Entities/RCommon.Entities.csproj +++ b/Src/RCommon.Entities/RCommon.Entities.csproj @@ -1,25 +1,11 @@  - net6.0;net7.0;net8.0; + net8.0; - - - - - - - - - - - - - - - +
diff --git a/Src/RCommon.Entities/ValueObjects/ISingleValueObject.cs b/Src/RCommon.Entities/ValueObjects/ISingleValueObject.cs deleted file mode 100644 index e71efacb..00000000 --- a/Src/RCommon.Entities/ValueObjects/ISingleValueObject.cs +++ /dev/null @@ -1,30 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// 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. - -namespace RCommon.Entities.ValueObjects -{ - public interface ISingleValueObject - { - object GetValue(); - } -} diff --git a/Src/RCommon.Entities/ValueObjects/SingleValueObject.cs b/Src/RCommon.Entities/ValueObjects/SingleValueObject.cs deleted file mode 100644 index d0791432..00000000 --- a/Src/RCommon.Entities/ValueObjects/SingleValueObject.cs +++ /dev/null @@ -1,81 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// 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. - -using System; -using System.Collections.Generic; -using System.Reflection; - -namespace RCommon.Entities.ValueObjects -{ - public abstract class SingleValueObject : ValueObject, IComparable, ISingleValueObject - where T : IComparable - { - private static readonly Type Type = typeof(T); - private static readonly TypeInfo TypeInfo = typeof(T).GetTypeInfo(); - - public T Value { get; } - - protected SingleValueObject(T value) - { - if (TypeInfo.IsEnum && !Enum.IsDefined(Type, value)) - { - throw new ArgumentException($"The value '{value}' isn't defined in enum '{Type}'"); - } - - Value = value; - } - - public int CompareTo(object obj) - { - if (ReferenceEquals(null, obj)) - { - throw new ArgumentNullException(nameof(obj)); - } - - var other = obj as SingleValueObject; - if (other == null) - { - throw new ArgumentException($"Cannot compare '{GetType()}' and '{obj.GetType()}'"); - } - - return Value.CompareTo(other.Value); - } - - protected override IEnumerable GetEqualityComponents() - { - yield return Value; - } - - public override string ToString() - { - return ReferenceEquals(Value, null) - ? string.Empty - : Value.ToString(); - } - - public object GetValue() - { - return Value; - } - } -} diff --git a/Src/RCommon.Entities/ValueObjects/SingleValueObjectConverter.cs b/Src/RCommon.Entities/ValueObjects/SingleValueObjectConverter.cs deleted file mode 100644 index ede4ed1f..00000000 --- a/Src/RCommon.Entities/ValueObjects/SingleValueObjectConverter.cs +++ /dev/null @@ -1,72 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// 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. - -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Reflection; -using Newtonsoft.Json; - -namespace RCommon.Entities.ValueObjects -{ - public class SingleValueObjectConverter : JsonConverter - { - private static readonly ConcurrentDictionary ConstructorArgumentTypes = new ConcurrentDictionary(); - - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - if (!(value is ISingleValueObject singleValueObject)) - { - return; - } - - serializer.Serialize(writer, singleValueObject.GetValue()); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - if (reader.Value == null) - { - return null; - } - - var parameterType = ConstructorArgumentTypes.GetOrAdd( - objectType, - t => - { - var constructorInfo = objectType.GetTypeInfo().GetConstructors(BindingFlags.Public | BindingFlags.Instance).Single(); - var parameterInfo = constructorInfo.GetParameters().Single(); - return parameterInfo.ParameterType; - }); - - var value = serializer.Deserialize(reader, parameterType); - - return Activator.CreateInstance(objectType, value); - } - - public override bool CanConvert(Type objectType) - { - return typeof(ISingleValueObject).GetTypeInfo().IsAssignableFrom(objectType); - } - } -} diff --git a/Src/RCommon.Entities/ValueObjects/ValueObject.cs b/Src/RCommon.Entities/ValueObjects/ValueObject.cs deleted file mode 100644 index 1ec26e14..00000000 --- a/Src/RCommon.Entities/ValueObjects/ValueObject.cs +++ /dev/null @@ -1,84 +0,0 @@ -// The MIT License (MIT) -// -// Copyright (c) 2015-2021 Rasmus Mikkelsen -// Copyright (c) 2015-2021 eBay Software Foundation -// https://github.com/eventflow/EventFlow -// -// 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. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace RCommon.Entities.ValueObjects -{ - public abstract class ValueObject - { - private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); - - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) return true; - if (ReferenceEquals(null, obj)) return false; - if (GetType() != obj.GetType()) return false; - var other = obj as ValueObject; - return other != null && GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); - } - - public override int GetHashCode() - { - unchecked - { - return GetEqualityComponents().Aggregate(17, (current, obj) => current * 23 + (obj?.GetHashCode() ?? 0)); - } - } - - public static bool operator ==(ValueObject left, ValueObject right) - { - return Equals(left, right); - } - - public static bool operator !=(ValueObject left, ValueObject right) - { - return !Equals(left, right); - } - - public override string ToString() - { - return $"{{{string.Join(", ", GetProperties().Select(f => $"{f.Name}: {f.GetValue(this)}"))}}}"; - } - - protected virtual IEnumerable GetEqualityComponents() - { - return GetProperties().Select(x => x.GetValue(this)); - } - - protected virtual IEnumerable GetProperties() - { - return TypeProperties.GetOrAdd( - GetType(), - t => t - .GetTypeInfo() - .GetProperties(BindingFlags.Instance | BindingFlags.Public) - .OrderBy(p => p.Name) - .ToList()); - } - } -} diff --git a/Src/RCommon.FluentValidation/RCommon.FluentValidation.csproj b/Src/RCommon.FluentValidation/RCommon.FluentValidation.csproj index be85babf..097310b7 100644 --- a/Src/RCommon.FluentValidation/RCommon.FluentValidation.csproj +++ b/Src/RCommon.FluentValidation/RCommon.FluentValidation.csproj @@ -1,14 +1,14 @@  - net6.0;net7.0;net8.0; + net8.0; enable enable - - + + diff --git a/Src/RCommon.Json/IJsonBuilder.cs b/Src/RCommon.Json/IJsonBuilder.cs new file mode 100644 index 00000000..ff4ee589 --- /dev/null +++ b/Src/RCommon.Json/IJsonBuilder.cs @@ -0,0 +1,14 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Json +{ + public interface IJsonBuilder + { + IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.Json/IJsonSerializer.cs b/Src/RCommon.Json/IJsonSerializer.cs new file mode 100644 index 00000000..b53cc0bb --- /dev/null +++ b/Src/RCommon.Json/IJsonSerializer.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Json +{ + public interface IJsonSerializer + { + public string Serialize(object obj, JsonSerializeOptions? options = null); + + public string Serialize(object obj, Type type, JsonSerializeOptions? options = null); + + public T Deserialize(string json, JsonDeserializeOptions? options = null); + + public object Deserialize(string json, Type type, JsonDeserializeOptions? options = null); + } +} diff --git a/Src/RCommon.Json/JsonBuilderExtensions.cs b/Src/RCommon.Json/JsonBuilderExtensions.cs new file mode 100644 index 00000000..940b52bc --- /dev/null +++ b/Src/RCommon.Json/JsonBuilderExtensions.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Json +{ + public static class JsonBuilderExtensions + { + public static IRCommonBuilder WithJsonSerialization(this IRCommonBuilder builder) + where T : IJsonBuilder + { + return WithJsonSerialization(builder, x => { }, x => { }, x => { }); + } + + public static IRCommonBuilder WithJsonSerialization(this IRCommonBuilder builder, Action serializeOptions, + Action deSerializeOptions) + where T : IJsonBuilder + { + return WithJsonSerialization(builder, serializeOptions, deSerializeOptions, x => { }); + } + + public static IRCommonBuilder WithJsonSerialization(this IRCommonBuilder builder, Action serializeOptions) + where T : IJsonBuilder + { + return WithJsonSerialization(builder, serializeOptions, x => { }, x => { }); + } + + public static IRCommonBuilder WithJsonSerialization(this IRCommonBuilder builder, + Action deSerializeOptions) + where T : IJsonBuilder + { + return WithJsonSerialization(builder, x => { }, deSerializeOptions, x => { }); + } + + public static IRCommonBuilder WithJsonSerialization(this IRCommonBuilder builder, Action actions) + where T : IJsonBuilder + { + + return WithJsonSerialization(builder, x => { }, x => { }, actions); + } + + public static IRCommonBuilder WithJsonSerialization(this IRCommonBuilder builder, Action serializeOptions, + Action deSerializeOptions, Action actions) + where T : IJsonBuilder + { + Guard.IsNotNull(serializeOptions, nameof(serializeOptions)); + Guard.IsNotNull(deSerializeOptions, nameof(deSerializeOptions)); + Guard.IsNotNull(actions, nameof(actions)); + builder.Services.Configure(serializeOptions); + var jsonConfig = (T)Activator.CreateInstance(typeof(T), new object[] { builder }); + actions(jsonConfig); + return builder; + } + } +} diff --git a/Src/RCommon.Json/JsonDeserializeOptions.cs b/Src/RCommon.Json/JsonDeserializeOptions.cs new file mode 100644 index 00000000..6cb9fc60 --- /dev/null +++ b/Src/RCommon.Json/JsonDeserializeOptions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Json +{ + public class JsonDeserializeOptions + { + public JsonDeserializeOptions() + { + this.CamelCase = true; + } + + public bool CamelCase { get; set; } + } +} diff --git a/Src/RCommon.Json/JsonSerializeOptions.cs b/Src/RCommon.Json/JsonSerializeOptions.cs new file mode 100644 index 00000000..2996532c --- /dev/null +++ b/Src/RCommon.Json/JsonSerializeOptions.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Json +{ + public class JsonSerializeOptions + { + public JsonSerializeOptions() + { + this.CamelCase = true; + this.Indented = false; + } + + public bool CamelCase { get; set; } + public bool Indented { get; set; } + } +} diff --git a/Src/RCommon.Json/RCommon.Json.csproj b/Src/RCommon.Json/RCommon.Json.csproj new file mode 100644 index 00000000..b27e3041 --- /dev/null +++ b/Src/RCommon.Json/RCommon.Json.csproj @@ -0,0 +1,11 @@ + + + + net8.0; + + + + + + + diff --git a/Src/RCommon.JsonNet/IJsonNetBuilder.cs b/Src/RCommon.JsonNet/IJsonNetBuilder.cs new file mode 100644 index 00000000..4d62325d --- /dev/null +++ b/Src/RCommon.JsonNet/IJsonNetBuilder.cs @@ -0,0 +1,13 @@ +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.JsonNet +{ + public interface IJsonNetBuilder : IJsonBuilder + { + } +} diff --git a/Src/RCommon.JsonNet/IJsonNetBuilderExtensions.cs b/Src/RCommon.JsonNet/IJsonNetBuilderExtensions.cs new file mode 100644 index 00000000..9c59c7dc --- /dev/null +++ b/Src/RCommon.JsonNet/IJsonNetBuilderExtensions.cs @@ -0,0 +1,20 @@ +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace RCommon.JsonNet +{ + public static class IJsonNetBuilderExtensions + { + public static IJsonNetBuilder Configure(this IJsonNetBuilder builder, Action options) + { + builder.Services.Configure(options); + return builder; + } + } +} diff --git a/Src/RCommon.JsonNet/JsonNetBuilder.cs b/Src/RCommon.JsonNet/JsonNetBuilder.cs new file mode 100644 index 00000000..77d3b785 --- /dev/null +++ b/Src/RCommon.JsonNet/JsonNetBuilder.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.JsonNet +{ + public class JsonNetBuilder : IJsonNetBuilder + { + public JsonNetBuilder(IRCommonBuilder builder) + { + Services = builder.Services; + this.RegisterServices(Services); + } + + protected void RegisterServices(IServiceCollection services) + { + services.AddTransient(); + } + + public IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.JsonNet/JsonNetSerializer.cs b/Src/RCommon.JsonNet/JsonNetSerializer.cs new file mode 100644 index 00000000..4c551423 --- /dev/null +++ b/Src/RCommon.JsonNet/JsonNetSerializer.cs @@ -0,0 +1,70 @@ +using Microsoft.Extensions.Options; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.JsonNet +{ + public class JsonNetSerializer : IJsonSerializer + { + private readonly JsonSerializerSettings _settings; + + public JsonNetSerializer(IOptions options) + { + _settings = options.Value; + } + + public T Deserialize(string json, JsonDeserializeOptions? options = null) + { + if (options != null && options.CamelCase) + { + _settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + } + return JsonConvert.DeserializeObject(json, _settings); + } + + public object Deserialize(string json, Type type, JsonDeserializeOptions? options = null) + { + if (options != null && options.CamelCase) + { + _settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + } + return JsonConvert.DeserializeObject(json, type, _settings); + } + + public string Serialize(object obj, JsonSerializeOptions? options = null) + { + if (options != null && options.CamelCase) + { + _settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + } + + if (options != null && options.Indented) + { + _settings.Formatting = Formatting.Indented; + } + + return JsonConvert.SerializeObject(obj, _settings); + } + + public string Serialize(object obj, Type type, JsonSerializeOptions? options = null) + { + if (options != null && options.CamelCase) + { + _settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + } + + if (options != null && options.Indented) + { + _settings.Formatting = Formatting.Indented; + } + + return JsonConvert.SerializeObject(obj, type, _settings); + } + } +} diff --git a/Src/RCommon.JsonNet/RCommon.JsonNet.csproj b/Src/RCommon.JsonNet/RCommon.JsonNet.csproj new file mode 100644 index 00000000..cdbb93e7 --- /dev/null +++ b/Src/RCommon.JsonNet/RCommon.JsonNet.csproj @@ -0,0 +1,15 @@ + + + + net8.0; + + + + + + + + + + + diff --git a/Src/RCommon.Linq2Db/Crud/Linq2DbRepository.cs b/Src/RCommon.Linq2Db/Crud/Linq2DbRepository.cs index 08b2b0ca..b06e52a3 100644 --- a/Src/RCommon.Linq2Db/Crud/Linq2DbRepository.cs +++ b/Src/RCommon.Linq2Db/Crud/Linq2DbRepository.cs @@ -3,7 +3,6 @@ using Microsoft.Extensions.Options; using RCommon.Entities; using RCommon.Collections; - using System; using System.Collections.Generic; using System.Linq; diff --git a/Src/RCommon.Linq2Db/Linq2DbPersistenceBuilder.cs b/Src/RCommon.Linq2Db/Linq2DbPersistenceBuilder.cs index 76af7df5..e269bcbf 100644 --- a/Src/RCommon.Linq2Db/Linq2DbPersistenceBuilder.cs +++ b/Src/RCommon.Linq2Db/Linq2DbPersistenceBuilder.cs @@ -31,6 +31,7 @@ public Linq2DbPersistenceBuilder(IServiceCollection services) services.AddTransient(typeof(ILinqRepository<>), typeof(Linq2DbRepository<>)); } + public IServiceCollection Services => _services; public ILinq2DbPersistenceBuilder AddDataConnection(string dataStoreName, Func options) where TDataConnection : RCommonDataConnection @@ -39,7 +40,7 @@ public ILinq2DbPersistenceBuilder AddDataConnection(string data Guard.Against(options == null, "You must set options to a value in order for them to be useful"); this._services.TryAddTransient(); - this._services.Configure(options => options.Register(dataStoreName)); + this._services.Configure(options => options.Register(dataStoreName)); this._services.AddLinqToDBContext(options); return this; } diff --git a/Src/RCommon.Linq2Db/RCommon.Linq2Db.csproj b/Src/RCommon.Linq2Db/RCommon.Linq2Db.csproj index b038ed79..60e1244d 100644 --- a/Src/RCommon.Linq2Db/RCommon.Linq2Db.csproj +++ b/Src/RCommon.Linq2Db/RCommon.Linq2Db.csproj @@ -1,25 +1,13 @@  - net6.0;net7.0;net8.0; + net8.0; enable enable - - - - - - - - - - - - diff --git a/Src/RCommon.MassTransit/RCommon.MassTransit.csproj b/Src/RCommon.MassTransit/RCommon.MassTransit.csproj index c443952b..8d65a318 100644 --- a/Src/RCommon.MassTransit/RCommon.MassTransit.csproj +++ b/Src/RCommon.MassTransit/RCommon.MassTransit.csproj @@ -1,13 +1,13 @@  - net6.0;net7.0;net8.0; + net8.0; enable enable - + diff --git a/Src/RCommon.Mediator/RCommon.Mediator.csproj b/Src/RCommon.Mediator/RCommon.Mediator.csproj index f23078aa..0016c8f2 100644 --- a/Src/RCommon.Mediator/RCommon.Mediator.csproj +++ b/Src/RCommon.Mediator/RCommon.Mediator.csproj @@ -1,20 +1,10 @@  - net6.0;net7.0;net8.0; + net8.0; enable enable - - - - - - - - - - diff --git a/Src/RCommon.Mediatr/Behaviors/UnitOfWorkBehavior.cs b/Src/RCommon.Mediatr/Behaviors/UnitOfWorkBehavior.cs index 2f8b8ef9..f025e026 100644 --- a/Src/RCommon.Mediatr/Behaviors/UnitOfWorkBehavior.cs +++ b/Src/RCommon.Mediatr/Behaviors/UnitOfWorkBehavior.cs @@ -41,7 +41,6 @@ public async Task Handle(TRequest request, RequestHandlerDelegate Handle(TRequest request, RequestHandlerDelegate(); } diff --git a/Src/RCommon.Mediatr/RCommon.MediatR.csproj b/Src/RCommon.Mediatr/RCommon.MediatR.csproj index 701456b2..b8255f09 100644 --- a/Src/RCommon.Mediatr/RCommon.MediatR.csproj +++ b/Src/RCommon.Mediatr/RCommon.MediatR.csproj @@ -1,13 +1,13 @@ - net6.0;net7.0;net8.0; + net8.0; enable enable - + diff --git a/Src/RCommon.MemoryCache/DistributedMemoryCacheBuilder.cs b/Src/RCommon.MemoryCache/DistributedMemoryCacheBuilder.cs new file mode 100644 index 00000000..d691c874 --- /dev/null +++ b/Src/RCommon.MemoryCache/DistributedMemoryCacheBuilder.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + public class DistributedMemoryCacheBuilder : IDistributedMemoryCachingBuilder + { + public DistributedMemoryCacheBuilder(IRCommonBuilder builder) + { + Services = builder.Services; + this.RegisterServices(Services); + } + + protected void RegisterServices(IServiceCollection services) + { + + } + + public IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.MemoryCache/DistributedMemoryCacheService.cs b/Src/RCommon.MemoryCache/DistributedMemoryCacheService.cs new file mode 100644 index 00000000..3f5f2f68 --- /dev/null +++ b/Src/RCommon.MemoryCache/DistributedMemoryCacheService.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Caching.Distributed; +using RCommon.Caching; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + /// + /// Just a proxy for Distributed memory caching implemented through caching abstractions + /// + /// This gives us a uniform way for getting/setting cache no matter the caching strategy + public class DistributedMemoryCacheService : ICacheService + { + private readonly IDistributedCache _distributedCache; + private readonly IJsonSerializer _jsonSerializer; + + public DistributedMemoryCacheService(IDistributedCache distributedCache, IJsonSerializer jsonSerializer) + { + _distributedCache = distributedCache; + _jsonSerializer = jsonSerializer; + } + + public TData GetOrCreate(object key, Func data) + { + var json = _distributedCache.GetString(key.ToString()); + + if (json == null) + { + _distributedCache.SetString(key.ToString(), _jsonSerializer.Serialize(data())); + return data(); + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + + public async Task GetOrCreateAsync(object key, Func data) + { + var json = await _distributedCache.GetStringAsync(key.ToString()).ConfigureAwait(false); + + if (json == null) + { + await _distributedCache.SetStringAsync(key.ToString(), _jsonSerializer.Serialize(data())).ConfigureAwait(false); + return data(); + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + } +} diff --git a/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilder.cs b/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilder.cs new file mode 100644 index 00000000..a500c5a1 --- /dev/null +++ b/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilder.cs @@ -0,0 +1,13 @@ +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + public interface IDistributedMemoryCachingBuilder : IDistributedCachingBuilder + { + } +} diff --git a/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilderExtensions.cs b/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilderExtensions.cs new file mode 100644 index 00000000..5139cb42 --- /dev/null +++ b/Src/RCommon.MemoryCache/IDistributedMemoryCachingBuilderExtensions.cs @@ -0,0 +1,50 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + public static class IDistributedMemoryCachingBuilderExtensions + { + public static IDistributedMemoryCachingBuilder Configure(this IDistributedMemoryCachingBuilder builder, Action actions) + { + builder.Services.AddDistributedMemoryCache(actions); + return builder; + } + + /// + /// This greatly improves performance across various areas of RCommon which use generics and reflection heavily + /// to compile expressions and lambdas + /// + /// Builder + /// Same builder to allow chaining + /// The most performant way to do this is through InMemoryCache but this works fine + public static IDistributedMemoryCachingBuilder CacheDynamicallyCompiledExpressions(this IDistributedMemoryCachingBuilder builder) + { + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case ExpressionCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + builder.Services.TryAddTransient, CommonFactory>(); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + return builder; + } + } +} diff --git a/Src/RCommon.MemoryCache/IInMemoryCachingBuilder.cs b/Src/RCommon.MemoryCache/IInMemoryCachingBuilder.cs new file mode 100644 index 00000000..50aea9fd --- /dev/null +++ b/Src/RCommon.MemoryCache/IInMemoryCachingBuilder.cs @@ -0,0 +1,13 @@ +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + public interface IInMemoryCachingBuilder : IMemoryCachingBuilder + { + } +} diff --git a/Src/RCommon.MemoryCache/IInMemoryCachingBuilderExtensions.cs b/Src/RCommon.MemoryCache/IInMemoryCachingBuilderExtensions.cs new file mode 100644 index 00000000..55a09805 --- /dev/null +++ b/Src/RCommon.MemoryCache/IInMemoryCachingBuilderExtensions.cs @@ -0,0 +1,51 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + public static class IInMemoryCachingBuilderExtensions + { + public static IInMemoryCachingBuilder Configure(this IInMemoryCachingBuilder builder, Action actions) + { + builder.Services.AddMemoryCache(actions); + return builder; + } + + /// + /// This greatly improves performance across various areas of RCommon which use generics and reflection heavily + /// to compile expressions and lambdas + /// + /// Builder + /// Same builder to allow chaining + /// This is the most performant way to cache expressions! + public static IInMemoryCachingBuilder CacheDynamicallyCompiledExpressions(this IInMemoryCachingBuilder builder) + { + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case ExpressionCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient, CommonFactory>(); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + return builder; + } + } +} diff --git a/Src/RCommon.MemoryCache/InMemoryCacheService.cs b/Src/RCommon.MemoryCache/InMemoryCacheService.cs new file mode 100644 index 00000000..cb0d19f7 --- /dev/null +++ b/Src/RCommon.MemoryCache/InMemoryCacheService.cs @@ -0,0 +1,40 @@ +using Microsoft.Extensions.Caching.Memory; +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + /// + /// Just a proxy for memory caching implemented through caching abstractions + /// + /// This gives us a uniform way for getting/setting cache no matter the caching strategy + public class InMemoryCacheService : ICacheService + { + private readonly IMemoryCache _memoryCache; + + public InMemoryCacheService(IMemoryCache memoryCache) + { + _memoryCache = memoryCache; + } + + public TData GetOrCreate(object key, Func data) + { + return _memoryCache.GetOrCreate(key, cacheEntry => + { + return data(); + }); + } + + public async Task GetOrCreateAsync(object key, Func data) + { + return await _memoryCache.GetOrCreateAsync(key, async cacheEntry => + { + return await Task.FromResult(data()); + }).ConfigureAwait(false); + } + } +} diff --git a/Src/RCommon.MemoryCache/InMemoryCachingBuilder.cs b/Src/RCommon.MemoryCache/InMemoryCachingBuilder.cs new file mode 100644 index 00000000..9053b2d2 --- /dev/null +++ b/Src/RCommon.MemoryCache/InMemoryCachingBuilder.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.MemoryCache +{ + public class InMemoryCachingBuilder : IInMemoryCachingBuilder + { + public InMemoryCachingBuilder(IRCommonBuilder builder) + { + Services = builder.Services; + this.RegisterServices(Services); + } + + protected void RegisterServices(IServiceCollection services) + { + + } + + public IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.MemoryCache/RCommon.MemoryCache.csproj b/Src/RCommon.MemoryCache/RCommon.MemoryCache.csproj new file mode 100644 index 00000000..b7833e0e --- /dev/null +++ b/Src/RCommon.MemoryCache/RCommon.MemoryCache.csproj @@ -0,0 +1,17 @@ + + + + net8.0; + + + + + + + + + + + + + diff --git a/Src/RCommon.ApplicationServices/Commands/CommandResult.cs b/Src/RCommon.Models/Commands/CommandResult.cs similarity index 64% rename from Src/RCommon.ApplicationServices/Commands/CommandResult.cs rename to Src/RCommon.Models/Commands/CommandResult.cs index 36631f07..0b69d5b3 100644 --- a/Src/RCommon.ApplicationServices/Commands/CommandResult.cs +++ b/Src/RCommon.Models/Commands/CommandResult.cs @@ -1,13 +1,13 @@ -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Models.ExecutionResults; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace RCommon.ApplicationServices.Commands +namespace RCommon.Models.Commands { - public class CommandResult : ICommandResult + public record CommandResult : ICommandResult where TExecutionResult : IExecutionResult { public TExecutionResult Result { get; } diff --git a/Src/RCommon.ApplicationServices/Commands/ICommand.cs b/Src/RCommon.Models/Commands/ICommand.cs similarity index 64% rename from Src/RCommon.ApplicationServices/Commands/ICommand.cs rename to Src/RCommon.Models/Commands/ICommand.cs index 47b60bd4..fe14835e 100644 --- a/Src/RCommon.ApplicationServices/Commands/ICommand.cs +++ b/Src/RCommon.Models/Commands/ICommand.cs @@ -1,13 +1,13 @@ -using RCommon.ApplicationServices.ExecutionResults; +using RCommon.Models.ExecutionResults; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace RCommon.ApplicationServices.Commands +namespace RCommon.Models.Commands { - public interface ICommand + public interface ICommand : IModel { } diff --git a/Src/RCommon.Models/Commands/ICommandResult.cs b/Src/RCommon.Models/Commands/ICommandResult.cs new file mode 100644 index 00000000..c54bbf93 --- /dev/null +++ b/Src/RCommon.Models/Commands/ICommandResult.cs @@ -0,0 +1,10 @@ +using RCommon.Models.ExecutionResults; + +namespace RCommon.Models.Commands +{ + public interface ICommandResult : IModel + where TExecutionResult : IExecutionResult + { + TExecutionResult Result { get; } + } +} diff --git a/Src/RCommon.ApplicationServices/ExecutionResults/ExecutionResult.cs b/Src/RCommon.Models/ExecutionResults/ExecutionResult.cs similarity index 94% rename from Src/RCommon.ApplicationServices/ExecutionResults/ExecutionResult.cs rename to Src/RCommon.Models/ExecutionResults/ExecutionResult.cs index 9f79c7df..87e827e5 100644 --- a/Src/RCommon.ApplicationServices/ExecutionResults/ExecutionResult.cs +++ b/Src/RCommon.Models/ExecutionResults/ExecutionResult.cs @@ -24,9 +24,9 @@ using System.Collections.Generic; using System.Linq; -namespace RCommon.ApplicationServices.ExecutionResults +namespace RCommon.Models.ExecutionResults { - public abstract class ExecutionResult : IExecutionResult + public abstract record ExecutionResult : IExecutionResult { private static readonly IExecutionResult SuccessResult = new SuccessExecutionResult(); private static readonly IExecutionResult FailedResult = new FailedExecutionResult(Enumerable.Empty()); diff --git a/Src/RCommon.ApplicationServices/ExecutionResults/FailedExecutionResult.cs b/Src/RCommon.Models/ExecutionResults/FailedExecutionResult.cs similarity index 93% rename from Src/RCommon.ApplicationServices/ExecutionResults/FailedExecutionResult.cs rename to Src/RCommon.Models/ExecutionResults/FailedExecutionResult.cs index 35f909ec..de169313 100644 --- a/Src/RCommon.ApplicationServices/ExecutionResults/FailedExecutionResult.cs +++ b/Src/RCommon.Models/ExecutionResults/FailedExecutionResult.cs @@ -24,9 +24,9 @@ using System.Collections.Generic; using System.Linq; -namespace RCommon.ApplicationServices.ExecutionResults +namespace RCommon.Models.ExecutionResults { - public class FailedExecutionResult : ExecutionResult + public record FailedExecutionResult : ExecutionResult { public IReadOnlyCollection Errors { get; } @@ -35,7 +35,7 @@ public FailedExecutionResult( { Errors = (errors ?? Enumerable.Empty()).ToList(); } - + public override bool IsSuccess { get; } = false; public override string ToString() diff --git a/Src/RCommon.ApplicationServices/ExecutionResults/IExecutionResult.cs b/Src/RCommon.Models/ExecutionResults/IExecutionResult.cs similarity index 90% rename from Src/RCommon.ApplicationServices/ExecutionResults/IExecutionResult.cs rename to Src/RCommon.Models/ExecutionResults/IExecutionResult.cs index a34cb295..19a1c482 100644 --- a/Src/RCommon.ApplicationServices/ExecutionResults/IExecutionResult.cs +++ b/Src/RCommon.Models/ExecutionResults/IExecutionResult.cs @@ -21,10 +21,10 @@ // 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. -namespace RCommon.ApplicationServices.ExecutionResults +namespace RCommon.Models.ExecutionResults { - public interface IExecutionResult + public interface IExecutionResult : IModel { - bool IsSuccess { get; } + bool IsSuccess { get; } } } diff --git a/Src/RCommon.ApplicationServices/ExecutionResults/SuccessExecutionResult.cs b/Src/RCommon.Models/ExecutionResults/SuccessExecutionResult.cs similarity index 92% rename from Src/RCommon.ApplicationServices/ExecutionResults/SuccessExecutionResult.cs rename to Src/RCommon.Models/ExecutionResults/SuccessExecutionResult.cs index 891ebb84..13d7604c 100644 --- a/Src/RCommon.ApplicationServices/ExecutionResults/SuccessExecutionResult.cs +++ b/Src/RCommon.Models/ExecutionResults/SuccessExecutionResult.cs @@ -21,9 +21,9 @@ // 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. -namespace RCommon.ApplicationServices.ExecutionResults +namespace RCommon.Models.ExecutionResults { - public class SuccessExecutionResult : ExecutionResult + public record SuccessExecutionResult : ExecutionResult { public override bool IsSuccess { get; } = true; diff --git a/Src/RCommon.Models/IPaginatedListRequest.cs b/Src/RCommon.Models/IPaginatedListRequest.cs index a62774a4..3925ca13 100644 --- a/Src/RCommon.Models/IPaginatedListRequest.cs +++ b/Src/RCommon.Models/IPaginatedListRequest.cs @@ -1,6 +1,6 @@ namespace RCommon.Models { - public interface IPaginatedListRequest + public interface IPaginatedListRequest : IModel { int PageNumber { get; set; } int? PageSize { get; set; } diff --git a/Src/RCommon.Models/ISearchPaginatedListRequest.cs b/Src/RCommon.Models/ISearchPaginatedListRequest.cs index 63778f57..a3501cc6 100644 --- a/Src/RCommon.Models/ISearchPaginatedListRequest.cs +++ b/Src/RCommon.Models/ISearchPaginatedListRequest.cs @@ -1,7 +1,7 @@ namespace RCommon.Models { - public interface ISearchPaginatedListRequest + public interface ISearchPaginatedListRequest : IModel { string SearchString { get; set; } } -} \ No newline at end of file +} diff --git a/Src/RCommon.Models/PaginatedListModel.cs b/Src/RCommon.Models/PaginatedListModel.cs index 3351fdbc..45a05d04 100644 --- a/Src/RCommon.Models/PaginatedListModel.cs +++ b/Src/RCommon.Models/PaginatedListModel.cs @@ -3,8 +3,6 @@ using System.Linq; using System.Linq.Expressions; using System.Text; -using System.Text.Json.Serialization; -using RCommon.Serialization.Json; using RCommon.Collections; namespace RCommon.Models @@ -131,7 +129,6 @@ protected void PaginateList(IPaginatedList source, PaginatedListRequest protected abstract IQueryable CastItems(IQueryable source); - [JsonIgnore] public virtual Expression> SortExpression { get => _sortExpression; set => _sortExpression = value; } public List Items { get; set; } @@ -144,7 +141,6 @@ protected void PaginateList(IPaginatedList source, PaginatedListRequest public string SortBy { get; set; } - [JsonConverter(typeof(JsonByteEnumConverter))] public SortDirectionEnum SortDirection { get; set; } public bool HasPreviousPage @@ -283,7 +279,6 @@ protected void PaginateList(IPaginatedList source, PaginatedListRequest Items = query.AsQueryable().ToList(); } - [JsonIgnore] public virtual Expression> SortExpression { get => _sortExpression; set => _sortExpression = value; } public List Items { get; set; } @@ -296,7 +291,6 @@ protected void PaginateList(IPaginatedList source, PaginatedListRequest public string SortBy { get; set; } - [JsonConverter(typeof(JsonByteEnumConverter))] public SortDirectionEnum SortDirection { get; set; } public bool HasPreviousPage @@ -315,4 +309,5 @@ public bool HasNextPage } } } + } diff --git a/Src/RCommon.Models/PaginatedListRequest.cs b/Src/RCommon.Models/PaginatedListRequest.cs index b18c5b33..f8346e12 100644 --- a/Src/RCommon.Models/PaginatedListRequest.cs +++ b/Src/RCommon.Models/PaginatedListRequest.cs @@ -4,7 +4,7 @@ namespace RCommon.Models { [DataContract] - public abstract class PaginatedListRequest : IModel, IPaginatedListRequest + public abstract record PaginatedListRequest : IPaginatedListRequest { public PaginatedListRequest() { diff --git a/Src/RCommon.ApplicationServices/Queries/IQuery.cs b/Src/RCommon.Models/Queries/IQuery.cs similarity index 82% rename from Src/RCommon.ApplicationServices/Queries/IQuery.cs rename to Src/RCommon.Models/Queries/IQuery.cs index 27ea145f..75e34d01 100644 --- a/Src/RCommon.ApplicationServices/Queries/IQuery.cs +++ b/Src/RCommon.Models/Queries/IQuery.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace RCommon.ApplicationServices.Queries +namespace RCommon.Models.Queries { public interface IQuery { diff --git a/Src/RCommon.Models/RCommon.Models.csproj b/Src/RCommon.Models/RCommon.Models.csproj index 3e146d47..9970ec25 100644 --- a/Src/RCommon.Models/RCommon.Models.csproj +++ b/Src/RCommon.Models/RCommon.Models.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0; + net8.0; diff --git a/Src/RCommon.Models/SearchPaginatedListRequest.cs b/Src/RCommon.Models/SearchPaginatedListRequest.cs index 8a12b081..9e6f2748 100644 --- a/Src/RCommon.Models/SearchPaginatedListRequest.cs +++ b/Src/RCommon.Models/SearchPaginatedListRequest.cs @@ -6,7 +6,7 @@ namespace RCommon.Models { - public class SearchPaginatedListRequest : PaginatedListRequest, ISearchPaginatedListRequest + public record SearchPaginatedListRequest : PaginatedListRequest, ISearchPaginatedListRequest { public SearchPaginatedListRequest() { diff --git a/Src/RCommon.Persistence.Caching.MemoryCache/IPersistenceBuilderExtensions.cs b/Src/RCommon.Persistence.Caching.MemoryCache/IPersistenceBuilderExtensions.cs new file mode 100644 index 00000000..5e5a7749 --- /dev/null +++ b/Src/RCommon.Persistence.Caching.MemoryCache/IPersistenceBuilderExtensions.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; +using RCommon.MemoryCache; +using RCommon.Persistence.Caching.Crud; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.MemoryCache +{ + public static class IPersistenceBuilderExtensions + { + public static void AddInMemoryPersistenceCaching(this IPersistenceBuilder builder) + { + // Add Caching services + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient, CommonFactory>(); + ConfigureCachingOptions(builder); + + // Add Caching Factory + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case PersistenceCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + + } + + public static void AddDistributedMemoryPersistenceCaching(this IPersistenceBuilder builder) + { + // Add Caching services + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient, CommonFactory>(); + ConfigureCachingOptions(builder); + + // Add Caching Factory + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case PersistenceCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + } + + private static void ConfigureCachingOptions(IPersistenceBuilder builder, Action configure = null) + { + // Add Caching repositories + builder.Services.TryAddTransient(typeof(ICachingGraphRepository<>), typeof(CachingGraphRepository<>)); + builder.Services.TryAddTransient(typeof(ICachingLinqRepository<>), typeof(CachingLinqRepository<>)); + builder.Services.TryAddTransient(typeof(ICachingSqlMapperRepository<>), typeof(CachingSqlMapperRepository<>)); + + if ( configure == null) + { + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + } + else + { + builder.Services.Configure(configure); + } + + } + } +} diff --git a/Src/RCommon.Persistence.Caching.MemoryCache/RCommon.Persistence.Caching.MemoryCache.csproj b/Src/RCommon.Persistence.Caching.MemoryCache/RCommon.Persistence.Caching.MemoryCache.csproj new file mode 100644 index 00000000..249eaf00 --- /dev/null +++ b/Src/RCommon.Persistence.Caching.MemoryCache/RCommon.Persistence.Caching.MemoryCache.csproj @@ -0,0 +1,14 @@ + + + + net8.0; + enable + enable + + + + + + + + diff --git a/Src/RCommon.Persistence.Caching.RedisCache/IPersistenceBuilderExtensions.cs b/Src/RCommon.Persistence.Caching.RedisCache/IPersistenceBuilderExtensions.cs new file mode 100644 index 00000000..cefc079c --- /dev/null +++ b/Src/RCommon.Persistence.Caching.RedisCache/IPersistenceBuilderExtensions.cs @@ -0,0 +1,60 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; +using RCommon.Persistence.Caching.Crud; +using RCommon.RedisCache; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.RedisCache +{ + public static class IPersistenceBuilderExtensions + { + + public static void AddRedisPersistenceCaching(this IPersistenceBuilder builder) + { + // Add Caching repositories + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient, CommonFactory>(); + ConfigureCachingOptions(builder); + + // Add Caching services + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case PersistenceCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + } + + private static void ConfigureCachingOptions(IPersistenceBuilder builder, Action configure = null) + { + // Add Caching repositories + builder.Services.TryAddTransient(typeof(ICachingGraphRepository<>), typeof(CachingGraphRepository<>)); + builder.Services.TryAddTransient(typeof(ICachingLinqRepository<>), typeof(CachingLinqRepository<>)); + builder.Services.TryAddTransient(typeof(ICachingSqlMapperRepository<>), typeof(CachingSqlMapperRepository<>)); + + if (configure == null) + { + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + } + else + { + builder.Services.Configure(configure); + } + + } + } +} diff --git a/Src/RCommon.Persistence.Caching.RedisCache/RCommon.Persistence.Caching.RedisCache.csproj b/Src/RCommon.Persistence.Caching.RedisCache/RCommon.Persistence.Caching.RedisCache.csproj new file mode 100644 index 00000000..66d5ef84 --- /dev/null +++ b/Src/RCommon.Persistence.Caching.RedisCache/RCommon.Persistence.Caching.RedisCache.csproj @@ -0,0 +1,14 @@ + + + + net8.0; + enable + enable + + + + + + + + diff --git a/Src/RCommon.Persistence.Caching/Crud/CachingGraphRepository.cs b/Src/RCommon.Persistence.Caching/Crud/CachingGraphRepository.cs new file mode 100644 index 00000000..c91b88b9 --- /dev/null +++ b/Src/RCommon.Persistence.Caching/Crud/CachingGraphRepository.cs @@ -0,0 +1,180 @@ +using RCommon.Caching; +using RCommon.Collections; +using RCommon.Entities; +using RCommon.Persistence.Crud; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.Crud +{ + public class CachingGraphRepository : ICachingGraphRepository + where TEntity : class, IBusinessEntity + { + private readonly IGraphRepository _repository; + private readonly ICacheService _cacheService; + + public CachingGraphRepository(IGraphRepository repository, ICommonFactory cacheFactory) + { + _repository = repository; + _cacheService = cacheFactory.Create(PersistenceCachingStrategy.Default); + } + + public bool Tracking { get => _repository.Tracking; set => _repository.Tracking = value; } + + public Type ElementType => _repository.ElementType; + + public Expression Expression => _repository.Expression; + + public IQueryProvider Provider => _repository.Provider; + + public string DataStoreName { get => _repository.DataStoreName; set => _repository.DataStoreName = value; } + + public async Task AddAsync(TEntity entity, CancellationToken token = default) + { + await _repository.AddAsync(entity, token); + } + + public async Task AnyAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.AnyAsync(expression, token); + } + + public async Task AnyAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.AnyAsync(specification, token); + } + + public async Task DeleteAsync(TEntity entity, CancellationToken token = default) + { + await _repository.DeleteAsync(entity, token); + } + + public async Task FindAsync(object primaryKey, CancellationToken token = default) + { + return await _repository.FindAsync(primaryKey, token); + } + + public IQueryable FindQuery(ISpecification specification) + { + return _repository.FindQuery(specification); + } + + public IQueryable FindQuery(Expression> expression) + { + return _repository.FindQuery(expression); + } + + public IQueryable FindQuery(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0) + { + return _repository.FindQuery(expression, orderByExpression, orderByAscending, pageNumber, pageSize); + } + + public IQueryable FindQuery(IPagedSpecification specification) + { + return _repository.FindQuery(specification); + } + + public async Task FindSingleOrDefaultAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.FindSingleOrDefaultAsync(expression, token); + } + + public async Task FindSingleOrDefaultAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.FindSingleOrDefaultAsync(specification, token); + } + + public async Task GetCountAsync(ISpecification selectSpec, CancellationToken token = default) + { + return await _repository.GetCountAsync(selectSpec, token); + } + + public async Task GetCountAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.GetCountAsync(expression, token); + } + + public IEnumerator GetEnumerator() + { + return _repository.GetEnumerator(); + } + + public IEagerLoadableQueryable Include(Expression> path) + { + return _repository.Include(path); + } + + public IEagerLoadableQueryable ThenInclude(Expression> path) + { + return _repository.ThenInclude(path); + } + + public async Task UpdateAsync(TEntity entity, CancellationToken token = default) + { + await _repository.UpdateAsync(entity, token); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _repository.GetEnumerator(); + } + + public async Task> FindAsync(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) + { + return await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); + } + + public async Task> FindAsync(IPagedSpecification specification, CancellationToken token = default) + { + return await _repository.FindAsync(specification, token); + } + + public async Task> FindAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.FindAsync(specification, token); + } + + public async Task> FindAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.FindAsync(expression, token); + } + + // Cached items + + public async Task> FindAsync(object cacheKey, Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token)); + return await data; + } + + public async Task> FindAsync(object cacheKey, IPagedSpecification specification, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(specification, token)); + return await data; + } + + public async Task> FindAsync(object cacheKey, ISpecification specification, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(specification, token)); + return await data; + } + + public async Task> FindAsync(object cacheKey, Expression> expression, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(expression, token)); + return await data; + } + } +} diff --git a/Src/RCommon.Persistence.Caching/Crud/CachingLinqRepository.cs b/Src/RCommon.Persistence.Caching/Crud/CachingLinqRepository.cs new file mode 100644 index 00000000..d697ef5a --- /dev/null +++ b/Src/RCommon.Persistence.Caching/Crud/CachingLinqRepository.cs @@ -0,0 +1,178 @@ +using RCommon.Caching; +using RCommon.Collections; +using RCommon.Entities; +using RCommon.Persistence.Crud; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.Crud +{ + public class CachingLinqRepository : ICachingLinqRepository + where TEntity : class, IBusinessEntity + { + private readonly IGraphRepository _repository; + private readonly ICacheService _cacheService; + + public CachingLinqRepository(IGraphRepository repository, ICommonFactory cacheFactory) + { + _repository = repository; + _cacheService = cacheFactory.Create(PersistenceCachingStrategy.Default); + } + + public Type ElementType => _repository.ElementType; + + public Expression Expression => _repository.Expression; + + public IQueryProvider Provider => _repository.Provider; + + public string DataStoreName { get => _repository.DataStoreName; set => _repository.DataStoreName = value; } + + public async Task AddAsync(TEntity entity, CancellationToken token = default) + { + await _repository.AddAsync(entity, token); + } + + public async Task AnyAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.AnyAsync(expression, token); + } + + public async Task AnyAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.AnyAsync(specification, token); + } + + public async Task DeleteAsync(TEntity entity, CancellationToken token = default) + { + await _repository.DeleteAsync(entity, token); + } + + public async Task FindAsync(object primaryKey, CancellationToken token = default) + { + return await _repository.FindAsync(primaryKey, token); + } + + public IQueryable FindQuery(ISpecification specification) + { + return _repository.FindQuery(specification); + } + + public IQueryable FindQuery(Expression> expression) + { + return _repository.FindQuery(expression); + } + + public IQueryable FindQuery(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0) + { + return _repository.FindQuery(expression, orderByExpression, orderByAscending, pageNumber, pageSize); + } + + public IQueryable FindQuery(IPagedSpecification specification) + { + return _repository.FindQuery(specification); + } + + public async Task FindSingleOrDefaultAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.FindSingleOrDefaultAsync(expression, token); + } + + public async Task FindSingleOrDefaultAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.FindSingleOrDefaultAsync(specification, token); + } + + public async Task GetCountAsync(ISpecification selectSpec, CancellationToken token = default) + { + return await _repository.GetCountAsync(selectSpec, token); + } + + public async Task GetCountAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.GetCountAsync(expression, token); + } + + public IEnumerator GetEnumerator() + { + return _repository.GetEnumerator(); + } + + public IEagerLoadableQueryable Include(Expression> path) + { + return _repository.Include(path); + } + + public IEagerLoadableQueryable ThenInclude(Expression> path) + { + return _repository.ThenInclude(path); + } + + public async Task UpdateAsync(TEntity entity, CancellationToken token = default) + { + await _repository.UpdateAsync(entity, token); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return _repository.GetEnumerator(); + } + + public async Task> FindAsync(Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) + { + return await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token); + } + + public async Task> FindAsync(IPagedSpecification specification, CancellationToken token = default) + { + return await _repository.FindAsync(specification, token); + } + + public async Task> FindAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.FindAsync(specification, token); + } + + public async Task> FindAsync(Expression> expression, CancellationToken token = default) + { + return await _repository.FindAsync(expression, token); + } + + // Cached items + + public async Task> FindAsync(object cacheKey, Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(expression, orderByExpression, orderByAscending, pageNumber, pageSize, token)); + return await data; + } + + public async Task> FindAsync(object cacheKey, IPagedSpecification specification, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(specification, token)); + return await data; + } + + public async Task> FindAsync(object cacheKey, ISpecification specification, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(specification, token)); + return await data; + } + + public async Task> FindAsync(object cacheKey, Expression> expression, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(expression, token)); + return await data; + } + } +} diff --git a/Src/RCommon.Persistence.Caching/Crud/CachingSqlMapperRepository.cs b/Src/RCommon.Persistence.Caching/Crud/CachingSqlMapperRepository.cs new file mode 100644 index 00000000..c46fb46e --- /dev/null +++ b/Src/RCommon.Persistence.Caching/Crud/CachingSqlMapperRepository.cs @@ -0,0 +1,104 @@ +using RCommon.Caching; +using RCommon.Entities; +using RCommon.Persistence.Crud; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.Crud +{ + public class CachingSqlMapperRepository : ICachingSqlMapperRepository + where TEntity : class, IBusinessEntity + { + private readonly ISqlMapperRepository _repository; + private readonly ICacheService _cacheService; + + public CachingSqlMapperRepository(ISqlMapperRepository repository, ICommonFactory cacheFactory) + { + _repository = repository; + _cacheService = cacheFactory.Create(PersistenceCachingStrategy.Default); + } + + public string TableName { get => _repository.TableName; set => _repository.TableName = value; } + public string DataStoreName { get => _repository.DataStoreName; set => _repository.DataStoreName = value; } + + public async Task AddAsync(TEntity entity, CancellationToken token = default) + { + await _repository.AddAsync(entity, token); + } + + public async Task AnyAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + return await _repository.AnyAsync(expression, token); + } + + public async Task AnyAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.AnyAsync(specification, token); + } + + public async Task DeleteAsync(TEntity entity, CancellationToken token = default) + { + await _repository.DeleteAsync(entity, token); + } + + public async Task> FindAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.FindAsync(specification, token); + } + + public async Task> FindAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + return await _repository.FindAsync(expression, token); + } + + public async Task FindAsync(object primaryKey, CancellationToken token = default) + { + return await _repository.FindAsync(primaryKey, token); + } + + public async Task FindSingleOrDefaultAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + return await _repository.FindSingleOrDefaultAsync(expression, token); + } + + public async Task FindSingleOrDefaultAsync(ISpecification specification, CancellationToken token = default) + { + return await _repository.FindSingleOrDefaultAsync(specification, token); + } + + public async Task GetCountAsync(ISpecification selectSpec, CancellationToken token = default) + { + return await _repository.GetCountAsync(selectSpec, token); + } + + public async Task GetCountAsync(System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + return await _repository.GetCountAsync(expression, token); + } + + public async Task UpdateAsync(TEntity entity, CancellationToken token = default) + { + await _repository.UpdateAsync(entity, token); + } + + // Cached Items + + public async Task> FindAsync(object cacheKey, ISpecification specification, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(specification, token)); + return await data; + } + + public async Task> FindAsync(object cacheKey, System.Linq.Expressions.Expression> expression, CancellationToken token = default) + { + var data = await _cacheService.GetOrCreateAsync(cacheKey, + async () => await _repository.FindAsync(expression, token)); + return await data; + } + } +} diff --git a/Src/RCommon.Persistence.Caching/Crud/ICachingGraphRepository.cs b/Src/RCommon.Persistence.Caching/Crud/ICachingGraphRepository.cs new file mode 100644 index 00000000..d6d642ba --- /dev/null +++ b/Src/RCommon.Persistence.Caching/Crud/ICachingGraphRepository.cs @@ -0,0 +1,22 @@ +using RCommon.Collections; +using RCommon.Entities; +using RCommon.Persistence.Crud; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.Crud +{ + public interface ICachingGraphRepository : IGraphRepository + where TEntity : class, IBusinessEntity + { + Task> FindAsync(object cacheKey, Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default); + Task> FindAsync(object cacheKey, IPagedSpecification specification, CancellationToken token = default); + Task> FindAsync(object cacheKey, ISpecification specification, CancellationToken token = default); + Task> FindAsync(object cacheKey, Expression> expression, CancellationToken token = default); + } +} diff --git a/Src/RCommon.Persistence.Caching/Crud/ICachingLinqRepository.cs b/Src/RCommon.Persistence.Caching/Crud/ICachingLinqRepository.cs new file mode 100644 index 00000000..000f09a2 --- /dev/null +++ b/Src/RCommon.Persistence.Caching/Crud/ICachingLinqRepository.cs @@ -0,0 +1,22 @@ +using RCommon.Collections; +using RCommon.Entities; +using RCommon.Persistence.Crud; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.Crud +{ + public interface ICachingLinqRepository : ILinqRepository + where TEntity : class, IBusinessEntity + { + Task> FindAsync(object cacheKey, Expression> expression, Expression> orderByExpression, bool orderByAscending, int pageNumber = 1, int pageSize = 0, CancellationToken token = default); + Task> FindAsync(object cacheKey, IPagedSpecification specification, CancellationToken token = default); + Task> FindAsync(object cacheKey, ISpecification specification, CancellationToken token = default); + Task> FindAsync(object cacheKey, Expression> expression, CancellationToken token = default); + } +} diff --git a/Src/RCommon.Persistence.Caching/Crud/ICachingSqlMapperRepository.cs b/Src/RCommon.Persistence.Caching/Crud/ICachingSqlMapperRepository.cs new file mode 100644 index 00000000..8596b525 --- /dev/null +++ b/Src/RCommon.Persistence.Caching/Crud/ICachingSqlMapperRepository.cs @@ -0,0 +1,19 @@ +using RCommon.Entities; +using RCommon.Persistence.Crud; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching.Crud +{ + public interface ICachingSqlMapperRepository : ISqlMapperRepository + where TEntity : class, IBusinessEntity + { + Task> FindAsync(object cacheKey, ISpecification specification, CancellationToken token = default); + Task> FindAsync(object cacheKey, Expression> expression, CancellationToken token = default); + } +} diff --git a/Src/RCommon.Persistence.Caching/IPersistenceBuilderExtensions.cs b/Src/RCommon.Persistence.Caching/IPersistenceBuilderExtensions.cs new file mode 100644 index 00000000..06096e7d --- /dev/null +++ b/Src/RCommon.Persistence.Caching/IPersistenceBuilderExtensions.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; +using RCommon.Persistence.Caching.Crud; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching +{ + public static class IPersistenceBuilderExtensions + { + public static void AddPersistenceCaching(this IPersistenceBuilder builder, Func> cacheFactory) + { + // Add caching services + builder.Services.TryAddTransient>(cacheFactory); + builder.Services.TryAddTransient, CommonFactory>(); + + // Add Caching repositories + builder.Services.TryAddTransient(typeof(ICachingGraphRepository<>), typeof(CachingGraphRepository<>)); + builder.Services.TryAddTransient(typeof(ICachingLinqRepository<>), typeof(CachingLinqRepository<>)); + builder.Services.TryAddTransient(typeof(ICachingSqlMapperRepository<>), typeof(CachingSqlMapperRepository<>)); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + } + } +} diff --git a/Src/RCommon.Persistence.Caching/PersistenceCachingStrategy.cs b/Src/RCommon.Persistence.Caching/PersistenceCachingStrategy.cs new file mode 100644 index 00000000..0897394d --- /dev/null +++ b/Src/RCommon.Persistence.Caching/PersistenceCachingStrategy.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Persistence.Caching +{ + public enum PersistenceCachingStrategy + { + Default + } +} diff --git a/Src/RCommon.Persistence.Caching/RCommon.Persistence.Caching.csproj b/Src/RCommon.Persistence.Caching/RCommon.Persistence.Caching.csproj new file mode 100644 index 00000000..51fba86c --- /dev/null +++ b/Src/RCommon.Persistence.Caching/RCommon.Persistence.Caching.csproj @@ -0,0 +1,12 @@ + + + + net8.0; + + + + + + + + diff --git a/Src/RCommon.Persistence/Crud/IGraphRepository.cs b/Src/RCommon.Persistence/Crud/IGraphRepository.cs index 54b763b8..2791f0e4 100644 --- a/Src/RCommon.Persistence/Crud/IGraphRepository.cs +++ b/Src/RCommon.Persistence/Crud/IGraphRepository.cs @@ -10,7 +10,7 @@ namespace RCommon.Persistence.Crud { - public interface IGraphRepository : ILinqRepository, IEagerLoadableQueryable + public interface IGraphRepository : ILinqRepository where TEntity : class, IBusinessEntity { diff --git a/Src/RCommon.Persistence/DataStoreFactory.cs b/Src/RCommon.Persistence/DataStoreFactory.cs index aae8dac3..74f970a4 100644 --- a/Src/RCommon.Persistence/DataStoreFactory.cs +++ b/Src/RCommon.Persistence/DataStoreFactory.cs @@ -2,6 +2,7 @@ using Microsoft.Extensions.Options; using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -12,33 +13,36 @@ namespace RCommon.Persistence public class DataStoreFactory : IDataStoreFactory { private readonly IServiceProvider _provider; - private readonly IDictionary _types; + private readonly ConcurrentBag _values; public DataStoreFactory(IServiceProvider provider, IOptions options) { _provider = provider; - _types = options.Value.Types; + _values = options.Value.Values; } - public IDataStore Resolve(string name) + public C Resolve(string name) + where B : IDataStore + where C : B, IDataStore { - if (_types.TryGetValue(name, out var type)) + DataStoreValue value = new DataStoreValue(name, typeof(B), typeof(C)); + if (_values.TryPeek(out value)) { - return (IDataStore)_provider.GetRequiredService(type); + return (C)_provider.GetRequiredService(value.ConcreteType); } throw new DataStoreNotFoundException($"DataStore with name of {name} not found"); } - public TDataStore Resolve(string name) - where TDataStore : IDataStore + public B Resolve(string name) + where B : IDataStore { - if (_types.TryGetValue(name, out var type)) + if (_values.Any(x => x.Name == name && x.BaseType == typeof(B))) { - return (TDataStore)_provider.GetRequiredService(type); + return (B)_provider.GetRequiredService(_values.First(x => x.Name == name && x.BaseType == typeof(B)).ConcreteType); } - throw new DataStoreNotFoundException($"DataStore with name of {name} not found"); + throw new DataStoreNotFoundException($"DataStore with name of {name} and base type of {typeof(B).GetGenericTypeName()} not found"); } } } diff --git a/Src/RCommon.Persistence/DataStoreFactoryOptions.cs b/Src/RCommon.Persistence/DataStoreFactoryOptions.cs index 0fd73c6e..df0b174b 100644 --- a/Src/RCommon.Persistence/DataStoreFactoryOptions.cs +++ b/Src/RCommon.Persistence/DataStoreFactoryOptions.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,11 +10,20 @@ namespace RCommon.Persistence { public class DataStoreFactoryOptions { - public IDictionary Types { get; } = new Dictionary(); + public ConcurrentBag Values { get; } = new ConcurrentBag(); - public void Register(string name) where T : IDataStore + public void Register(string name) + where B : IDataStore + where C : IDataStore { - Types.Add(name, typeof(T)); + if (!Values.Any(x => x.Name == name && x.BaseType == typeof(B))) + { + Values.Add(new DataStoreValue(name, typeof(B), typeof(C))); + } + else + { + throw new UnsupportedDataStoreException($"You cannot register a data store with the same name of {name} as an existing one with the same base type of {typeof(B).GetGenericTypeName()}"); + } } } } diff --git a/Src/RCommon.Persistence/DataStoreValue.cs b/Src/RCommon.Persistence/DataStoreValue.cs new file mode 100644 index 00000000..360c9943 --- /dev/null +++ b/Src/RCommon.Persistence/DataStoreValue.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.Persistence +{ + public class DataStoreValue + { + public DataStoreValue(string name, Type baseType, Type concreteType) + { + Name = name; + BaseType = baseType; + ConcreteType = concreteType; + + if (concreteType.BaseType != baseType) + { + throw new UnsupportedDataStoreException($"Concrete type must implement base type."); + } + } + + public string Name { get; } + public Type BaseType { get; } + public Type ConcreteType { get; } + } +} diff --git a/Src/RCommon.Persistence/IDataStoreFactory.cs b/Src/RCommon.Persistence/IDataStoreFactory.cs index 1dea9318..2eb5748d 100644 --- a/Src/RCommon.Persistence/IDataStoreFactory.cs +++ b/Src/RCommon.Persistence/IDataStoreFactory.cs @@ -1,9 +1,28 @@ namespace RCommon.Persistence { + /// + /// Abstraction for allowing repositories to find Data Stores + /// public interface IDataStoreFactory { - IDataStore Resolve(string name); - TDataStore Resolve(string name) - where TDataStore : IDataStore; + /// + /// Resolves a data store value to it's concrete type + /// + /// Name of base type used for + /// Name of concrete type used for + /// Name of + /// Concrete Type of + /// The combination of name and base type will be unique and there should not be duplicates + C Resolve(string name) + where B : IDataStore + where C : IDataStore, B; + /// + /// Resolves a data store value to it's base type + /// + /// Name of base type used for + /// Name of + /// Base Type of + /// The combination of name and base type will be unique and there should not be duplicates + B Resolve(string name) where B : IDataStore; } } diff --git a/Src/RCommon.Persistence/IPersistenceBuilder.cs b/Src/RCommon.Persistence/IPersistenceBuilder.cs index 93266315..740a897f 100644 --- a/Src/RCommon.Persistence/IPersistenceBuilder.cs +++ b/Src/RCommon.Persistence/IPersistenceBuilder.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.DependencyInjection; using System; namespace RCommon @@ -8,5 +9,6 @@ namespace RCommon public interface IPersistenceBuilder { IPersistenceBuilder SetDefaultDataStore(Action options); + IServiceCollection Services { get; } } } diff --git a/Src/RCommon.Persistence/PersistenceBuilderExtensions.cs b/Src/RCommon.Persistence/PersistenceBuilderExtensions.cs index 147c2895..6b179962 100644 --- a/Src/RCommon.Persistence/PersistenceBuilderExtensions.cs +++ b/Src/RCommon.Persistence/PersistenceBuilderExtensions.cs @@ -15,7 +15,52 @@ namespace RCommon { public static class PersistenceBuilderExtensions { + public static IRCommonBuilder WithPersistence(this IRCommonBuilder builder) + where TObjectAccess : IPersistenceBuilder + { + return WithPersistence(builder, x => { }); + } + + public static IRCommonBuilder WithPersistence(this IRCommonBuilder builder, Action objectAccessActions) + where TObjectAccess : IPersistenceBuilder + { + var dataConfiguration = (TObjectAccess)Activator.CreateInstance(typeof(TObjectAccess), new object[] { builder.Services }); + objectAccessActions(dataConfiguration); + builder = WithEventTracking(builder); + return builder; + } + + public static IRCommonBuilder WithUnitOfWork(this IRCommonBuilder builder, Action unitOfWorkActions) + where TUnitOfWork : IUnitOfWorkBuilder + { + var unitOfWorkConfiguration = (TUnitOfWork)Activator.CreateInstance(typeof(TUnitOfWork), new object[] { builder.Services }); + unitOfWorkActions(unitOfWorkConfiguration); + return builder; + } + + /// + /// Right now we are always using change tracking due to requirements for publishing entity events and those events being + /// somewhat tied to Change Tracking. + /// + /// Instance of passed in. + /// Updated instance of RCommon Configuration + private static IRCommonBuilder WithEventTracking(this IRCommonBuilder builder) + { + builder.Services.AddScoped(); + builder.Services.AddScoped(); + return builder; + } + // Deprecated + /// + /// Deprecated. Use or + /// and if unit of work is required then use in conjuction with + /// + /// + /// + /// + /// + [Obsolete("This is deprecated as peristence is decoupled from unit of work.")] public static IRCommonBuilder WithPersistence(this IRCommonBuilder builder) where TObjectAccess: IPersistenceBuilder where TUnitOfWork : IUnitOfWorkBuilder @@ -23,6 +68,16 @@ public static IRCommonBuilder WithPersistence(this I return WithPersistence(builder, x => { }, x => { }); } + /// + /// Deprecated. Use or + /// and if unit of work is required then use in conjuction with + /// + /// + /// + /// + /// + /// + [Obsolete("This is deprecated as peristence is decoupled from unit of work.")] public static IRCommonBuilder WithPersistence(this IRCommonBuilder builder, Action objectAccessActions) where TObjectAccess : IPersistenceBuilder @@ -31,6 +86,16 @@ public static IRCommonBuilder WithPersistence(this I return WithPersistence(builder, objectAccessActions, x => { }); } + /// + /// Deprecated. Use or + /// and if unit of work is required then use in conjuction with + /// + /// + /// + /// + /// + /// + [Obsolete("This is deprecated as peristence is decoupled from unit of work.")] public static IRCommonBuilder WithPersistence(this IRCommonBuilder builder, Action uniOfWorkActions) where TObjectAccess : IPersistenceBuilder @@ -39,6 +104,17 @@ public static IRCommonBuilder WithPersistence(this I return WithPersistence(builder, x => { }, uniOfWorkActions); } + /// + /// Deprecated. Use or + /// and if unit of work is required then use in conjuction with + /// + /// + /// + /// + /// + /// + /// + [Obsolete("This is deprecated as peristence is decoupled from unit of work.")] public static IRCommonBuilder WithPersistence(this IRCommonBuilder builder, Action objectAccessActions, Action unitOfWorkActions) where TObjectAccess : IPersistenceBuilder @@ -51,22 +127,5 @@ public static IRCommonBuilder WithPersistence(this I builder = WithEventTracking(builder); return builder; } - - - /// - /// Right now we are always using change tracking due to requirements for publishing entity events and those events being - /// somewhat tied to Change Tracking. - /// - /// Instance of passed in. - /// Updated instance of RCommon Configuration - private static IRCommonBuilder WithEventTracking(this IRCommonBuilder builder) - { - builder.Services.AddScoped(); - builder.Services.AddScoped(); - return builder; - } - - - } } diff --git a/Src/RCommon.Persistence/RCommon.Persistence.csproj b/Src/RCommon.Persistence/RCommon.Persistence.csproj index 1744ad5d..f8dec7f2 100644 --- a/Src/RCommon.Persistence/RCommon.Persistence.csproj +++ b/Src/RCommon.Persistence/RCommon.Persistence.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0; + net8.0; @@ -9,19 +9,6 @@ - - - - - - - - - - - - - diff --git a/Src/RCommon.RedisCache/IRedisCachingBuilder.cs b/Src/RCommon.RedisCache/IRedisCachingBuilder.cs new file mode 100644 index 00000000..963e861a --- /dev/null +++ b/Src/RCommon.RedisCache/IRedisCachingBuilder.cs @@ -0,0 +1,13 @@ +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.RedisCache +{ + public interface IRedisCachingBuilder : IDistributedCachingBuilder + { + } +} diff --git a/Src/RCommon.RedisCache/IRedisCachingBuilderExtensions.cs b/Src/RCommon.RedisCache/IRedisCachingBuilderExtensions.cs new file mode 100644 index 00000000..32427920 --- /dev/null +++ b/Src/RCommon.RedisCache/IRedisCachingBuilderExtensions.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.Caching.StackExchangeRedis; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.RedisCache +{ + public static class IRedisCachingBuilderExtensions + { + public static IRedisCachingBuilder Configure(this IRedisCachingBuilder builder, Action actions) + { + builder.Services.AddStackExchangeRedisCache(actions); + return builder; + } + + /// + /// This greatly improves performance across various areas of RCommon which use generics and reflection heavily + /// to compile expressions and lambdas + /// + /// Builder + /// Same builder to allow chaining + /// The most performant way to do this is through InMemoryCache but this works fine + public static IRedisCachingBuilder CacheDynamicallyCompiledExpressions(this IRedisCachingBuilder builder) + { + builder.Services.TryAddTransient(); + builder.Services.TryAddTransient, CommonFactory>(); + + builder.Services.Configure(x => + { + x.CachingEnabled = true; + x.CacheDynamicallyCompiledExpressions = true; + }); + builder.Services.TryAddTransient>(serviceProvider => strategy => + { + switch (strategy) + { + case ExpressionCachingStrategy.Default: + return serviceProvider.GetService(); + default: + return serviceProvider.GetService(); + } + }); + + return builder; + } + } +} diff --git a/Src/RCommon.RedisCache/RCommon.RedisCache.csproj b/Src/RCommon.RedisCache/RCommon.RedisCache.csproj new file mode 100644 index 00000000..b1e58d86 --- /dev/null +++ b/Src/RCommon.RedisCache/RCommon.RedisCache.csproj @@ -0,0 +1,16 @@ + + + + net8.0; + + + + + + + + + + + + diff --git a/Src/RCommon.RedisCache/RedisCacheService.cs b/Src/RCommon.RedisCache/RedisCacheService.cs new file mode 100644 index 00000000..2e89bf59 --- /dev/null +++ b/Src/RCommon.RedisCache/RedisCacheService.cs @@ -0,0 +1,57 @@ +using Microsoft.Extensions.Caching.Distributed; +using RCommon.Caching; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.RedisCache +{ + /// + /// Just a wrapper for Redis data caching implemented through caching abstractions + /// + /// This gives us a uniform way for getting/setting cache no matter the caching strategy + public class RedisCacheService : ICacheService + { + private readonly IDistributedCache _distributedCache; + private readonly IJsonSerializer _jsonSerializer; + + public RedisCacheService(IDistributedCache distributedCache, IJsonSerializer jsonSerializer) + { + _distributedCache = distributedCache; + _jsonSerializer = jsonSerializer; + } + + public TData GetOrCreate(object key, Func data) + { + var json = _distributedCache.GetString(key.ToString()); + + if (json == null) + { + _distributedCache.SetString(key.ToString(), _jsonSerializer.Serialize(data())); + return data(); + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + + public async Task GetOrCreateAsync(object key, Func data) + { + var json = await _distributedCache.GetStringAsync(key.ToString()).ConfigureAwait(false); + + if (json == null) + { + await _distributedCache.SetStringAsync(key.ToString(), _jsonSerializer.Serialize(data())).ConfigureAwait(false); + return data(); + } + else + { + return _jsonSerializer.Deserialize(json); + } + } + } +} diff --git a/Src/RCommon.RedisCache/RedisCachingBuilder.cs b/Src/RCommon.RedisCache/RedisCachingBuilder.cs new file mode 100644 index 00000000..a0adf641 --- /dev/null +++ b/Src/RCommon.RedisCache/RedisCachingBuilder.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using RCommon.Caching; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.RedisCache +{ + public class RedisCachingBuilder : IRedisCachingBuilder + { + public RedisCachingBuilder(IRCommonBuilder builder) + { + Services = builder.Services; + this.RegisterServices(Services); + } + + protected void RegisterServices(IServiceCollection services) + { + + } + + public IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.Security/RCommon.Security.csproj b/Src/RCommon.Security/RCommon.Security.csproj index f77be10d..15520891 100644 --- a/Src/RCommon.Security/RCommon.Security.csproj +++ b/Src/RCommon.Security/RCommon.Security.csproj @@ -1,22 +1,11 @@  - net6.0;net7.0;net8.0; + net8.0; - - - - - - - - - - - - + diff --git a/Src/RCommon.SendGrid/RCommon.SendGrid.csproj b/Src/RCommon.SendGrid/RCommon.SendGrid.csproj index c4c9b662..c5e89775 100644 --- a/Src/RCommon.SendGrid/RCommon.SendGrid.csproj +++ b/Src/RCommon.SendGrid/RCommon.SendGrid.csproj @@ -1,7 +1,7 @@  - net6.0;net7.0;net8.0; + net8.0; enable enable diff --git a/Src/RCommon.SystemTextJson/ITextJsonBuilder.cs b/Src/RCommon.SystemTextJson/ITextJsonBuilder.cs new file mode 100644 index 00000000..539c7b15 --- /dev/null +++ b/Src/RCommon.SystemTextJson/ITextJsonBuilder.cs @@ -0,0 +1,13 @@ +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.SystemTextJson +{ + public interface ITextJsonBuilder : IJsonBuilder + { + } +} diff --git a/Src/RCommon.SystemTextJson/ITextJsonBuilderExtensions.cs b/Src/RCommon.SystemTextJson/ITextJsonBuilderExtensions.cs new file mode 100644 index 00000000..0e8f5cc8 --- /dev/null +++ b/Src/RCommon.SystemTextJson/ITextJsonBuilderExtensions.cs @@ -0,0 +1,21 @@ +using Microsoft.Extensions.DependencyInjection; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.AccessControl; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace RCommon.SystemTextJson +{ + public static class ITextJsonBuilderExtensions + { + public static ITextJsonBuilder Configure(this ITextJsonBuilder builder, Action options) + { + builder.Services.Configure(options); + return builder; + } + } +} diff --git a/Src/RCommon.Core/Serialization/Json/JsonByteEnumConverter.cs b/Src/RCommon.SystemTextJson/JsonByteEnumConverter.cs similarity index 95% rename from Src/RCommon.Core/Serialization/Json/JsonByteEnumConverter.cs rename to Src/RCommon.SystemTextJson/JsonByteEnumConverter.cs index 08a33835..af29da0f 100644 --- a/Src/RCommon.Core/Serialization/Json/JsonByteEnumConverter.cs +++ b/Src/RCommon.SystemTextJson/JsonByteEnumConverter.cs @@ -6,7 +6,7 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; -namespace RCommon.Serialization.Json +namespace RCommon.SystemTextJson { public class JsonByteEnumConverter : JsonConverter where T : Enum { diff --git a/Src/RCommon.Core/Serialization/Json/JsonIntEnumConverter.cs b/Src/RCommon.SystemTextJson/JsonIntEnumConverter.cs similarity index 95% rename from Src/RCommon.Core/Serialization/Json/JsonIntEnumConverter.cs rename to Src/RCommon.SystemTextJson/JsonIntEnumConverter.cs index 4e8934db..0983829a 100644 --- a/Src/RCommon.Core/Serialization/Json/JsonIntEnumConverter.cs +++ b/Src/RCommon.SystemTextJson/JsonIntEnumConverter.cs @@ -6,7 +6,7 @@ using System.Text.Json.Serialization; using System.Threading.Tasks; -namespace RCommon.Serialization.Json +namespace RCommon.SystemTextJson { public class JsonIntEnumConverter : JsonConverter where T : Enum { diff --git a/Src/RCommon.SystemTextJson/RCommon.SystemTextJson.csproj b/Src/RCommon.SystemTextJson/RCommon.SystemTextJson.csproj new file mode 100644 index 00000000..d3eedea2 --- /dev/null +++ b/Src/RCommon.SystemTextJson/RCommon.SystemTextJson.csproj @@ -0,0 +1,15 @@ + + + + net8.0; + + + + + + + + + + + diff --git a/Src/RCommon.SystemTextJson/TextJsonBuilder.cs b/Src/RCommon.SystemTextJson/TextJsonBuilder.cs new file mode 100644 index 00000000..8774482c --- /dev/null +++ b/Src/RCommon.SystemTextJson/TextJsonBuilder.cs @@ -0,0 +1,26 @@ +using Microsoft.Extensions.DependencyInjection; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace RCommon.SystemTextJson +{ + public class TextJsonBuilder : ITextJsonBuilder + { + public TextJsonBuilder(IRCommonBuilder builder) + { + Services = builder.Services; + this.RegisterServices(Services); + } + + protected void RegisterServices(IServiceCollection services) + { + services.AddTransient(); + } + + public IServiceCollection Services { get; } + } +} diff --git a/Src/RCommon.SystemTextJson/TextJsonSerializer.cs b/Src/RCommon.SystemTextJson/TextJsonSerializer.cs new file mode 100644 index 00000000..75fd91c9 --- /dev/null +++ b/Src/RCommon.SystemTextJson/TextJsonSerializer.cs @@ -0,0 +1,77 @@ +using Microsoft.Extensions.Options; +using RCommon.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace RCommon.SystemTextJson +{ + public class TextJsonSerializer : IJsonSerializer + { + private JsonSerializerOptions _jsonOptions; + + public TextJsonSerializer(IOptions options) + { + _jsonOptions = options.Value; + } + + public T Deserialize(string json, JsonDeserializeOptions? options = null) + { + if (options != null) + { + if (options.CamelCase) + { + _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + } + } + + return JsonSerializer.Deserialize(json, _jsonOptions); + } + + public object Deserialize(string json, Type type, JsonDeserializeOptions? options = null) + { + if (options != null) + { + if (options.CamelCase) + { + _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + } + } + + return JsonSerializer.Deserialize(json, type, _jsonOptions); + } + + public string Serialize(object obj, JsonSerializeOptions? options = null) + { + if (options != null) + { + _jsonOptions.WriteIndented = options.Indented; + + if (options.CamelCase) + { + _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + } + } + + return JsonSerializer.Serialize(obj, _jsonOptions); + } + + public string Serialize(object obj, Type type, JsonSerializeOptions? options = null) + { + if (options != null) + { + _jsonOptions.WriteIndented = options.Indented; + + if (options.CamelCase) + { + _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase; + } + } + + return JsonSerializer.Serialize(obj, type, _jsonOptions); + } + } +} diff --git a/Src/RCommon.Web/RCommon.Web.csproj b/Src/RCommon.Web/RCommon.Web.csproj index 9c066430..6477ba41 100644 --- a/Src/RCommon.Web/RCommon.Web.csproj +++ b/Src/RCommon.Web/RCommon.Web.csproj @@ -1,15 +1,8 @@  - net6.0;net7.0;net8.0; - - - - - - - - + net8.0; + diff --git a/Src/RCommon.Wolverine/RCommon.Wolverine.csproj b/Src/RCommon.Wolverine/RCommon.Wolverine.csproj index d2fa3009..a696d5f2 100644 --- a/Src/RCommon.Wolverine/RCommon.Wolverine.csproj +++ b/Src/RCommon.Wolverine/RCommon.Wolverine.csproj @@ -1,23 +1,13 @@  - net6.0;net7.0;net8.0; + net8.0; enable enable - - - - - - - - - - - + diff --git a/Src/RCommon.sln b/Src/RCommon.sln index ef6249e5..e1ad5bdb 100644 --- a/Src/RCommon.sln +++ b/Src/RCommon.sln @@ -79,6 +79,28 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Validation", "Validation", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.FluentValidation", "RCommon.FluentValidation\RCommon.FluentValidation.csproj", "{67456010-8054-4F45-9CCA-F21B70BB6F3F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Caching", "Caching", "{09EB12C1-8CE6-47DE-9204-26779A33B40C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Json", "Json", "{62620D67-BE67-4C3C-ABE7-205B805A7002}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Json", "RCommon.Json\RCommon.Json.csproj", "{FE26EBCD-5AC0-43E8-9E4E-7A28F85F8765}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.JsonNet", "RCommon.JsonNet\RCommon.JsonNet.csproj", "{05FC7A37-750B-42E7-9229-3CEC31D107D1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Caching", "RCommon.Caching\RCommon.Caching.csproj", "{DD412BA6-2431-471C-8542-CBF9C8F9B9EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.RedisCache", "RCommon.RedisCache\RCommon.RedisCache.csproj", "{C66C9E1B-F76F-401B-A135-D894FB732481}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Persistence.Caching", "RCommon.Persistence.Caching\RCommon.Persistence.Caching.csproj", "{1343BAC3-A380-42FB-89E3-FD64E1EE7F0A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.MemoryCache", "RCommon.MemoryCache\RCommon.MemoryCache.csproj", "{76DD86CF-B7D4-4F77-B772-C1F740B117EA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Persistence.Caching.MemoryCache", "RCommon.Persistence.Caching.MemoryCache\RCommon.Persistence.Caching.MemoryCache.csproj", "{0D7E4DB6-D03E-43B4-BB01-D6C35DD68292}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.Persistence.Caching.RedisCache", "RCommon.Persistence.Caching.RedisCache\RCommon.Persistence.Caching.RedisCache.csproj", "{8255067B-DDB6-4FAC-AB12-90B7903AEA11}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RCommon.SystemTextJson", "RCommon.SystemTextJson\RCommon.SystemTextJson.csproj", "{B9E0B12D-1EF5-4584-ACBA-4B027DBA6A82}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -191,6 +213,42 @@ Global {67456010-8054-4F45-9CCA-F21B70BB6F3F}.Debug|Any CPU.Build.0 = Debug|Any CPU {67456010-8054-4F45-9CCA-F21B70BB6F3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {67456010-8054-4F45-9CCA-F21B70BB6F3F}.Release|Any CPU.Build.0 = Release|Any CPU + {FE26EBCD-5AC0-43E8-9E4E-7A28F85F8765}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FE26EBCD-5AC0-43E8-9E4E-7A28F85F8765}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FE26EBCD-5AC0-43E8-9E4E-7A28F85F8765}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FE26EBCD-5AC0-43E8-9E4E-7A28F85F8765}.Release|Any CPU.Build.0 = Release|Any CPU + {05FC7A37-750B-42E7-9229-3CEC31D107D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05FC7A37-750B-42E7-9229-3CEC31D107D1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05FC7A37-750B-42E7-9229-3CEC31D107D1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05FC7A37-750B-42E7-9229-3CEC31D107D1}.Release|Any CPU.Build.0 = Release|Any CPU + {DD412BA6-2431-471C-8542-CBF9C8F9B9EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD412BA6-2431-471C-8542-CBF9C8F9B9EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD412BA6-2431-471C-8542-CBF9C8F9B9EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD412BA6-2431-471C-8542-CBF9C8F9B9EF}.Release|Any CPU.Build.0 = Release|Any CPU + {C66C9E1B-F76F-401B-A135-D894FB732481}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C66C9E1B-F76F-401B-A135-D894FB732481}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C66C9E1B-F76F-401B-A135-D894FB732481}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C66C9E1B-F76F-401B-A135-D894FB732481}.Release|Any CPU.Build.0 = Release|Any CPU + {1343BAC3-A380-42FB-89E3-FD64E1EE7F0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1343BAC3-A380-42FB-89E3-FD64E1EE7F0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1343BAC3-A380-42FB-89E3-FD64E1EE7F0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1343BAC3-A380-42FB-89E3-FD64E1EE7F0A}.Release|Any CPU.Build.0 = Release|Any CPU + {76DD86CF-B7D4-4F77-B772-C1F740B117EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76DD86CF-B7D4-4F77-B772-C1F740B117EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76DD86CF-B7D4-4F77-B772-C1F740B117EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76DD86CF-B7D4-4F77-B772-C1F740B117EA}.Release|Any CPU.Build.0 = Release|Any CPU + {0D7E4DB6-D03E-43B4-BB01-D6C35DD68292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D7E4DB6-D03E-43B4-BB01-D6C35DD68292}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D7E4DB6-D03E-43B4-BB01-D6C35DD68292}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D7E4DB6-D03E-43B4-BB01-D6C35DD68292}.Release|Any CPU.Build.0 = Release|Any CPU + {8255067B-DDB6-4FAC-AB12-90B7903AEA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8255067B-DDB6-4FAC-AB12-90B7903AEA11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8255067B-DDB6-4FAC-AB12-90B7903AEA11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8255067B-DDB6-4FAC-AB12-90B7903AEA11}.Release|Any CPU.Build.0 = Release|Any CPU + {B9E0B12D-1EF5-4584-ACBA-4B027DBA6A82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9E0B12D-1EF5-4584-ACBA-4B027DBA6A82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9E0B12D-1EF5-4584-ACBA-4B027DBA6A82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9E0B12D-1EF5-4584-ACBA-4B027DBA6A82}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -220,6 +278,15 @@ Global {397769BB-08F3-44DE-9BE6-90A73E0ABB76} = {6D73446A-6E32-4324-B524-E054334B394B} {A83FB80E-2B41-403E-9529-FF0FA8E74045} = {206A03B9-635F-4811-8519-8B02170B8CB3} {67456010-8054-4F45-9CCA-F21B70BB6F3F} = {65E88F7E-066C-4A7F-BE19-83565D65873A} + {FE26EBCD-5AC0-43E8-9E4E-7A28F85F8765} = {62620D67-BE67-4C3C-ABE7-205B805A7002} + {05FC7A37-750B-42E7-9229-3CEC31D107D1} = {62620D67-BE67-4C3C-ABE7-205B805A7002} + {DD412BA6-2431-471C-8542-CBF9C8F9B9EF} = {09EB12C1-8CE6-47DE-9204-26779A33B40C} + {C66C9E1B-F76F-401B-A135-D894FB732481} = {09EB12C1-8CE6-47DE-9204-26779A33B40C} + {1343BAC3-A380-42FB-89E3-FD64E1EE7F0A} = {09EB12C1-8CE6-47DE-9204-26779A33B40C} + {76DD86CF-B7D4-4F77-B772-C1F740B117EA} = {09EB12C1-8CE6-47DE-9204-26779A33B40C} + {0D7E4DB6-D03E-43B4-BB01-D6C35DD68292} = {09EB12C1-8CE6-47DE-9204-26779A33B40C} + {8255067B-DDB6-4FAC-AB12-90B7903AEA11} = {09EB12C1-8CE6-47DE-9204-26779A33B40C} + {B9E0B12D-1EF5-4584-ACBA-4B027DBA6A82} = {62620D67-BE67-4C3C-ABE7-205B805A7002} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0B0CD26D-8067-4667-863E-6B0EE7EDAA42} diff --git a/Tests/RCommon.Emailing.SendGrid.Tests/RCommon.Emailing.SendGrid.Tests.csproj b/Tests/RCommon.Emailing.SendGrid.Tests/RCommon.Emailing.SendGrid.Tests.csproj index 96326720..546f9c58 100644 --- a/Tests/RCommon.Emailing.SendGrid.Tests/RCommon.Emailing.SendGrid.Tests.csproj +++ b/Tests/RCommon.Emailing.SendGrid.Tests/RCommon.Emailing.SendGrid.Tests.csproj @@ -8,16 +8,16 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/RCommon.Emailing.Tests/RCommon.Emailing.Tests.csproj b/Tests/RCommon.Emailing.Tests/RCommon.Emailing.Tests.csproj index 47c0e33d..30a0ed4c 100644 --- a/Tests/RCommon.Emailing.Tests/RCommon.Emailing.Tests.csproj +++ b/Tests/RCommon.Emailing.Tests/RCommon.Emailing.Tests.csproj @@ -8,15 +8,15 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/RCommon.Mediator.MediatR.Tests/RCommon.Mediator.MediatR.Tests.csproj b/Tests/RCommon.Mediator.MediatR.Tests/RCommon.Mediator.MediatR.Tests.csproj index 2eff2099..c02ef958 100644 --- a/Tests/RCommon.Mediator.MediatR.Tests/RCommon.Mediator.MediatR.Tests.csproj +++ b/Tests/RCommon.Mediator.MediatR.Tests/RCommon.Mediator.MediatR.Tests.csproj @@ -9,16 +9,16 @@ - - - - - + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Tests/RCommon.Messaging.MassTransit.Tests/RCommon.Messaging.MassTransit.Tests.csproj b/Tests/RCommon.Messaging.MassTransit.Tests/RCommon.Messaging.MassTransit.Tests.csproj index d6d3e55f..5672fb3a 100644 --- a/Tests/RCommon.Messaging.MassTransit.Tests/RCommon.Messaging.MassTransit.Tests.csproj +++ b/Tests/RCommon.Messaging.MassTransit.Tests/RCommon.Messaging.MassTransit.Tests.csproj @@ -9,10 +9,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/RCommon.Messaging.Wolverine.Tests/RCommon.Messaging.Wolverine.Tests.csproj b/Tests/RCommon.Messaging.Wolverine.Tests/RCommon.Messaging.Wolverine.Tests.csproj index 1dd04735..4b894d84 100644 --- a/Tests/RCommon.Messaging.Wolverine.Tests/RCommon.Messaging.Wolverine.Tests.csproj +++ b/Tests/RCommon.Messaging.Wolverine.Tests/RCommon.Messaging.Wolverine.Tests.csproj @@ -9,10 +9,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Tests/RCommon.Persistence.Dapper.Tests/DapperTestBase.cs b/Tests/RCommon.Persistence.Dapper.Tests/DapperTestBase.cs index 21e08690..7136feeb 100644 --- a/Tests/RCommon.Persistence.Dapper.Tests/DapperTestBase.cs +++ b/Tests/RCommon.Persistence.Dapper.Tests/DapperTestBase.cs @@ -38,7 +38,7 @@ protected void InitializeRCommon(IServiceCollection services) { guidOptions.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString; }) - .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to NHibernate without impact to domain service up through the stack + .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to NHibernate without impact to domain service up through the stack { // Add all the DbContexts here ef.AddDbContext("TestDbContext", ef => @@ -46,17 +46,9 @@ protected void InitializeRCommon(IServiceCollection services) ef.UseSqlServer( this.Configuration.GetConnectionString("TestDbContext")); }); - }, unitOfWork => - { - unitOfWork.SetOptions(options => - { - options.AutoCompleteScope = false; - options.DefaultIsolation = System.Transactions.IsolationLevel.ReadCommitted; - }); }) - .WithPersistence(objectAccessActions: dapper => + .WithPersistence(dapper => { - dapper.AddDbConnection("TestDbConnection", db => { db.DbFactory = SqlClientFactory.Instance; @@ -73,7 +65,8 @@ protected void InitializeRCommon(IServiceCollection services) { dataStore.DefaultDataStoreName = "TestDbConnection"; }); - }, unitOfWork => + }) + .WithUnitOfWork(unitOfWork => { unitOfWork.SetOptions(options => { diff --git a/Tests/RCommon.Persistence.Dapper.Tests/RCommon.Persistence.Dapper.Tests.csproj b/Tests/RCommon.Persistence.Dapper.Tests/RCommon.Persistence.Dapper.Tests.csproj index 170c1cf8..c4503c00 100644 --- a/Tests/RCommon.Persistence.Dapper.Tests/RCommon.Persistence.Dapper.Tests.csproj +++ b/Tests/RCommon.Persistence.Dapper.Tests/RCommon.Persistence.Dapper.Tests.csproj @@ -7,22 +7,22 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + diff --git a/Tests/RCommon.Persistence.EFCore.Tests/EFCoreTestBase.cs b/Tests/RCommon.Persistence.EFCore.Tests/EFCoreTestBase.cs index 019489f6..a98c6baa 100644 --- a/Tests/RCommon.Persistence.EFCore.Tests/EFCoreTestBase.cs +++ b/Tests/RCommon.Persistence.EFCore.Tests/EFCoreTestBase.cs @@ -30,7 +30,7 @@ protected void InitializeRCommon(IServiceCollection services) services.AddRCommon() .WithSequentialGuidGenerator(guid => guid.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString) .WithDateTimeSystem(dateTime => dateTime.Kind = DateTimeKind.Utc) - .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to NHibernate without impact to domain service up through the stack + .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to Linq2Db without impact to domain service up through the stack { // Add all the DbContexts here ef.AddDbContext("TestDbContext", ef => @@ -42,7 +42,8 @@ protected void InitializeRCommon(IServiceCollection services) { dataStore.DefaultDataStoreName = "TestDbContext"; }); - }, unitOfWork => + }) + .WithUnitOfWork(unitOfWork => { unitOfWork.SetOptions(options => { diff --git a/Tests/RCommon.Persistence.EFCore.Tests/RCommon.Persistence.EFCore.Tests.csproj b/Tests/RCommon.Persistence.EFCore.Tests/RCommon.Persistence.EFCore.Tests.csproj index 08b276df..b150fe96 100644 --- a/Tests/RCommon.Persistence.EFCore.Tests/RCommon.Persistence.EFCore.Tests.csproj +++ b/Tests/RCommon.Persistence.EFCore.Tests/RCommon.Persistence.EFCore.Tests.csproj @@ -9,23 +9,23 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - - + + + + + - - - - + + + + diff --git a/Tests/RCommon.Persistence.Linq2Db.Tests/Linq2DbTestBase.cs b/Tests/RCommon.Persistence.Linq2Db.Tests/Linq2DbTestBase.cs index 6c807b2a..8e400164 100644 --- a/Tests/RCommon.Persistence.Linq2Db.Tests/Linq2DbTestBase.cs +++ b/Tests/RCommon.Persistence.Linq2Db.Tests/Linq2DbTestBase.cs @@ -39,7 +39,7 @@ protected void InitializeRCommon(IServiceCollection services) services.AddRCommon() .WithSequentialGuidGenerator(guid => guid.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString) .WithDateTimeSystem(dateTime => dateTime.Kind = DateTimeKind.Utc) - .WithPersistence(objectAccessActions: ef => // Repository/ORM configuration. We could easily swap out to NHibernate without impact to domain service up through the stack + .WithPersistence(ef => // Repository/ORM configuration. We could easily swap out to NHibernate without impact to domain service up through the stack { // Add all the DbContexts here ef.AddDbContext("TestDbContext", ef => @@ -47,15 +47,8 @@ protected void InitializeRCommon(IServiceCollection services) ef.UseSqlServer( this.Configuration.GetConnectionString("TestDbContext")); }); - }, unitOfWork => - { - unitOfWork.SetOptions(options => - { - options.AutoCompleteScope = false; - options.DefaultIsolation = System.Transactions.IsolationLevel.ReadCommitted; - }); }) - .WithPersistence(objectAccessActions: linq2Db => + .WithPersistence(linq2Db => { // Add all the DbContexts here linq2Db.AddDataConnection("TestDataConnection", (provider, options) => @@ -70,12 +63,13 @@ protected void InitializeRCommon(IServiceCollection services) { dataStore.DefaultDataStoreName = "TestDataConnection"; }); - }, unitOfWork => + }) + .WithUnitOfWork(unitOfWork => { unitOfWork.SetOptions(options => { options.AutoCompleteScope = false; - options.DefaultIsolation = IsolationLevel.ReadCommitted; + options.DefaultIsolation = System.Transactions.IsolationLevel.ReadCommitted; }); }); diff --git a/Tests/RCommon.Persistence.Linq2Db.Tests/RCommon.Persistence.Linq2Db.Tests.csproj b/Tests/RCommon.Persistence.Linq2Db.Tests/RCommon.Persistence.Linq2Db.Tests.csproj index 34cac01b..124cf073 100644 --- a/Tests/RCommon.Persistence.Linq2Db.Tests/RCommon.Persistence.Linq2Db.Tests.csproj +++ b/Tests/RCommon.Persistence.Linq2Db.Tests/RCommon.Persistence.Linq2Db.Tests.csproj @@ -8,20 +8,20 @@ - + - + - - - - - + + + + + - - - - + + + + diff --git a/Tests/RCommon.Security.Tests/RCommon.Security.Tests.csproj b/Tests/RCommon.Security.Tests/RCommon.Security.Tests.csproj index 2c408e23..a53c9ab8 100644 --- a/Tests/RCommon.Security.Tests/RCommon.Security.Tests.csproj +++ b/Tests/RCommon.Security.Tests/RCommon.Security.Tests.csproj @@ -8,17 +8,17 @@ - - - - - + + + + + - + - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Tests/RCommon.TestBase.Data/EFConfigurations/TestDbContext.cs b/Tests/RCommon.TestBase.Data/EFConfigurations/TestDbContext.cs index 025d5ec0..b9857257 100644 --- a/Tests/RCommon.TestBase.Data/EFConfigurations/TestDbContext.cs +++ b/Tests/RCommon.TestBase.Data/EFConfigurations/TestDbContext.cs @@ -70,7 +70,8 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (_configuration != null) { - optionsBuilder.UseSqlServer(_configuration.GetConnectionString(@"TestDbContext")); + string connString = _configuration.GetConnectionString(@"TestDbContext"); + optionsBuilder.UseSqlServer(connString); optionsBuilder.UseLoggerFactory(EFUnitTestLoggingFactory) .EnableSensitiveDataLogging(); } diff --git a/Tests/RCommon.TestBase.Data/RCommon.TestBase.Data.csproj b/Tests/RCommon.TestBase.Data/RCommon.TestBase.Data.csproj index 660cb901..4d27aa86 100644 --- a/Tests/RCommon.TestBase.Data/RCommon.TestBase.Data.csproj +++ b/Tests/RCommon.TestBase.Data/RCommon.TestBase.Data.csproj @@ -7,23 +7,23 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - - - + + + + + - - - - + + + + diff --git a/Tests/RCommon.TestBase.Data/TestRepository.cs b/Tests/RCommon.TestBase.Data/TestRepository.cs index f4dceec5..e56685b9 100644 --- a/Tests/RCommon.TestBase.Data/TestRepository.cs +++ b/Tests/RCommon.TestBase.Data/TestRepository.cs @@ -26,10 +26,10 @@ public TestRepository(DbContext context) _entityDeleteActions = new List>(); } - public TestRepository(ServiceProvider serviceProvider) + public TestRepository(IServiceProvider serviceProvider) { _dataStoreFactory = serviceProvider.GetService(); - _context = _dataStoreFactory.Resolve("TestDbContext"); + _context = _dataStoreFactory.Resolve("TestDbContext"); _entityDeleteActions = new List>(); } diff --git a/Tests/RCommon.TestBase/RCommon.TestBase.csproj b/Tests/RCommon.TestBase/RCommon.TestBase.csproj index 2604a003..2cc64236 100644 --- a/Tests/RCommon.TestBase/RCommon.TestBase.csproj +++ b/Tests/RCommon.TestBase/RCommon.TestBase.csproj @@ -7,19 +7,19 @@ - - - - - + + + + + - - - - - + + + + + - + diff --git a/Tests/RCommon.TestBase/TestBootstrapper.cs b/Tests/RCommon.TestBase/TestBootstrapper.cs index 9bf6f6bd..ea2e85d0 100644 --- a/Tests/RCommon.TestBase/TestBootstrapper.cs +++ b/Tests/RCommon.TestBase/TestBootstrapper.cs @@ -21,8 +21,6 @@ public abstract class TestBootstrapper private ServiceProvider _serviceProvider; private Microsoft.Extensions.Logging.ILogger _logger; - static object _configureLock = new object(); - public TestBootstrapper() { diff --git a/Tests/RCommon.Tests/RCommon.Tests.csproj b/Tests/RCommon.Tests/RCommon.Tests.csproj index 6021f72f..5b54b008 100644 --- a/Tests/RCommon.Tests/RCommon.Tests.csproj +++ b/Tests/RCommon.Tests/RCommon.Tests.csproj @@ -14,9 +14,9 @@ - - - + + +