diff --git a/Cacheable.sln b/Cacheable.sln index b81519e..c6689a3 100644 --- a/Cacheable.sln +++ b/Cacheable.sln @@ -15,6 +15,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{D1F536AC EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cacheable.Tests", "tests\Cacheable.Tests\Cacheable.Tests.csproj", "{D4F95B27-30B5-4391-8322-A456A7EE4EA5}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{FC7F7153-1BFC-476F-9AE6-C771F987D6B6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MediatR", "MediatR", "{A285DE98-56C9-4713-A033-EDC8BC65F110}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cacheable.Microsoft.Extensions.Caching.Abstractions", "src\Cacheable.Microsoft.Extensions.Caching.Abstractions\Cacheable.Microsoft.Extensions.Caching.Abstractions.csproj", "{F665B4DB-4416-41CE-AF82-F05CFC709067}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cacheable.MediatR", "src\Cacheable.MediatR\Cacheable.MediatR.csproj", "{126F7A77-6F66-4235-A8A4-76B416ED712E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApiDotNetCore20", "samples\MediatR\WebApiDotNetCore20\WebApiDotNetCore20.csproj", "{1109466A-4366-44C6-908B-8A43053860B8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -29,12 +39,26 @@ Global {D4F95B27-30B5-4391-8322-A456A7EE4EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {D4F95B27-30B5-4391-8322-A456A7EE4EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU {D4F95B27-30B5-4391-8322-A456A7EE4EA5}.Release|Any CPU.Build.0 = Release|Any CPU + {F665B4DB-4416-41CE-AF82-F05CFC709067}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F665B4DB-4416-41CE-AF82-F05CFC709067}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F665B4DB-4416-41CE-AF82-F05CFC709067}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F665B4DB-4416-41CE-AF82-F05CFC709067}.Release|Any CPU.Build.0 = Release|Any CPU + {126F7A77-6F66-4235-A8A4-76B416ED712E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {126F7A77-6F66-4235-A8A4-76B416ED712E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {126F7A77-6F66-4235-A8A4-76B416ED712E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {126F7A77-6F66-4235-A8A4-76B416ED712E}.Release|Any CPU.Build.0 = Release|Any CPU + {1109466A-4366-44C6-908B-8A43053860B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1109466A-4366-44C6-908B-8A43053860B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1109466A-4366-44C6-908B-8A43053860B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1109466A-4366-44C6-908B-8A43053860B8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {D4F95B27-30B5-4391-8322-A456A7EE4EA5} = {8C6885A4-912E-4900-A809-EC9607DFA495} + {A285DE98-56C9-4713-A033-EDC8BC65F110} = {FC7F7153-1BFC-476F-9AE6-C771F987D6B6} + {1109466A-4366-44C6-908B-8A43053860B8} = {A285DE98-56C9-4713-A033-EDC8BC65F110} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E8200E84-166D-4FBF-90FD-9FAB13CE5191} diff --git a/README.md b/README.md index e69de29..edc9f9b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,33 @@ +cacheable +==================== + +[![Build status](https://ci.appveyor.com/api/projects/status/w91o128dbwdwvj8a?svg=true)](https://ci.appveyor.com/project/neekgreen/cacheable) +[![NuGet](https://img.shields.io/nuget/v/cacheable.svg)](https://www.nuget.org/packages/cacheable) +[![NuGet](https://img.shields.io/nuget/dt/cacheable.svg)](https://www.nuget.org/packages/cacheable) +[![CodeFactor](https://www.codefactor.io/repository/github/neekgreen/cacheable/badge)](https://www.codefactor.io/repository/github/neekgreen/cacheable) + +A set of extensions to provide caching support on MediatR based request handlers. + +[![something](https://img.shields.io/badge/.netstandard-2.0-blue.svg)](https://img.shields.io/badge/.netstandard-1.3-blue.svg) + +## Installing Cacheable + +You should install [Cacheable with NuGet](https://www.nuget.org/packages/cacheable): + + Install-Package Cacheable + +This command will download and install Cacheable. Let me know if you have questions! + +## Using Cacheable + +Cacheable requires MediatR and your IoC container of choice. The decorator pattern is used to wrap the `IRequestHandler` and `IAsyncRequestHandler` classes with Cacheable implementations that will handle the caching. + +### With StructureMap + +``` +For(typeof(IRequestHandler<,>)).DecorateAllWith(typeof(MemoryCacheRequestHandler<,>)); +``` + +``` +For(typeof(IAsyncRequestHandler<,>)).DecorateAllWith(typeof(MemoryCacheAsyncRequestHandler<,>)); +``` \ No newline at end of file diff --git a/build.sh b/build.sh old mode 100644 new mode 100755 diff --git a/samples/MediatR/WebApiDotNetCore20/BenchmarkAttribute.cs b/samples/MediatR/WebApiDotNetCore20/BenchmarkAttribute.cs new file mode 100644 index 0000000..67ebfea --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/BenchmarkAttribute.cs @@ -0,0 +1,20 @@ +namespace WebApiDotNetCore20 +{ + using System.Diagnostics; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc.Filters; + + public class BenchmarkAttribute : ActionFilterAttribute + { + public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + var stopWatch = new Stopwatch(); + stopWatch.Start(); + + await next(); + + stopWatch.Stop(); + context.HttpContext.Response.Headers.Add("x-time-elapsed", stopWatch.Elapsed.ToString()); + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Features/NoCache/Command.cs b/samples/MediatR/WebApiDotNetCore20/Features/NoCache/Command.cs new file mode 100644 index 0000000..21fe67f --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Features/NoCache/Command.cs @@ -0,0 +1,10 @@ +namespace WebApiDotNetCore20.Features.NoCache +{ + using MediatR; + using Models; + + public class Command : IRequest + { + public int Number { get; set; } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Features/NoCache/CommandHandler.cs b/samples/MediatR/WebApiDotNetCore20/Features/NoCache/CommandHandler.cs new file mode 100644 index 0000000..37d0acc --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Features/NoCache/CommandHandler.cs @@ -0,0 +1,21 @@ +namespace WebApiDotNetCore20.Features.NoCache +{ + using System.Threading.Tasks; + using MediatR; + using Models; + + public class CommandHandler : IAsyncRequestHandler + { + private readonly IResultBuilder resultBuilder; + + public CommandHandler(IResultBuilder resultBuilder) + { + this.resultBuilder = resultBuilder; + } + + Task IAsyncRequestHandler.Handle(Command message) + { + return Task.FromResult(resultBuilder.GetResult(message.Number)); + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Features/NoCache/TestController.cs b/samples/MediatR/WebApiDotNetCore20/Features/NoCache/TestController.cs new file mode 100644 index 0000000..19bc4c1 --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Features/NoCache/TestController.cs @@ -0,0 +1,26 @@ +namespace WebApiDotNetCore20.Features.NoCache +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using MediatR; + + [Route("api/no-cache")] + public class TestController : Controller + { + private readonly IMediator mediator; + + public TestController(IMediator mediator) + { + this.mediator = mediator; + } + + [Benchmark, HttpGet("{number}")] + public async Task Get(int number) + { + var result = + await mediator.Send(new Command { Number = number }); + + return Ok(result); + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Features/WithCache/Command.cs b/samples/MediatR/WebApiDotNetCore20/Features/WithCache/Command.cs new file mode 100644 index 0000000..224e110 --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Features/WithCache/Command.cs @@ -0,0 +1,21 @@ +namespace WebApiDotNetCore20.Features.WithCache +{ + using System; + using Cacheable; + using MediatR; + using Models; + + public class Command : IRequest, ICacheableRequest + { + public int Number { get; set; } + + public bool IsCacheable { get; set; } = true; + + public string GetCacheKey() { return "number:" + Number; } + + public CacheEntryOptions GetCacheOptions() + { + return new CacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(5) }; + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Features/WithCache/CommandHandler.cs b/samples/MediatR/WebApiDotNetCore20/Features/WithCache/CommandHandler.cs new file mode 100644 index 0000000..4965a18 --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Features/WithCache/CommandHandler.cs @@ -0,0 +1,21 @@ +namespace WebApiDotNetCore20.Features.WithCache +{ + using Cacheable; + using MediatR; + using Models; + + public class CommandHandler : IRequestHandler, ICacheableRequestHandler + { + private readonly IResultBuilder resultBuilder; + + public CommandHandler(IResultBuilder resultBuilder) + { + this.resultBuilder = resultBuilder; + } + + Result IRequestHandler.Handle(Command message) + { + return resultBuilder.GetResult(message.Number); + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Features/WithCache/TestController.cs b/samples/MediatR/WebApiDotNetCore20/Features/WithCache/TestController.cs new file mode 100644 index 0000000..382045f --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Features/WithCache/TestController.cs @@ -0,0 +1,26 @@ +namespace WebApiDotNetCore20.Features.WithCache +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Mvc; + using MediatR; + + [Route("api/with-cache")] + public class TestController : Controller + { + private readonly IMediator mediator; + + public TestController(IMediator mediator) + { + this.mediator = mediator; + } + + [Benchmark, HttpGet("{number}")] + public async Task Get(int number, bool isCacheable) + { + var result = + await mediator.Send(new Command { Number = number, IsCacheable = isCacheable }); + + return Ok(result); + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Models/IResultBuilder.cs b/samples/MediatR/WebApiDotNetCore20/Models/IResultBuilder.cs new file mode 100644 index 0000000..ab3edeb --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Models/IResultBuilder.cs @@ -0,0 +1,7 @@ +namespace WebApiDotNetCore20.Models +{ + public interface IResultBuilder + { + Result GetResult(int number); + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Models/Result.cs b/samples/MediatR/WebApiDotNetCore20/Models/Result.cs new file mode 100644 index 0000000..b67473e --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Models/Result.cs @@ -0,0 +1,11 @@ +namespace WebApiDotNetCore20.Models +{ + using System; + + public class Result + { + public int Number { get; set; } + public string Text { get; set; } + public DateTimeOffset Created { get; } = DateTimeOffset.Now; + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Models/ResultBuilder.cs b/samples/MediatR/WebApiDotNetCore20/Models/ResultBuilder.cs new file mode 100644 index 0000000..af25e51 --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Models/ResultBuilder.cs @@ -0,0 +1,21 @@ +namespace WebApiDotNetCore20.Models +{ + using System.Threading; + using Humanizer; + + public class ResultBuilder : IResultBuilder + { + public Result GetResult(int number) + { + Thread.Sleep(5000); //# this thing is expensive :( + + var result = new Result + { + Number = number, + Text = number.ToWords(), + }; + + return result; + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Program.cs b/samples/MediatR/WebApiDotNetCore20/Program.cs new file mode 100644 index 0000000..78fa802 --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Program.cs @@ -0,0 +1,18 @@ +namespace WebApiDotNetCore20 +{ + using Microsoft.AspNetCore; + using Microsoft.AspNetCore.Hosting; + + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Properties/launchSettings.json b/samples/MediatR/WebApiDotNetCore20/Properties/launchSettings.json new file mode 100644 index 0000000..cfbd139 --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Properties/launchSettings.json @@ -0,0 +1,27 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:64435/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "WebApiDotNetCore20": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:64436/" + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/Startup.cs b/samples/MediatR/WebApiDotNetCore20/Startup.cs new file mode 100644 index 0000000..3d605be --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/Startup.cs @@ -0,0 +1,80 @@ +namespace WebApiDotNetCore20 +{ + using System; + using MediatR; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Caching.Memory; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Options; + using StructureMap; + using Swashbuckle.AspNetCore.Swagger; + using WebApiDotNetCore20.Models; + using Cacheable; + + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + public IServiceProvider ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + services.AddMediatR(typeof(Startup).Assembly); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new Info { Title = "My API", Version = "v1" }); + }); + + var container = new Container(); + + container.Configure(config => + { + config.Scan(scanner => + { + scanner.AssemblyContainingType(); + scanner.ConnectImplementationsToTypesClosing(typeof(IRequestHandler<,>)); + }); + + config.For().Use().Singleton(); + + config.For(typeof(IRequestHandler<,>)).DecorateAllWith(typeof(MemoryCacheRequestHandler<,>)); + config.For(typeof(IAsyncRequestHandler<,>)).DecorateAllWith(typeof(MemoryCacheAsyncRequestHandler<,>), + (t) => t.ReturnedType.IsAssignableFrom(typeof(ICacheableRequestHandler))); + + config.For().Use(ctx => t => ctx.GetInstance(t)); + config.For().Use(ctx => t => ctx.GetAllInstances(t)); + + config.For().Use(); + + config.For().Use(() => new MemoryCache(Options.Create(new MemoryCacheOptions()))).Singleton(); + + config.Populate(services); + }); + + return container.GetInstance(); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseSwagger(); + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1"); + }); + + app.UseMvc(); + } + } +} \ No newline at end of file diff --git a/samples/MediatR/WebApiDotNetCore20/WebApiDotNetCore20.csproj b/samples/MediatR/WebApiDotNetCore20/WebApiDotNetCore20.csproj new file mode 100644 index 0000000..8e2405f --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/WebApiDotNetCore20.csproj @@ -0,0 +1,30 @@ + + + + netcoreapp2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/MediatR/WebApiDotNetCore20/appsettings.Development.json b/samples/MediatR/WebApiDotNetCore20/appsettings.Development.json new file mode 100644 index 0000000..fa8ce71 --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/samples/MediatR/WebApiDotNetCore20/appsettings.json b/samples/MediatR/WebApiDotNetCore20/appsettings.json new file mode 100644 index 0000000..26bb0ac --- /dev/null +++ b/samples/MediatR/WebApiDotNetCore20/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/src/Cacheable.MediatR/Cacheable.MediatR.csproj b/src/Cacheable.MediatR/Cacheable.MediatR.csproj new file mode 100644 index 0000000..1e30f25 --- /dev/null +++ b/src/Cacheable.MediatR/Cacheable.MediatR.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.0 + Cacheable + git://github.com/neekgreen/cacheable + git + https://github.com/neekgreen/cacheable + Copyright Nicholas Myers + A set of extensions to provide caching support to MediatR request handlers. + 0.2.0 + Nicholas Myers + Nicholas Myers + Cacheable.MediatR + + caching;request;mediators;handlers;command;query;decorators + + + + + + + + + + + + diff --git a/src/Cacheable.MediatR/DistributedMemoryCacheAsyncRequestHandler.cs b/src/Cacheable.MediatR/DistributedMemoryCacheAsyncRequestHandler.cs new file mode 100644 index 0000000..50e4409 --- /dev/null +++ b/src/Cacheable.MediatR/DistributedMemoryCacheAsyncRequestHandler.cs @@ -0,0 +1,33 @@ +namespace Microsoft.Extensions.Caching.Distributed +{ + using System; + using System.Threading.Tasks; + using Cacheable; + using MediatR; + + public abstract class DistributedMemoryCacheAsyncRequestHandler : IAsyncRequestHandler where TRequest : IRequest + { + private readonly IAsyncRequestHandler innerHandler; + private readonly IDistributedCache cache; + + public DistributedMemoryCacheAsyncRequestHandler(IAsyncRequestHandler innerHandler, IDistributedCache cache) + { + this.innerHandler = innerHandler; + this.cache = cache; + } + + Task IAsyncRequestHandler.Handle(TRequest message) + { + var cacheableRequest = message as ICacheableRequest; + + //if (cacheableRequest != null && cacheableRequest.IsCacheable) + //{ + // var cacheKey = cacheableRequest.GetCacheKey(); + //} + + //throw new NotImplementedException(); + + return innerHandler.Handle(message); + } + } +} \ No newline at end of file diff --git a/src/Cacheable.MediatR/DistributedMemoryCacheRequestHandler.cs b/src/Cacheable.MediatR/DistributedMemoryCacheRequestHandler.cs new file mode 100644 index 0000000..5da637c --- /dev/null +++ b/src/Cacheable.MediatR/DistributedMemoryCacheRequestHandler.cs @@ -0,0 +1,32 @@ +namespace Microsoft.Extensions.Caching.Distributed +{ + using System; + using Cacheable; + using MediatR; + + public abstract class DistributedMemoryCacheRequestHandler : IRequestHandler where TRequest : IRequest + { + private readonly IRequestHandler innerHandler; + private readonly IDistributedCache cache; + + public DistributedMemoryCacheRequestHandler(IRequestHandler innerHandler, IDistributedCache cache) + { + this.innerHandler = innerHandler; + this.cache = cache; + } + + TResponse IRequestHandler.Handle(TRequest message) + { + var cacheableRequest = message as ICacheableRequest; + + //if (cacheableRequest != null && cacheableRequest.IsCacheable) + //{ + // var cacheKey = cacheableRequest.GetCacheKey(); + //} + + //throw new NotImplementedException(); + + return innerHandler.Handle(message); + } + } +} \ No newline at end of file diff --git a/src/Cacheable.MediatR/MemoryCacheAsyncRequestHandler.cs b/src/Cacheable.MediatR/MemoryCacheAsyncRequestHandler.cs new file mode 100644 index 0000000..f1a0d9a --- /dev/null +++ b/src/Cacheable.MediatR/MemoryCacheAsyncRequestHandler.cs @@ -0,0 +1,37 @@ +namespace Microsoft.Extensions.Caching.Memory +{ + using System.Threading.Tasks; + using Cacheable; + using MediatR; + + public class MemoryCacheAsyncRequestHandler : IAsyncRequestHandler where TRequest : IRequest + { + private readonly IAsyncRequestHandler innerHandler; + private readonly IMemoryCache cache; + + public MemoryCacheAsyncRequestHandler(IAsyncRequestHandler innerHandler, IMemoryCache cache) + { + this.innerHandler = innerHandler; + this.cache = cache; + } + + Task IAsyncRequestHandler.Handle(TRequest message) + { + var cacheableRequest = message as ICacheableRequest; + + if (cacheableRequest != null && cacheableRequest.IsCacheable) + { + var cacheKey = cacheableRequest.GetCacheKey(); + + return cache.GetOrCreateAsync(cacheKey, entry => + { + entry.SetOptions(cacheableRequest.GetMemoryCacheOptions()); + + return innerHandler.Handle(message); + }); + } + + return innerHandler.Handle(message); + } + } +} \ No newline at end of file diff --git a/src/Cacheable.MediatR/MemoryCacheRequestHandler.cs b/src/Cacheable.MediatR/MemoryCacheRequestHandler.cs new file mode 100644 index 0000000..e479e57 --- /dev/null +++ b/src/Cacheable.MediatR/MemoryCacheRequestHandler.cs @@ -0,0 +1,36 @@ +namespace Microsoft.Extensions.Caching.Memory +{ + using Cacheable; + using MediatR; + + public class MemoryCacheRequestHandler : IRequestHandler where TRequest : IRequest + { + private readonly IRequestHandler innerHandler; + private readonly IMemoryCache cache; + + public MemoryCacheRequestHandler(IRequestHandler innerHandler, IMemoryCache cache) + { + this.innerHandler = innerHandler; + this.cache = cache; + } + + TResponse IRequestHandler.Handle(TRequest message) + { + var cacheableRequest = message as ICacheableRequest; + + if (cacheableRequest != null && cacheableRequest.IsCacheable) + { + var cacheKey = cacheableRequest.GetCacheKey(); + + return cache.GetOrCreate(cacheKey, entry => + { + entry.SetOptions(cacheableRequest.GetMemoryCacheOptions()); + + return innerHandler.Handle(message); + }); + } + + return innerHandler.Handle(message); + } + } +} \ No newline at end of file diff --git a/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/CacheEntryOptionsExtensions.cs b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/CacheEntryOptionsExtensions.cs new file mode 100644 index 0000000..45f02c7 --- /dev/null +++ b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/CacheEntryOptionsExtensions.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Extensions.Caching.Memory +{ + using Cacheable; + + public static class CacheEntryOptionsExtensions + { + public static MemoryCacheEntryOptions GetMemoryCacheOptions(this ICacheableRequest request) + { + return new MemoryCacheEntryAdapter().Convert(request.GetCacheOptions()); + } + } +} \ No newline at end of file diff --git a/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/Cacheable.Microsoft.Extensions.Caching.Abstractions.csproj b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/Cacheable.Microsoft.Extensions.Caching.Abstractions.csproj new file mode 100644 index 0000000..f5d1834 --- /dev/null +++ b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/Cacheable.Microsoft.Extensions.Caching.Abstractions.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + Cacheable + git://github.com/neekgreen/cacheable + git + https://github.com/neekgreen/cacheable + Copyright Nicholas Myers + Abstractions used to help provide caching support. + 0.2.0 + Nicholas Myers + Nicholas Myers + caching;request;mediators;handlers;command;query;decorators + Cacheable.Microsoft.Extensions.Caching.Abstractions + Cacheable.Microsoft.Extensions.Caching.Abstractions + + + + + + + diff --git a/src/Cacheable/ICacheableRequest.cs b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/ICacheableRequest.cs similarity index 100% rename from src/Cacheable/ICacheableRequest.cs rename to src/Cacheable.Microsoft.Extensions.Caching.Abstractions/ICacheableRequest.cs diff --git a/src/Cacheable/ICacheableRequestHandler.cs b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/ICacheableRequestHandler.cs similarity index 100% rename from src/Cacheable/ICacheableRequestHandler.cs rename to src/Cacheable.Microsoft.Extensions.Caching.Abstractions/ICacheableRequestHandler.cs diff --git a/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/MemoryCacheEntryAdapter.cs b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/MemoryCacheEntryAdapter.cs new file mode 100644 index 0000000..8a0cef3 --- /dev/null +++ b/src/Cacheable.Microsoft.Extensions.Caching.Abstractions/MemoryCacheEntryAdapter.cs @@ -0,0 +1,18 @@ +namespace Microsoft.Extensions.Caching.Memory +{ + using Cacheable; + + public class MemoryCacheEntryAdapter + { + public MemoryCacheEntryOptions Convert(CacheEntryOptions options) + { + return new MemoryCacheEntryOptions + { + AbsoluteExpiration = options.AbsoluteExpiration, + AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow, + SlidingExpiration = options.SlidingExpiration, + Priority = CacheItemPriority.Normal, + }; + } + } +} \ No newline at end of file diff --git a/src/Cacheable/Cacheable.csproj b/src/Cacheable/Cacheable.csproj index 7de5914..369b955 100644 --- a/src/Cacheable/Cacheable.csproj +++ b/src/Cacheable/Cacheable.csproj @@ -2,24 +2,18 @@ netstandard2.0 - - - - A light weight caching framework for MediatR + Generic interfaces to provide caching support. Copyright Nicholas Myers - 0.1.0 + 0.2.0 Nicholas Myers netstandard2.0 Cacheable Cacheable - caching;request;requesthandlers + caching;request;mediators;handlers;command;query;decorators https://github.com/neekgreen/cacheable git git://github.com/neekgreen/cacheable - - - - bin\ + Cacheable diff --git a/tools/packages.config b/tools/packages.config index 3669064..f7f0258 100644 --- a/tools/packages.config +++ b/tools/packages.config @@ -1,4 +1,4 @@ - + \ No newline at end of file