diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/CacheDatabaseQueriesApiSample/CacheDatabaseQueriesApiSample.csproj b/CacheDatabaseQueriesApiSample/CacheDatabaseQueriesApiSample.csproj new file mode 100644 index 0000000..18be97f --- /dev/null +++ b/CacheDatabaseQueriesApiSample/CacheDatabaseQueriesApiSample.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp2.2 + + + + + + + + + + + + + + + + + diff --git a/Samples/ApiAsyncCachingSample/Controllers/DbQueriesController.cs b/CacheDatabaseQueriesApiSample/Controllers/DbQueriesController.cs similarity index 63% rename from Samples/ApiAsyncCachingSample/Controllers/DbQueriesController.cs rename to CacheDatabaseQueriesApiSample/Controllers/DbQueriesController.cs index 773e5b8..efca4e8 100644 --- a/Samples/ApiAsyncCachingSample/Controllers/DbQueriesController.cs +++ b/CacheDatabaseQueriesApiSample/Controllers/DbQueriesController.cs @@ -1,9 +1,9 @@ -using System.Web.Http; -using ApiAsyncCachingSample.Models; +using CacheDatabaseQueriesApiSample; +using Microsoft.AspNetCore.Mvc; namespace ApiAsyncCachingSample.Controllers { - public class DbQueriesController : ApiController + public class DbQueriesController : Controller { [HttpGet] [Route("api/dbQueries")] diff --git a/CacheDatabaseQueriesApiSample/Controllers/DbTimeController.cs b/CacheDatabaseQueriesApiSample/Controllers/DbTimeController.cs new file mode 100644 index 0000000..8479d15 --- /dev/null +++ b/CacheDatabaseQueriesApiSample/Controllers/DbTimeController.cs @@ -0,0 +1,40 @@ +using System; +using LazyCache; +using Microsoft.AspNetCore.Mvc; + +namespace CacheDatabaseQueriesApiSample.Controllers +{ + public class DbTimeController : Controller + { + private readonly IAppCache cache; + private readonly string cacheKey = "DbTimeController.Get"; + private readonly DbTimeContext dbContext; + + + public DbTimeController(DbTimeContext context, IAppCache cache) + { + dbContext = context; + this.cache = cache; + } + + [HttpGet] + [Route("api/dbtime")] + public DbTimeEntity Get() + { + Func actionThatWeWantToCache = () => dbContext.GeDbTime(); + + var cachedDatabaseTime = cache.GetOrAdd(cacheKey, actionThatWeWantToCache); + + return cachedDatabaseTime; + } + + [HttpDelete] + [Route("api/dbtime")] + public IActionResult DeleteFromCache() + { + cache.Remove(cacheKey); + var friendlyMessage = new {Message = $"Item with key '{cacheKey}' removed from server in-memory cache"}; + return Ok(friendlyMessage); + } + } +} \ No newline at end of file diff --git a/CacheDatabaseQueriesApiSample/DbTimeContext.cs b/CacheDatabaseQueriesApiSample/DbTimeContext.cs new file mode 100644 index 0000000..0990351 --- /dev/null +++ b/CacheDatabaseQueriesApiSample/DbTimeContext.cs @@ -0,0 +1,35 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; + +namespace CacheDatabaseQueriesApiSample +{ + public class DbTimeContext : DbContext + { + private static int databaseRequestCounter; //just for demo - don't use static fields for statistics! + + public DbTimeContext(DbContextOptions options) + : base(options) + { + } + + // simulate a table in the database so we can get just one row with the current time + private DbSet Times { get; set; } + + public static int DatabaseRequestCounter() + { + return databaseRequestCounter; + } + + public DbTimeEntity GeDbTime() + { + // get the current time from SQL server right now asynchronously (simulating a slow query) + var result = Times + .FromSql("WAITFOR DELAY '00:00:00:500'; SELECT 1 as [ID], GETDATE() as [TimeNowInTheDatabase]") + .Single(); + + databaseRequestCounter++; + + return result; + } + } +} \ No newline at end of file diff --git a/CacheDatabaseQueriesApiSample/DbTimeEntity.cs b/CacheDatabaseQueriesApiSample/DbTimeEntity.cs new file mode 100644 index 0000000..b40f609 --- /dev/null +++ b/CacheDatabaseQueriesApiSample/DbTimeEntity.cs @@ -0,0 +1,23 @@ +using System; + +namespace CacheDatabaseQueriesApiSample +{ + /// + /// Simulates loading a record from a table, but really just gets the current datatime from the database + /// + public class DbTimeEntity + { + public DbTimeEntity(DateTime now) + { + TimeNowInTheDatabase = now; + } + + public DbTimeEntity() + { + } + + public virtual int id { get; set; } + + public virtual DateTime TimeNowInTheDatabase { get; set; } + } +} \ No newline at end of file diff --git a/CacheDatabaseQueriesApiSample/Program.cs b/CacheDatabaseQueriesApiSample/Program.cs new file mode 100644 index 0000000..1807f13 --- /dev/null +++ b/CacheDatabaseQueriesApiSample/Program.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace CacheDatabaseQueriesApiSample +{ + public class Program + { + public static void Main(string[] args) + { + BuildWebHost(args).Run(); + } + + public static IWebHost BuildWebHost(string[] args) + { + return WebHost.CreateDefaultBuilder(args) + .UseStartup() + .Build(); + } + } +} \ No newline at end of file diff --git a/CacheDatabaseQueriesApiSample/Properties/launchSettings.json b/CacheDatabaseQueriesApiSample/Properties/launchSettings.json new file mode 100644 index 0000000..47fffba --- /dev/null +++ b/CacheDatabaseQueriesApiSample/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:52671/", + "sslPort": 0 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "CacheDatabaseQueriesApiSample": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "http://localhost:52672/" + } + } +} diff --git a/CacheDatabaseQueriesApiSample/Startup.cs b/CacheDatabaseQueriesApiSample/Startup.cs new file mode 100644 index 0000000..e1a3044 --- /dev/null +++ b/CacheDatabaseQueriesApiSample/Startup.cs @@ -0,0 +1,45 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace CacheDatabaseQueriesApiSample +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(); + + // just for demo - use app settings for db config + var connection = + @"Server=(localdb)\projectsv13;Database=Master;Trusted_Connection=True;ConnectRetryCount=0"; + + // register the database + services.AddDbContext(options => options.UseSqlServer(connection)); + + // Register IAppCache as a singleton CachingService + services.AddLazyCache(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + if (env.IsDevelopment()) + app.UseDeveloperExceptionPage(); + + app.UseDefaultFiles(); + app.UseStaticFiles(); + app.UseMvc(); + } + } +} \ No newline at end of file diff --git a/CacheDatabaseQueriesApiSample/appsettings.Development.json b/CacheDatabaseQueriesApiSample/appsettings.Development.json new file mode 100644 index 0000000..f334029 --- /dev/null +++ b/CacheDatabaseQueriesApiSample/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/CacheDatabaseQueriesApiSample/appsettings.json b/CacheDatabaseQueriesApiSample/appsettings.json new file mode 100644 index 0000000..647f1a9 --- /dev/null +++ b/CacheDatabaseQueriesApiSample/appsettings.json @@ -0,0 +1,15 @@ +{ + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/Samples/ApiAsyncCachingSample/index.html b/CacheDatabaseQueriesApiSample/wwwroot/index.html similarity index 92% rename from Samples/ApiAsyncCachingSample/index.html rename to CacheDatabaseQueriesApiSample/wwwroot/index.html index 773a017..dd9faa3 100644 --- a/Samples/ApiAsyncCachingSample/index.html +++ b/CacheDatabaseQueriesApiSample/wwwroot/index.html @@ -17,7 +17,7 @@
0 Database query(s)
-

Sample app to demonstrate using an async cache in your API to save database SQL queries and speed up API calls

+

Sample app to demonstrate using an cache in your API to save database SQL queries and speed up API calls

Every 3 seconds we fetch the current time from the database, however because the sql query @@ -70,7 +70,7 @@

Sample app to demonstrate using an async cache in your API to save database }, updateLogWithTime: function (dbTime, duration) { - var message = "API reports that the time now in the database is " + dbTime.TimeNowInTheDatabase + " (" + duration + "ms)"; + var message = "API reports that the time now in the database is " + dbTime.timeNowInTheDatabase + " (" + duration + "ms)"; app.log(message); }, @@ -80,7 +80,7 @@

Sample app to demonstrate using an async cache in your API to save database url: "/api/dbtime" }) .done(function (data) { - app.log(data.Message); + app.log(data.message); }); }, diff --git a/Console.Net461/App.config b/Console.Net461/App.config new file mode 100644 index 0000000..c244acc --- /dev/null +++ b/Console.Net461/App.config @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Console.Net461/Console.Net461.csproj b/Console.Net461/Console.Net461.csproj new file mode 100644 index 0000000..c16cd8a --- /dev/null +++ b/Console.Net461/Console.Net461.csproj @@ -0,0 +1,92 @@ + + + + + Debug + AnyCPU + {32142F20-DFCE-4DF0-A263-093111E5A3FA} + Exe + Console.Net461 + Console.Net461 + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.Extensions.Caching.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Caching.Memory.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Caching.Memory.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.2.2.0\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Options.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Primitives.2.2.0\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + + + + ..\packages\System.Buffers.4.5.0\lib\netstandard2.0\System.Buffers.dll + + + ..\packages\System.ComponentModel.Annotations.4.5.0\lib\net461\System.ComponentModel.Annotations.dll + + + + + ..\packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll + + + + ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll + + + + + + + + + + + + + + + + + + + {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7} + LazyCache + + + + \ No newline at end of file diff --git a/Console.Net461/Program.cs b/Console.Net461/Program.cs new file mode 100644 index 0000000..6c1c649 --- /dev/null +++ b/Console.Net461/Program.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using LazyCache; + +namespace Console.Net461 +{ + class Program + { + static void Main(string[] args) + { + IAppCache cache = new CachingService(CachingService.DefaultCacheProvider); + + var item = cache.GetOrAdd("Program.Main.Person", () => Tuple.Create("Joe Blogs", DateTime.UtcNow)); + + System.Console.WriteLine(item.Item1); + } + } +} diff --git a/LazyCache.UnitTests/Properties/AssemblyInfo.cs b/Console.Net461/Properties/AssemblyInfo.cs similarity index 72% rename from LazyCache.UnitTests/Properties/AssemblyInfo.cs rename to Console.Net461/Properties/AssemblyInfo.cs index f0e46fc..593ecab 100644 --- a/LazyCache.UnitTests/Properties/AssemblyInfo.cs +++ b/Console.Net461/Properties/AssemblyInfo.cs @@ -1,37 +1,36 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -[assembly: AssemblyTitle("LazyCache.UnitTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("LazyCache.UnitTests")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid("bc1a33b7-98ff-483f-8f0c-f62dda71e3f1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Console.Net461")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Console.Net461")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("32142f20-dfce-4df0-a263-093111e5a3fa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Console.Net461/packages.config b/Console.Net461/packages.config new file mode 100644 index 0000000..d68252b --- /dev/null +++ b/Console.Net461/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/LazyCache.AspNetCore/LazyCache.AspNetCore.csproj b/LazyCache.AspNetCore/LazyCache.AspNetCore.csproj new file mode 100644 index 0000000..2939774 --- /dev/null +++ b/LazyCache.AspNetCore/LazyCache.AspNetCore.csproj @@ -0,0 +1,33 @@ + + + + + netstandard2.0 + LazyCache + true + 1.0.0 + + $(LazyCacheAspNetCoreVersion)$(LazyCacheAspNetCoreVersionSuffix) + $(APPVEYOR_BUILD_NUMBER) + 0 + $(LazyCacheAspNetCoreVersion).$(AppVeyorBuildNumber) + https://github.com/alastairtree + https://github.com/alastairtree + ServiceCollection regististrations fopr LazyCache to initialise the depndency injection + Copyright 2014 - 2018 Alastair Crabtree + https://github.com/alastairtree/LazyCache + https://raw.githubusercontent.com/alastairtree/LazyCache/master/artwork/logo-128.png + https://github.com/alastairtree/LazyCache + LazyCache DependecyInjection ServiceCollection SingleTon Transient + MIT + + + + + + + + + + + diff --git a/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs b/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs new file mode 100644 index 0000000..3a5643e --- /dev/null +++ b/LazyCache.AspNetCore/LazyCacheServiceCollectionExtensions.cs @@ -0,0 +1,41 @@ +using System; +using LazyCache; +using LazyCache.Providers; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection.Extensions; + +// ReSharper disable once CheckNamespace - MS guidelines say put DI registration in this NS +namespace Microsoft.Extensions.DependencyInjection +{ + // See https://github.com/aspnet/Caching/blob/dev/src/Microsoft.Extensions.Caching.Memory/MemoryCacheServiceCollectionExtensions.cs + public static class LazyCacheServiceCollectionExtensions + { + public static IServiceCollection AddLazyCache(this IServiceCollection services) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + + services.AddOptions(); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + + services.TryAdd(ServiceDescriptor.Singleton()); + + return services; + } + + public static IServiceCollection AddLazyCache(this IServiceCollection services, + Func implmentationFactory) + { + if (services == null) throw new ArgumentNullException(nameof(services)); + if (implmentationFactory == null) throw new ArgumentNullException(nameof(implmentationFactory)); + + services.AddOptions(); + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Singleton()); + + services.TryAdd(ServiceDescriptor.Singleton(implmentationFactory)); + + return services; + } + } +} \ No newline at end of file diff --git a/LazyCache.UnitTests/AsyncHelper.cs b/LazyCache.UnitTests/AsyncHelper.cs index 89a866c..9f82c7a 100644 --- a/LazyCache.UnitTests/AsyncHelper.cs +++ b/LazyCache.UnitTests/AsyncHelper.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; namespace LazyCache.UnitTests @@ -22,4 +19,4 @@ public static Task CreateTaskWithException() where TException return tcs.Task; } } -} +} \ No newline at end of file diff --git a/LazyCache.UnitTests/CachingServiceTests.cs b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs similarity index 67% rename from LazyCache.UnitTests/CachingServiceTests.cs rename to LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs index d20ea1a..85e5e8b 100644 --- a/LazyCache.UnitTests/CachingServiceTests.cs +++ b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs @@ -1,43 +1,44 @@ using System; using System.Collections.Generic; -using System.Runtime.Caching; using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using LazyCache.Providers; +using Microsoft.Extensions.Caching.Memory; using NUnit.Framework; namespace LazyCache.UnitTests { [TestFixture] - public class CachingServiceTests + public class CachingServiceMemoryCacheProviderTests { - [TearDown] - public void TearDown() - { - MemoryCache.Default.Remove(TestKey); - } - [SetUp] public void BeforeEachTest() { - sut = new CachingService(); + sut = BuildCache(); testObject = new ComplexTestObject(); } - private CachingService sut; - - private readonly CacheItemPolicy oneHourNonRemoveableCacheItemPolicy = new CacheItemPolicy + private static CachingService BuildCache() { - AbsoluteExpiration = DateTimeOffset.Now.AddHours(1), - Priority = CacheItemPriority.NotRemovable - }; + return new CachingService(new MemoryCacheProvider(new MemoryCache(new MemoryCacheOptions()))); + } + + private IAppCache sut; + + private readonly MemoryCacheEntryOptions oneHourNonRemoveableMemoryCacheEntryOptions = + new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.AddHours(1), + Priority = CacheItemPriority.NeverRemove + }; private ComplexTestObject testObject = new ComplexTestObject(); private class ComplexTestObject { - public const string SomeMessage = "testing123"; - public readonly IList SomeItems = new object[] {1, 2, 3, "testing123"}; + public readonly IList SomeItems = new List {1, 2, 3, "testing123"}; + public string SomeMessage = "testing123"; } private const string TestKey = "testKey"; @@ -46,11 +47,15 @@ private class ComplexTestObject [Test] public void AddComplexObjectThenGetGenericReturnsCachedObject() { + testObject.SomeItems.Add("Another"); + testObject.SomeMessage = "changed-it-up"; sut.Add(TestKey, testObject); var actual = sut.Get(TestKey); var expected = testObject; Assert.NotNull(actual); Assert.AreEqual(expected, actual); + testObject.SomeItems.Should().Contain("Another"); + testObject.SomeMessage.Should().Be("changed-it-up"); } [Test] @@ -67,63 +72,63 @@ public void AddComplexObjectThenGetReturnsCachedObject() public void AddEmptyKeyThrowsException() { Action act = () => sut.Add("", new object()); - act.ShouldThrow(); + act.Should().Throw(); } [Test] public void AddEmptyKeyThrowsExceptionWithExpiration() { Action act = () => sut.Add("", new object(), DateTimeOffset.Now.AddHours(1)); - act.ShouldThrow(); + act.Should().Throw(); } [Test] public void AddEmptyKeyThrowsExceptionWithPolicy() { - Action act = () => sut.Add("", new object(), new CacheItemPolicy()); - act.ShouldThrow(); + Action act = () => sut.Add("", new object(), new MemoryCacheEntryOptions()); + act.Should().Throw(); } [Test] public void AddEmptyKeyThrowsExceptionWithSliding() { Action act = () => sut.Add("", new object(), new TimeSpan(1000)); - act.ShouldThrow(); + act.Should().Throw(); } [Test] public void AddNullKeyThrowsException() { Action act = () => sut.Add(null, new object()); - act.ShouldThrow(); + act.Should().Throw(); } [Test] public void AddNullKeyThrowsExceptionWithExpiration() { Action act = () => sut.Add(null, new object(), DateTimeOffset.Now.AddHours(1)); - act.ShouldThrow(); + act.Should().Throw(); } [Test] public void AddNullKeyThrowsExceptionWithPolicy() { - Action act = () => sut.Add(null, new object(), new CacheItemPolicy()); - act.ShouldThrow(); + Action act = () => sut.Add(null, new object(), new MemoryCacheEntryOptions()); + act.Should().Throw(); } [Test] public void AddNullKeyThrowsExceptionWithSliding() { Action act = () => sut.Add(null, new object(), new TimeSpan(1000)); - act.ShouldThrow(); + act.Should().Throw(); } [Test] public void AddNullThrowsException() { Action act = () => sut.Add(TestKey, null); - act.ShouldThrow(); + act.Should().Throw(); } [Test] @@ -151,7 +156,7 @@ public void AddWithOffsetThatExpiresReturnsNull() [Test] public void AddWithPolicyReturnsCachedItem() { - sut.Add(TestKey, "testObject", new CacheItemPolicy()); + sut.Add(TestKey, "testObject", new MemoryCacheEntryOptions()); Assert.AreEqual("testObject", sut.Get(TestKey)); } @@ -170,6 +175,25 @@ public void AddWithSlidingThatExpiresReturnsNull() Assert.IsNull(sut.Get(TestKey)); } + [Test] + public void CacheProviderIsNotNull() + { + sut.CacheProvider.Should().NotBeNull(); + } + + [Test] + public void DefaultContructorThenGetOrAddFromSecondCachingServiceHasSharedUnderlyingCache() + { + var cacheOne = new CachingService(); + var cacheTwo = new CachingService(); + + var resultOne = cacheOne.GetOrAdd(TestKey, () => "resultOne"); + var resultTwo = cacheTwo.GetOrAdd(TestKey, () => "resultTwo"); // should not get executed + + resultOne.Should().Be("resultOne", "GetOrAdd should execute the delegate"); + resultTwo.Should().Be("resultOne", "CachingService should use a shared cache by default"); + } + [Test] public void GetCachedNullableStructTypeParamReturnsType() { @@ -182,7 +206,7 @@ public void GetCachedNullableStructTypeParamReturnsType() public void GetEmptyKeyThrowsException() { Action act = () => sut.Get(""); - act.ShouldThrow(); + act.Should().Throw(); } [Test] @@ -217,60 +241,75 @@ public void GetFromCacheTwiceAtSameTimeOnlyAddsOnce() public void GetNullKeyThrowsException() { Action act = () => sut.Get(null); - act.ShouldThrow(); + act.Should().Throw(); } [Test] - public async Task GetOrAddFollowinGetOrAddAsyncReturnsTheFirstObjectAndUnwrapsTheFirstTask() + public void GetOrAddAndThenGetObjectReturnsCorrectType() { - Func> fetchAsync = () => Task.FromResult(testObject); - Func fetchSync = () => new ComplexTestObject(); - - var actualAsync = await sut.GetOrAddAsync(TestKey, fetchAsync); - var actualSync = sut.GetOrAdd(TestKey, fetchSync); - - Assert.IsNotNull(actualAsync); - Assert.That(actualAsync, Is.EqualTo(testObject)); + sut.GetOrAdd(TestKey, () => testObject); + var actual = sut.Get(TestKey); + Assert.IsNotNull(actual); + } - Assert.IsNotNull(actualSync); - Assert.That(actualSync, Is.EqualTo(testObject)); + [Test] + public void GetOrAddAndThenGetValueObjectReturnsCorrectType() + { + sut.GetOrAdd(TestKey, () => 123); + var actual = sut.Get(TestKey); + Assert.AreEqual(123, actual); + } - Assert.AreEqual(actualAsync, actualSync); + [Test] + public void GetOrAddAndThenGetWrongtypeObjectReturnsNull() + { + sut.GetOrAdd(TestKey, () => testObject); + var actual = sut.Get(TestKey); + Assert.IsNull(actual); } [Test] - public async Task GetOrAddAsyncFollowinGetOrAddReturnsTheFirstObjectAndIgnoresTheSecondTask() + public void GetOrAddAsyncACancelledTaskDoesNotCacheIt() { - Func> fetchAsync = () => Task.FromResult(new ComplexTestObject()); - Func fetchSync = () => testObject; + Assert.ThrowsAsync(async () => + await sut.GetOrAddAsync(TestKey, AsyncHelper.CreateCancelledTask)); - var actualSync = sut.GetOrAdd(TestKey, fetchSync); - var actualAsync = await sut.GetOrAddAsync(TestKey, fetchAsync); + var stillCached = sut.Get>(TestKey); + Assert.That(stillCached, Is.Null); + } - Assert.IsNotNull(actualSync); - Assert.That(actualSync, Is.EqualTo(testObject)); + [Test] + public void GetOrAddAsyncACancelledTaskReturnsTheCacelledTaskToConsumer() + { + var cancelledTask = sut.GetOrAddAsync(TestKey, AsyncHelper.CreateCancelledTask); - Assert.IsNotNull(actualAsync); - Assert.That(actualAsync, Is.EqualTo(testObject)); + Assert.That(cancelledTask, Is.Not.Null); - Assert.AreEqual(actualAsync, actualSync); + Assert.Throws(cancelledTask.Wait); + + Assert.That(cancelledTask.IsCanceled, Is.True); } [Test] - public void GetOrAddAndThenGetObjectReturnsCorrectType() + public void GetOrAddAsyncAFailingTaskDoesNotCacheIt() { - Func fetch = () => testObject; - sut.GetOrAdd(TestKey, fetch); - var actual = sut.Get(TestKey); - Assert.IsNotNull(actual); + Task FetchAsync() + { + return Task.Factory.StartNew(() => throw new ApplicationException()); + } + + Assert.ThrowsAsync(async () => await sut.GetOrAddAsync(TestKey, FetchAsync)); + + var stillCached = sut.Get>(TestKey); + + Assert.That(stillCached, Is.Null); } [Test] public async Task GetOrAddAsyncAndThenGetAsyncObjectReturnsCorrectType() { - Func> fetch = () => Task.FromResult(testObject); - await sut.GetOrAddAsync(TestKey, fetch); + await sut.GetOrAddAsync(TestKey, () => Task.FromResult(testObject)); var actual = await sut.GetAsync(TestKey); Assert.IsNotNull(actual); Assert.That(actual, Is.EqualTo(testObject)); @@ -279,36 +318,41 @@ public async Task GetOrAddAsyncAndThenGetAsyncObjectReturnsCorrectType() [Test] public async Task GetOrAddAsyncAndThenGetAsyncWrongObjectReturnsNull() { - Func> fetch = () => Task.FromResult(testObject); - await sut.GetOrAddAsync(TestKey, fetch); + await sut.GetOrAddAsync(TestKey, () => Task.FromResult(testObject)); var actual = await sut.GetAsync(TestKey); Assert.IsNull(actual); } [Test] - public void GetOrAddAndThenGetValueObjectReturnsCorrectType() + public async Task GetOrAddAsyncFollowinGetOrAddReturnsTheFirstObjectAndIgnoresTheSecondTask() { - Func fetch = () => 123; - sut.GetOrAdd(TestKey, fetch); - var actual = sut.Get(TestKey); - Assert.AreEqual(123, actual); - } + ComplexTestObject FetchSync() + { + return testObject; + } - [Test] - public void GetOrAddAndThenGetWrongtypeObjectReturnsNull() - { - Func fetch = () => testObject; - sut.GetOrAdd(TestKey, fetch); - var actual = sut.Get(TestKey); - Assert.IsNull(actual); + Task FetchAsync() + { + return Task.FromResult(new ComplexTestObject()); + } + + var actualSync = sut.GetOrAdd(TestKey, FetchSync); + var actualAsync = await sut.GetOrAddAsync(TestKey, FetchAsync); + + Assert.IsNotNull(actualSync); + Assert.That(actualSync, Is.EqualTo(testObject)); + + Assert.IsNotNull(actualAsync); + Assert.That(actualAsync, Is.EqualTo(testObject)); + + Assert.AreEqual(actualAsync, actualSync); } [Test] public async Task GetOrAddAsyncTaskAndThenGetTaskOfAnotherTypeReturnsNull() { var cachedAsyncResult = testObject; - Func> fetch = () => Task.FromResult(cachedAsyncResult); - await sut.GetOrAddAsync(TestKey, fetch); + await sut.GetOrAddAsync(TestKey, () => Task.FromResult(cachedAsyncResult)); var actual = sut.Get>(TestKey); Assert.Null(actual); } @@ -317,8 +361,7 @@ public async Task GetOrAddAsyncTaskAndThenGetTaskOfAnotherTypeReturnsNull() public async Task GetOrAddAsyncTaskAndThenGetTaskOfObjectReturnsCorrectType() { var cachedAsyncResult = testObject; - Func> fetch = () => Task.FromResult(cachedAsyncResult); - await sut.GetOrAddAsync(TestKey, fetch); + await sut.GetOrAddAsync(TestKey, () => Task.FromResult(cachedAsyncResult)); var actual = sut.Get>(TestKey); Assert.IsNotNull(actual); Assert.That(actual.Result, Is.EqualTo(cachedAsyncResult)); @@ -378,28 +421,22 @@ public async Task GetOrAddAsyncWillNotAddIfExistingData() Assert.AreEqual(0, times); } - [Test, Timeout(20000)] - public async Task GetOrAddAsyncWithCallbackOnRemovedReturnsTheOriginalCachedObjectEvenIfNotGettedBeforehand() + [Test] + [MaxTime(1000)] + public void GetOrAddAsyncWithALongTaskReturnsBeforeTaskCompletes() { - Func> fetch = () => Task.FromResult(123); - CacheEntryRemovedArguments removedCallbackArgs = null; - CacheEntryRemovedCallback callback = args => removedCallbackArgs = args; - await sut.GetOrAddAsync(TestKey, fetch, - new CacheItemPolicy - { - AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100), - RemovedCallback = callback - }); + var cachedResult = testObject; - sut.Remove(TestKey); //force removed callback to fire - while (removedCallbackArgs == null) - Thread.Sleep(500); + Task FetchAsync() + { + return Task.Delay(TimeSpan.FromMinutes(1)) + .ContinueWith(x => cachedResult); + } - var callbackResult = removedCallbackArgs.CacheItem.Value; - Assert.That(callbackResult, Is.AssignableTo>()); - var callbackResultValue = await (Task) removedCallbackArgs.CacheItem.Value; + var actualResult = sut.GetOrAddAsync(TestKey, FetchAsync); - Assert.AreEqual(123, callbackResultValue); + Assert.That(actualResult, Is.Not.Null); + Assert.That(actualResult.IsCompleted, Is.Not.True); } [Test] @@ -409,7 +446,7 @@ public async Task GetOrAddAsyncWithOffsetWillAddAndReturnTaskOfCached() TestKey, () => Task.FromResult(new DateTime(2001, 01, 01)), DateTimeOffset.Now.AddSeconds(5) - ); + ); var expectedSecond = await sut.Get>(TestKey); Assert.AreEqual(2001, expectedFirst.Year); @@ -420,24 +457,100 @@ public async Task GetOrAddAsyncWithOffsetWillAddAndReturnTaskOfCached() public async Task GetOrAddAsyncWithPolicyAndThenGetTaskObjectReturnsCorrectType() { var item = testObject; - Func> fetch = () => Task.FromResult(item); - await sut.GetOrAddAsync(TestKey, fetch, - oneHourNonRemoveableCacheItemPolicy); + await sut.GetOrAddAsync(TestKey, () => Task.FromResult(item), + oneHourNonRemoveableMemoryCacheEntryOptions); var actual = await sut.Get>(TestKey); Assert.That(actual, Is.EqualTo(item)); } + [Test] + [MaxTime(20000)] + public async Task GetOrAddAsyncWithPostEvictionCallbacksReturnsTheOriginalCachedKeyEvenIfNotGettedBeforehand() + { + string callbackKey = null; + var memoryCacheEntryOptions = new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100) + }; + memoryCacheEntryOptions.RegisterPostEvictionCallback((key, value, reason, state) => + { + callbackKey = key.ToString(); + }); + await sut.GetOrAddAsync(TestKey, () => Task.FromResult(123), memoryCacheEntryOptions); + + sut.Remove(TestKey); //force removed callback to fire + while (callbackKey == null) + Thread.Sleep(500); + + callbackKey.Should().Be(TestKey); + } + + [Test] + [MaxTime(20000)] + public async Task + GetOrAddAsyncWithPostEvictionCallbacksReturnsTheOriginalCachedObjectEvenIfNotGettedBeforehand() + { + object callbackValue = null; + var memoryCacheEntryOptions = new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100) + }; + memoryCacheEntryOptions.RegisterPostEvictionCallback((key, value, reason, state) => + { + callbackValue = value; + }); + await sut.GetOrAddAsync(TestKey, () => Task.FromResult(123), memoryCacheEntryOptions); + + sut.Remove(TestKey); //force removed callback to fire + while (callbackValue == null) + Thread.Sleep(500); + + Assert.That(callbackValue, Is.AssignableTo>()); + var callbackResultValue = await (Task) callbackValue; + Assert.AreEqual(123, callbackResultValue); + } + [Test] public async Task GetOrAddAyncAllowsCachingATask() { var cachedResult = testObject; - Func> fetchAsync = () => Task.FromResult(cachedResult); - var actualResult = await sut.GetOrAddAsync(TestKey, fetchAsync, oneHourNonRemoveableCacheItemPolicy); + Task FetchAsync() + { + return Task.FromResult(cachedResult); + } + + var actualResult = + await sut.GetOrAddAsync(TestKey, FetchAsync, oneHourNonRemoveableMemoryCacheEntryOptions); Assert.That(actualResult, Is.EqualTo(cachedResult)); } + [Test] + public async Task GetOrAddFollowinGetOrAddAsyncReturnsTheFirstObjectAndUnwrapsTheFirstTask() + { + Task FetchAsync() + { + return Task.FromResult(testObject); + } + + ComplexTestObject FetchSync() + { + return new ComplexTestObject(); + } + + var actualAsync = await sut.GetOrAddAsync(TestKey, FetchAsync); + var actualSync = sut.GetOrAdd(TestKey, FetchSync); + + Assert.IsNotNull(actualAsync); + Assert.That(actualAsync, Is.EqualTo(testObject)); + + Assert.IsNotNull(actualSync); + Assert.That(actualSync, Is.EqualTo(testObject)); + + Assert.AreEqual(actualAsync, actualSync); + } + [Test] public void GetOrAddWillAddOnFirstCall() { @@ -493,26 +606,6 @@ public void GetOrAddWillNotAddIfExistingData() Assert.AreEqual(0, times); } - [Test, Timeout(20000)] - public void GetOrAddWithCallbackOnRemovedReturnsTheOriginalCachedObjectEvenIfNotGettedBeforehand() - { - Func fetch = () => 123; - CacheEntryRemovedArguments removedCallbackArgs = null; - CacheEntryRemovedCallback callback = args => removedCallbackArgs = args; - sut.GetOrAdd(TestKey, fetch, - new CacheItemPolicy - { - AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100), - RemovedCallback = callback - }); - - sut.Remove(TestKey); //force removed callback to fire - while (removedCallbackArgs == null) - Thread.Sleep(500); - - Assert.AreEqual(123, removedCallbackArgs.CacheItem.Value); - } - [Test] public void GetOrAddWithOffsetWillAddAndReturnCached() { @@ -520,7 +613,7 @@ public void GetOrAddWithOffsetWillAddAndReturnCached() TestKey, () => new DateTime(2001, 01, 01), DateTimeOffset.Now.AddSeconds(5) - ); + ); var expectedSecond = sut.Get(TestKey); Assert.AreEqual(2001, expectedFirst.Year); @@ -530,9 +623,8 @@ public void GetOrAddWithOffsetWillAddAndReturnCached() [Test] public void GetOrAddWithPolicyAndThenGetObjectReturnsCorrectType() { - Func fetch = () => testObject; - sut.GetOrAdd(TestKey, fetch, - oneHourNonRemoveableCacheItemPolicy); + sut.GetOrAdd(TestKey, () => testObject, + oneHourNonRemoveableMemoryCacheEntryOptions); var actual = sut.Get(TestKey); Assert.IsNotNull(actual); } @@ -540,8 +632,12 @@ public void GetOrAddWithPolicyAndThenGetObjectReturnsCorrectType() [Test] public void GetOrAddWithPolicyAndThenGetValueObjectReturnsCorrectType() { - Func fetch = () => 123; - sut.GetOrAdd(TestKey, fetch, oneHourNonRemoveableCacheItemPolicy); + int Fetch() + { + return 123; + } + + sut.GetOrAdd(TestKey, Fetch, oneHourNonRemoveableMemoryCacheEntryOptions); var actual = sut.Get(TestKey); Assert.AreEqual(123, actual); } @@ -556,40 +652,37 @@ public void GetOrAddWithPolicyWillAddOnFirstCallButReturnCachedOnSecond() { times++; return new DateTime(2001, 01, 01); - }, oneHourNonRemoveableCacheItemPolicy); + }, oneHourNonRemoveableMemoryCacheEntryOptions); var expectedSecond = sut.GetOrAdd(TestKey, () => { times++; return new DateTime(2002, 01, 01); - }, oneHourNonRemoveableCacheItemPolicy); + }, oneHourNonRemoveableMemoryCacheEntryOptions); Assert.AreEqual(2001, expectedFirst.Year); Assert.AreEqual(2001, expectedSecond.Year); Assert.AreEqual(1, times); } - [Test, Timeout(20000)] - public void GetOrAddWithPolicyWithCallbackOnRemovedReturnsTheOriginalCachedObject() + [Test] + [MaxTime(20000)] + public void GetOrAddWithPostEvictionCallbackdReturnsTheOriginalCachedObjectEvenIfNotGettedBeforehand() { - Func fetch = () => 123; - CacheEntryRemovedArguments removedCallbackArgs = null; - CacheEntryRemovedCallback callback = args => removedCallbackArgs = args; - - - sut.GetOrAdd(TestKey, fetch, - new CacheItemPolicy - { - AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100), - RemovedCallback = callback - }); - sut.Get(TestKey); + object cacheValue = null; + var cacheEntryOptions = new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(100) + }.RegisterPostEvictionCallback((key, value, reason, state) => cacheValue = value); + sut.GetOrAdd(TestKey, () => 123, + cacheEntryOptions); sut.Remove(TestKey); //force removed callback to fire - while (removedCallbackArgs == null) + while (cacheValue == null) Thread.Sleep(500); - Assert.AreEqual(123, removedCallbackArgs.CacheItem.Value); + cacheValue.Should().BeOfType(); + cacheValue.Should().Be(123); } [Test] @@ -662,54 +755,5 @@ public void RemovedItemCannotBeRetrievedFromCache() sut.Remove(TestKey); Assert.Null(sut.Get(TestKey)); } - - [Test] - [Timeout(1000)] - public void GetOrAddAsyncWithALongTaskReturnsBeforeTaskCompletes() - { - var cachedResult = testObject; - Func> fetchAsync = () => Task.Delay(TimeSpan.FromMinutes(1)).ContinueWith(x=> cachedResult); - - var actualResult = sut.GetOrAddAsync(TestKey, fetchAsync); - - Assert.That(actualResult, Is.Not.Null); - Assert.That(actualResult.IsCompleted, Is.Not.True); - } - - [Test] - public void GetOrAddAsyncAFailingTaskDoesNotCacheIt() - { - Func> fetchAsync = () => - Task.Factory.StartNew( - () => { throw new ApplicationException(); }); - - Assert.ThrowsAsync(async () => await sut.GetOrAddAsync(TestKey, fetchAsync)); - - var stillCached = sut.Get>(TestKey); - - Assert.That(stillCached, Is.Null); - } - - [Test] - public void GetOrAddAsyncACancelledTaskDoesNotCacheIt() - { - Assert.ThrowsAsync(async () => await sut.GetOrAddAsync(TestKey, AsyncHelper.CreateCancelledTask)); - - var stillCached = sut.Get>(TestKey); - - Assert.That(stillCached, Is.Null); - } - - [Test] - public void GetOrAddAsyncACancelledTaskReturnsTheCacelledTaskToConsumer() - { - var cancelledTask = sut.GetOrAddAsync(TestKey, AsyncHelper.CreateCancelledTask); - - Assert.That(cancelledTask, Is.Not.Null); - - Assert.Throws(cancelledTask.Wait); - - Assert.That(cancelledTask.IsCanceled, Is.True); - } } } \ No newline at end of file diff --git a/LazyCache.UnitTests/LazyCache.UnitTests.csproj b/LazyCache.UnitTests/LazyCache.UnitTests.csproj index 77a9fcd..84c55c2 100644 --- a/LazyCache.UnitTests/LazyCache.UnitTests.csproj +++ b/LazyCache.UnitTests/LazyCache.UnitTests.csproj @@ -1,83 +1,20 @@ - - - - - Debug - AnyCPU - {7F6C8799-CA9E-43BD-AE97-72C31BB4E106} - Library - Properties - LazyCache.UnitTests - LazyCache.UnitTests - v4.5 - 512 - ..\ - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\packages\FluentAssertions.4.13.1\lib\net45\FluentAssertions.dll - True - - - ..\packages\FluentAssertions.4.13.1\lib\net45\FluentAssertions.Core.dll - True - - - ..\packages\NUnit.3.4.1\lib\net45\nunit.framework.dll - True - - - - - - - - - - - - - - - - - - {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7} - LazyCache - - - - - - - - - - + + + + netcoreapp2.2 + + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/LazyCache.UnitTests/packages.config b/LazyCache.UnitTests/packages.config deleted file mode 100644 index 0814980..0000000 --- a/LazyCache.UnitTests/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/LazyCache.sln b/LazyCache.sln index a729d3f..00183dd 100644 --- a/LazyCache.sln +++ b/LazyCache.sln @@ -1,21 +1,27 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyCache", "LazyCache\LazyCache.csproj", "{E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache", "LazyCache\LazyCache.csproj", "{E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyCache.UnitTests", "LazyCache.UnitTests\LazyCache.UnitTests.csproj", "{7F6C8799-CA9E-43BD-AE97-72C31BB4E106}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.UnitTests", "LazyCache.UnitTests\LazyCache.UnitTests.csproj", "{7F6C8799-CA9E-43BD-AE97-72C31BB4E106}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "info", "info", "{81C0E096-59B7-4129-851B-8183FDB9B02B}" ProjectSection(SolutionItems) = preProject + appveyor.yml = appveyor.yml + build.ps1 = build.ps1 Readme.md = Readme.md ReleaseNotes.md = ReleaseNotes.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{335BA426-C839-4996-8476-F3EE4056C40E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ApiAsyncCachingSample", "Samples\ApiAsyncCachingSample\ApiAsyncCachingSample.csproj", "{FB8EDC6D-CD41-47AA-9758-D8D796F7728C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheDatabaseQueriesApiSample", "CacheDatabaseQueriesApiSample\CacheDatabaseQueriesApiSample.csproj", "{5D6A88DD-230C-4057-B8EB-A987FF4F29DB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyCache.AspNetCore", "LazyCache.AspNetCore\LazyCache.AspNetCore.csproj", "{A7B07002-29F5-4463-8CA7-097C337337A1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console.Net461", "Console.Net461\Console.Net461.csproj", "{32142F20-DFCE-4DF0-A263-093111E5A3FA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -31,15 +37,27 @@ Global {7F6C8799-CA9E-43BD-AE97-72C31BB4E106}.Debug|Any CPU.Build.0 = Debug|Any CPU {7F6C8799-CA9E-43BD-AE97-72C31BB4E106}.Release|Any CPU.ActiveCfg = Release|Any CPU {7F6C8799-CA9E-43BD-AE97-72C31BB4E106}.Release|Any CPU.Build.0 = Release|Any CPU - {FB8EDC6D-CD41-47AA-9758-D8D796F7728C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FB8EDC6D-CD41-47AA-9758-D8D796F7728C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FB8EDC6D-CD41-47AA-9758-D8D796F7728C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FB8EDC6D-CD41-47AA-9758-D8D796F7728C}.Release|Any CPU.Build.0 = Release|Any CPU + {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D6A88DD-230C-4057-B8EB-A987FF4F29DB}.Release|Any CPU.Build.0 = Release|Any CPU + {A7B07002-29F5-4463-8CA7-097C337337A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7B07002-29F5-4463-8CA7-097C337337A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7B07002-29F5-4463-8CA7-097C337337A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7B07002-29F5-4463-8CA7-097C337337A1}.Release|Any CPU.Build.0 = Release|Any CPU + {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {32142F20-DFCE-4DF0-A263-093111E5A3FA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {FB8EDC6D-CD41-47AA-9758-D8D796F7728C} = {335BA426-C839-4996-8476-F3EE4056C40E} + {5D6A88DD-230C-4057-B8EB-A987FF4F29DB} = {335BA426-C839-4996-8476-F3EE4056C40E} + {32142F20-DFCE-4DF0-A263-093111E5A3FA} = {335BA426-C839-4996-8476-F3EE4056C40E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {5040E431-0FAA-4DC7-A678-D218CD57D542} EndGlobalSection EndGlobal diff --git a/LazyCache/AppCacheExtenions.cs b/LazyCache/AppCacheExtenions.cs new file mode 100644 index 0000000..3823a87 --- /dev/null +++ b/LazyCache/AppCacheExtenions.cs @@ -0,0 +1,100 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; + +namespace LazyCache +{ + public static class AppCacheExtenions + { + public static void Add(this IAppCache cache, string key, T item) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + cache.Add(key, item, cache.DefaultCachePolicy.BuildOptions()); + } + + public static void Add(this IAppCache cache, string key, T item, DateTimeOffset expires) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + cache.Add(key, item, new MemoryCacheEntryOptions {AbsoluteExpiration = expires}); + } + + public static void Add(this IAppCache cache, string key, T item, TimeSpan slidingExpiration) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + cache.Add(key, item, new MemoryCacheEntryOptions {SlidingExpiration = slidingExpiration}); + } + + public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + return cache.GetOrAdd(key, addItemFactory, cache.DefaultCachePolicy.BuildOptions()); + } + + public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory, DateTimeOffset expires) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + return cache.GetOrAdd(key, addItemFactory, new MemoryCacheEntryOptions {AbsoluteExpiration = expires}); + } + + public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory, + TimeSpan slidingExpiration) + { + return cache.GetOrAdd(key, addItemFactory, + new MemoryCacheEntryOptions {SlidingExpiration = slidingExpiration}); + } + + public static T GetOrAdd(this IAppCache cache, string key, Func addItemFactory, + MemoryCacheEntryOptions policy) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + return cache.GetOrAdd(key, entry => + { + entry.SetOptions(policy); + return addItemFactory(); + }); + } + + public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + return cache.GetOrAddAsync(key, addItemFactory, cache.DefaultCachePolicy.BuildOptions()); + } + + + public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory, + DateTimeOffset expires) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + return cache.GetOrAddAsync(key, addItemFactory, new MemoryCacheEntryOptions {AbsoluteExpiration = expires}); + } + + public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory, + TimeSpan slidingExpiration) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + return cache.GetOrAddAsync(key, addItemFactory, + new MemoryCacheEntryOptions {SlidingExpiration = slidingExpiration}); + } + + public static Task GetOrAddAsync(this IAppCache cache, string key, Func> addItemFactory, + MemoryCacheEntryOptions policy) + { + if (cache == null) throw new ArgumentNullException(nameof(cache)); + + return cache.GetOrAddAsync(key, entry => + { + entry.SetOptions(policy); + return addItemFactory(); + }); + } + } +} \ No newline at end of file diff --git a/LazyCache/AsyncLazy.cs b/LazyCache/AsyncLazy.cs index b52c837..769d6bd 100644 --- a/LazyCache/AsyncLazy.cs +++ b/LazyCache/AsyncLazy.cs @@ -19,6 +19,10 @@ public AsyncLazy(Func> taskFactory) : base(() => Task.Factory.StartNew(taskFactory).Unwrap()) { } - public TaskAwaiter GetAwaiter() { return Value.GetAwaiter(); } + + public TaskAwaiter GetAwaiter() + { + return Value.GetAwaiter(); + } } } \ No newline at end of file diff --git a/LazyCache/CacheDefaults.cs b/LazyCache/CacheDefaults.cs new file mode 100644 index 0000000..f30b90a --- /dev/null +++ b/LazyCache/CacheDefaults.cs @@ -0,0 +1,19 @@ +using System; +using Microsoft.Extensions.Caching.Memory; + +namespace LazyCache +{ + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global + public class CacheDefaults + { + public virtual int DefaultCacheDurationSeconds { get; set; } = 60 * 20; + + internal MemoryCacheEntryOptions BuildOptions() + { + return new MemoryCacheEntryOptions + { + AbsoluteExpiration = DateTimeOffset.UtcNow.AddSeconds(DefaultCacheDurationSeconds) + }; + } + } +} \ No newline at end of file diff --git a/LazyCache/CacheItemPolicy.cs b/LazyCache/CacheItemPolicy.cs new file mode 100644 index 0000000..be940fc --- /dev/null +++ b/LazyCache/CacheItemPolicy.cs @@ -0,0 +1,15 @@ +using System; +using System.Linq; +using Microsoft.Extensions.Caching.Memory; + +namespace LazyCache +{ + [Obsolete( + "CacheItemPolicy was part of System.Runtime.Caching which is no longer used by LazyCache. " + + "This class is a fake used to maintain backward compatibility and will be removed in a later version." + + "Change to MemoryCacheEntryOptions instead")] + public class CacheItemPolicy : MemoryCacheEntryOptions + { + public PostEvictionCallbackRegistration RemovedCallback => PostEvictionCallbacks.FirstOrDefault(); + } +} \ No newline at end of file diff --git a/LazyCache/CachingService.cs b/LazyCache/CachingService.cs index ac59d88..1bd5305 100644 --- a/LazyCache/CachingService.cs +++ b/LazyCache/CachingService.cs @@ -1,242 +1,235 @@ using System; -using System.Runtime.Caching; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; +using LazyCache.Providers; +using Microsoft.Extensions.Caching.Memory; namespace LazyCache { + // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global public class CachingService : IAppCache { - public CachingService() : this(MemoryCache.Default) + private readonly Lazy cacheProvider; + + private readonly SemaphoreSlim locker = new SemaphoreSlim(1, 1); + + public CachingService() : this(DefaultCacheProvider) { } - public CachingService(ObjectCache cache) + public CachingService(Lazy cacheProvider) { - if (cache == null) - throw new ArgumentNullException(nameof(cache)); - - ObjectCache = cache; - DefaultCacheDuration = 60*20; + this.cacheProvider = cacheProvider ?? throw new ArgumentNullException(nameof(cacheProvider)); } - /// - /// Seconds to cache objects for by default - /// - public int DefaultCacheDuration { get; set; } - - private DateTimeOffset DefaultExpiryDateTime => DateTimeOffset.Now.AddSeconds(DefaultCacheDuration); - - public void Add(string key, T item) + public CachingService(Func cacheProviderFactory) { - Add(key, item, DefaultExpiryDateTime); + if (cacheProviderFactory == null) throw new ArgumentNullException(nameof(cacheProviderFactory)); + cacheProvider = new Lazy(cacheProviderFactory); } - public void Add(string key, T item, DateTimeOffset expires) + public CachingService(ICacheProvider cache) : this(() => cache) { - Add(key, item, new CacheItemPolicy {AbsoluteExpiration = expires}); + if (cache == null) throw new ArgumentNullException(nameof(cache)); } - public void Add(string key, T item, TimeSpan slidingExpiration) + public static Lazy DefaultCacheProvider { get; set; } + = new Lazy(() => + new MemoryCacheProvider( + new MemoryCache( + new MemoryCacheOptions()) + )); + + /// + /// Seconds to cache objects for by default + /// + [Obsolete("DefaultCacheDuration has been replaced with DefaultCacheDurationSeconds")] + public virtual int DefaultCacheDuration { - Add(key, item, new CacheItemPolicy {SlidingExpiration = slidingExpiration}); + get => DefaultCachePolicy.DefaultCacheDurationSeconds; + set => DefaultCachePolicy.DefaultCacheDurationSeconds = value; } - public void Add(string key, T item, CacheItemPolicy policy) + /// + /// Policy defining how long items should be cached for unless specified + /// + public virtual CacheDefaults DefaultCachePolicy { get; set; } = new CacheDefaults(); + + public virtual void Add(string key, T item, MemoryCacheEntryOptions policy) { if (item == null) throw new ArgumentNullException(nameof(item)); ValidateKey(key); - ObjectCache.Set(key, item, policy); + CacheProvider.Set(key, item, policy); } - public T Get(string key) + public virtual T Get(string key) { ValidateKey(key); - var item = ObjectCache[key]; + var item = CacheProvider.Get(key); - return UnwrapLazy(item); + return GetValueFromLazy(item); } - - public async Task GetAsync(string key) + public virtual Task GetAsync(string key) { ValidateKey(key); - var item = ObjectCache[key]; - - return await UnwrapAsyncLazys(item); - } - + var item = CacheProvider.Get(key); - public T GetOrAdd(string key, Func addItemFactory) - { - return GetOrAdd(key, addItemFactory, DefaultExpiryDateTime); + return GetValueFromAsyncLazy(item); } - - public async Task GetOrAddAsync(string key, Func> addItemFactory, CacheItemPolicy policy) + public virtual T GetOrAdd(string key, Func addItemFactory) { ValidateKey(key); - var newLazyCacheItem = new AsyncLazy(addItemFactory); - - EnsureRemovedCallbackDoesNotReturnTheAsyncLazy(policy); - - var existingCacheItem = ObjectCache.AddOrGetExisting(key, newLazyCacheItem, policy); - - if (existingCacheItem != null) - return await UnwrapAsyncLazys(existingCacheItem); - + object cacheItem; + locker.Wait(); //TODO: do we really need this? Could we just lock on the key? try { - var result = newLazyCacheItem.Value; - - if (result.IsCanceled || result.IsFaulted) - ObjectCache.Remove(key); - - return await result; + cacheItem = CacheProvider.GetOrCreate(key, entry => + new Lazy(() => + { + var result = addItemFactory(entry); + EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy(entry.PostEvictionCallbacks); + return result; + }) + ); } - catch //addItemFactory errored so do not cache the exception + finally { - ObjectCache.Remove(key); - throw; + locker.Release(); } - } - - public T GetOrAdd(string key, Func addItemFactory, DateTimeOffset expires) - { - return GetOrAdd(key, addItemFactory, new CacheItemPolicy {AbsoluteExpiration = expires}); - } - - - public T GetOrAdd(string key, Func addItemFactory, TimeSpan slidingExpiration) - { - return GetOrAdd(key, addItemFactory, new CacheItemPolicy {SlidingExpiration = slidingExpiration}); - } - - public T GetOrAdd(string key, Func addItemFactory, CacheItemPolicy policy) - { - ValidateKey(key); - - var newLazyCacheItem = new Lazy(addItemFactory); - - EnsureRemovedCallbackDoesNotReturnTheLazy(policy); - - var existingCacheItem = ObjectCache.AddOrGetExisting(key, newLazyCacheItem, policy); - - if (existingCacheItem != null) - return UnwrapLazy(existingCacheItem); try { - return newLazyCacheItem.Value; + return GetValueFromLazy(cacheItem); } catch //addItemFactory errored so do not cache the exception { - ObjectCache.Remove(key); + CacheProvider.Remove(key); throw; } } - - public void Remove(string key) + public virtual void Remove(string key) { ValidateKey(key); - ObjectCache.Remove(key); + CacheProvider.Remove(key); } - public ObjectCache ObjectCache { get; } + public virtual ICacheProvider CacheProvider => cacheProvider.Value; - public async Task GetOrAddAsync(string key, Func> addItemFactory) + public virtual async Task GetOrAddAsync(string key, Func> addItemFactory) { - return await GetOrAddAsync(key, addItemFactory, DefaultExpiryDateTime); - } + ValidateKey(key); - public async Task GetOrAddAsync(string key, Func> addItemFactory, DateTimeOffset expires) - { - return await GetOrAddAsync(key, addItemFactory, new CacheItemPolicy {AbsoluteExpiration = expires}); - } + object cacheItem; - public async Task GetOrAddAsync(string key, Func> addItemFactory, TimeSpan slidingExpiration) - { - return await GetOrAddAsync(key, addItemFactory, new CacheItemPolicy {SlidingExpiration = slidingExpiration}); - } - - private static T UnwrapLazy(object item) - { - var lazy = item as Lazy; - if (lazy != null) - return lazy.Value; + // Ensure only one thread can place an item into the cache provider at a time. + // We are not evaluating the addItemFactory inside here - that happens outside the lock, + // below, and guarded using the async lazy. Here we just ensure only one thread can place + // the AsyncLazy into the cache at one time - if (item is T) - return (T) item; + await locker.WaitAsync() + .ConfigureAwait( + false); //TODO: do we really need to lock everything here - faster if we could lock on just the key? + try + { + cacheItem = CacheProvider.GetOrCreate(key, entry => + new AsyncLazy(() => + { + var result = addItemFactory(entry); + EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy(entry.PostEvictionCallbacks); + return result; + }) + ); + } + finally + { + locker.Release(); + } - var asyncLazy = item as AsyncLazy; - if (asyncLazy != null) - return asyncLazy.Value.Result; + try + { + var result = GetValueFromAsyncLazy(cacheItem); - var task = item as Task; - if (task != null) - return task.Result; + if (result.IsCanceled || result.IsFaulted) + CacheProvider.Remove(key); - return default(T); + return await result.ConfigureAwait(false); + } + catch //addItemFactory errored so do not cache the exception + { + CacheProvider.Remove(key); + throw; + } } - private static async Task UnwrapAsyncLazys(object item) + protected virtual T GetValueFromLazy(object item) { - var asyncLazy = item as AsyncLazy; - if (asyncLazy != null) - return await asyncLazy.Value; - - var task = item as Task; - if (task != null) - return await task; - - var lazy = item as Lazy; - if (lazy != null) - return lazy.Value; - - if (item is T) - return (T) item; + switch (item) + { + case Lazy lazy: + return lazy.Value; + case T variable: + return variable; + case AsyncLazy asyncLazy: + // this is async to sync - and should not really happen as long as GetOrAddAsync is used for an async + // value. Only happens when you cache something async and then try and grab it again later using + // the non async methods. + return asyncLazy.Value.ConfigureAwait(false).GetAwaiter().GetResult(); + case Task task: + return task.Result; + } return default(T); } - private static void EnsureRemovedCallbackDoesNotReturnTheLazy(CacheItemPolicy policy) + protected virtual Task GetValueFromAsyncLazy(object item) { - if (policy?.RemovedCallback != null) + switch (item) { - var originallCallback = policy.RemovedCallback; - policy.RemovedCallback = args => - { - //unwrap the cache item in a callback given one is specified - var item = args?.CacheItem?.Value as Lazy; - if (item != null) - args.CacheItem.Value = item.IsValueCreated ? item.Value : default(T); - originallCallback(args); - }; + case AsyncLazy asyncLazy: + return asyncLazy.Value; + case Task task: + return task; + // this is sync to async and only happens if you cache something sync and then get it later async + case Lazy lazy: + return Task.FromResult(lazy.Value); + case T variable: + return Task.FromResult(variable); } + + return Task.FromResult(default(T)); } - private static void EnsureRemovedCallbackDoesNotReturnTheAsyncLazy(CacheItemPolicy policy) + protected virtual void EnsureEvictionCallbackDoesNotReturnTheAsyncOrLazy( + IList callbackRegistrations) { - if (policy?.RemovedCallback != null) - { - var originallCallback = policy.RemovedCallback; - policy.RemovedCallback = args => + if (callbackRegistrations != null) + foreach (var item in callbackRegistrations) { - //unwrap the cache item in a callback given one is specified - var item = args?.CacheItem?.Value as AsyncLazy; - if (item != null) - args.CacheItem.Value = item.IsValueCreated ? item.Value : Task.FromResult(default(T)); - originallCallback(args); - }; - } - } - - private void ValidateKey(string key) + var originalCallback = item.EvictionCallback; + item.EvictionCallback = (key, value, reason, state) => + { + // before the original callback we need to unwrap the Lazy that holds the cache item + if (value is AsyncLazy asyncCacheItem) + value = asyncCacheItem.IsValueCreated ? asyncCacheItem.Value : Task.FromResult(default(T)); + else if (value is Lazy cacheItem) + value = cacheItem.IsValueCreated ? cacheItem.Value : default(T); + + // pass the unwrapped cached value to the original callback + originalCallback(key, value, reason, state); + }; + } + } + + protected virtual void ValidateKey(string key) { if (key == null) throw new ArgumentNullException(nameof(key)); diff --git a/LazyCache/IAppCache.cs b/LazyCache/IAppCache.cs index b4d65a8..b474cbe 100644 --- a/LazyCache/IAppCache.cs +++ b/LazyCache/IAppCache.cs @@ -1,30 +1,28 @@ using System; -using System.Runtime.Caching; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; namespace LazyCache { public interface IAppCache { - ObjectCache ObjectCache { get; } - void Add(string key, T item); - void Add(string key, T item, DateTimeOffset absoluteExpiration); - void Add(string key, T item, TimeSpan slidingExpiration); - void Add(string key, T item, CacheItemPolicy policy); + ICacheProvider CacheProvider { get; } - T Get(string key); + /// + /// Define the number of seconds to cache objects for by default + /// + CacheDefaults DefaultCachePolicy { get; } - T GetOrAdd(string key, Func addItemFactory); - T GetOrAdd(string key, Func addItemFactory, DateTimeOffset absoluteExpiration); - T GetOrAdd(string key, Func addItemFactory, TimeSpan slidingExpiration); - T GetOrAdd(string key, Func addItemFactory, CacheItemPolicy policy); + void Add(string key, T item, MemoryCacheEntryOptions policy); - void Remove(string key); + T Get(string key); + + T GetOrAdd(string key, Func addItemFactory); - Task GetOrAddAsync(string key, Func> addItemFactory); - Task GetOrAddAsync(string key, Func> addItemFactory, CacheItemPolicy policy); - Task GetOrAddAsync(string key, Func> addItemFactory, DateTimeOffset expires); - Task GetOrAddAsync(string key, Func> addItemFactory, TimeSpan slidingExpiration); Task GetAsync(string key); + + Task GetOrAddAsync(string key, Func> addItemFactory); + + void Remove(string key); } } \ No newline at end of file diff --git a/LazyCache/ICacheProvider.cs b/LazyCache/ICacheProvider.cs new file mode 100644 index 0000000..2007416 --- /dev/null +++ b/LazyCache/ICacheProvider.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; + +namespace LazyCache +{ + public interface ICacheProvider : IDisposable + { + void Set(string key, object item, MemoryCacheEntryOptions policy); + object Get(string key); + object GetOrCreate(string key, Func func); + void Remove(string key); + Task GetOrCreateAsync(string key, Func> func); + } +} \ No newline at end of file diff --git a/LazyCache/LazyCache.csproj b/LazyCache/LazyCache.csproj index 46650a0..e412b30 100644 --- a/LazyCache/LazyCache.csproj +++ b/LazyCache/LazyCache.csproj @@ -1,66 +1,30 @@ - - - - - Debug - AnyCPU - {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7} - Library - Properties - LazyCache - LazyCache - v4.5 - 512 - ..\ - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - - - - - - - - - - - - - - - - - - Designer - - - - - \ No newline at end of file + + + + + netstandard2.0 + true + 1.0.0 + + $(LazyCacheVersion)$(LazyCacheVersionSuffix) + $(APPVEYOR_BUILD_NUMBER) + 0 + $(LazyCacheVersion).$(AppVeyorBuildNumber) + https://github.com/alastairtree + https://github.com/alastairtree + Lazy cache is a simple, thread safe, in-memory caching library that makes it easy to add high performance caching to your dotnet app. + https://github.com/alastairtree/LazyCache + Copyright 2014 - 2018 Alastair Crabtree + https://raw.githubusercontent.com/alastairtree/LazyCache/master/artwork/logo-128.png + https://github.com/alastairtree/LazyCache + Caching Performance Speed In-memory IMemoryCache Generics ServiceCacheing Lazy Cache Lazy-Load MemoryCache CachingService AppCache ApplicationCache Memcached + See https://raw.githubusercontent.com/alastairtree/LazyCache/master/ReleaseNotes.md + MIT + + + + + + + + diff --git a/LazyCache/LazyCache.nuspec b/LazyCache/LazyCache.nuspec deleted file mode 100644 index 73d1899..0000000 --- a/LazyCache/LazyCache.nuspec +++ /dev/null @@ -1,19 +0,0 @@ - - - - $id$ - $version$ - $title$ - https://github.com/alastairtree - https://github.com/alastairtree - https://github.com/alastairtree/LazyCache/blob/master/LICENSE - https://github.com/alastairtree/LazyCache - https://raw.githubusercontent.com/alastairtree/LazyCache/master/artwork/logo-128.png - false - Lazy cache is a simple, thread safe in-memory caching service - See https://raw.githubusercontent.com/alastairtree/LazyCache/master/ReleaseNotes.md - Copyright 2014 - Lazy cache is a simple in-memory caching service for .net and c sharp. It has a developer friendly generics based API, and providing a thread safe cache implementation that guarantees to only execute your cachable delegates once (it's lazy!). Under the hood it leverages ObjectCache and Lazy to provide performance and reliability in heavy load scenarios. For more info see https://github.com/alastairtree/LazyCache - Caching Performance Speed In-memory ObjectCache Generics ServiceCacheing Lazy Cache Lazy-Load MemoryCache CachingService AppCache ApplicationCache Memcached - - \ No newline at end of file diff --git a/LazyCache/Mocks/MockCacheEntry.cs b/LazyCache/Mocks/MockCacheEntry.cs new file mode 100644 index 0000000..c306ca0 --- /dev/null +++ b/LazyCache/Mocks/MockCacheEntry.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; + +namespace LazyCache.Mocks +{ + public class MockCacheEntry : ICacheEntry + { + public MockCacheEntry(string key) + { + Key = key; + } + + public void Dispose() + { + } + + public object Key { get; } + public object Value { get; set; } + public DateTimeOffset? AbsoluteExpiration { get; set; } + public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; } + public TimeSpan? SlidingExpiration { get; set; } + public IList ExpirationTokens { get; } + public IList PostEvictionCallbacks { get; } + public CacheItemPriority Priority { get; set; } + public long? Size { get; set; } + } +} \ No newline at end of file diff --git a/LazyCache/Mocks/MockCacheProvider.cs b/LazyCache/Mocks/MockCacheProvider.cs new file mode 100644 index 0000000..c7463e7 --- /dev/null +++ b/LazyCache/Mocks/MockCacheProvider.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; + +namespace LazyCache.Mocks +{ + public class MockCacheProvider : ICacheProvider + { + public void Set(string key, object item, MemoryCacheEntryOptions policy) + { + } + + public object Get(string key) + { + return null; + } + + public object GetOrCreate(string key, Func func) + { + return func(null); + } + + public void Remove(string key) + { + } + + public Task GetOrCreateAsync(string key, Func> func) + { + return func(null); + } + + public void Dispose() + { + } + } +} \ No newline at end of file diff --git a/LazyCache/Mocks/MockCachingService.cs b/LazyCache/Mocks/MockCachingService.cs index 8f6946d..af7be48 100644 --- a/LazyCache/Mocks/MockCachingService.cs +++ b/LazyCache/Mocks/MockCachingService.cs @@ -1,6 +1,6 @@ using System; -using System.Runtime.Caching; using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; namespace LazyCache.Mocks { @@ -10,51 +10,26 @@ namespace LazyCache.Mocks /// public class MockCachingService : IAppCache { - public void Add(string key, T item) - { - } - - public void Add(string key, T item, DateTimeOffset expires) - { - } + public ICacheProvider CacheProvider { get; } = new MockCacheProvider(); + public CacheDefaults DefaultCachePolicy { get; set; } public T Get(string key) { return default(T); } - public T GetOrAdd(string key, Func addItemFactory) - { - return addItemFactory.Invoke(); - } - - public T GetOrAdd(string key, Func addItemFactory, DateTimeOffset expires) + public T GetOrAdd(string key, Func addItemFactory) { - return addItemFactory.Invoke(); + return addItemFactory(new MockCacheEntry(key)); } public void Remove(string key) { } - public Task GetOrAddAsync(string key, Func> addItemFactory, CacheItemPolicy policy) - { - return addItemFactory.Invoke(); - } - - public Task GetOrAddAsync(string key, Func> addItemFactory) - { - return addItemFactory.Invoke(); - } - - public Task GetOrAddAsync(string key, Func> addItemFactory, DateTimeOffset expires) + public Task GetOrAddAsync(string key, Func> addItemFactory) { - return addItemFactory.Invoke(); - } - - public Task GetOrAddAsync(string key, Func> addItemFactory, TimeSpan slidingExpiration) - { - return addItemFactory.Invoke(); + return addItemFactory(new MockCacheEntry(key)); } public Task GetAsync(string key) @@ -62,25 +37,8 @@ public Task GetAsync(string key) return Task.FromResult(default(T)); } - public ObjectCache ObjectCache => null; - - - public void Add(string key, T item, TimeSpan slidingExpiration) - { - } - - public void Add(string key, T item, CacheItemPolicy policy) - { - } - - public T GetOrAdd(string key, Func addItemFactory, TimeSpan slidingExpiration) - { - return addItemFactory.Invoke(); - } - - public T GetOrAdd(string key, Func addItemFactory, CacheItemPolicy policy) + public void Add(string key, T item, MemoryCacheEntryOptions policy) { - return addItemFactory.Invoke(); } } } \ No newline at end of file diff --git a/LazyCache/Properties/AssemblyInfo.cs b/LazyCache/Properties/AssemblyInfo.cs deleted file mode 100644 index f564453..0000000 --- a/LazyCache/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -[assembly: AssemblyTitle("LazyCache")] -[assembly: AssemblyDescription(@"Lazy cache is a simple,thread safe in-memory caching service")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyProduct("LazyCache")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid("86194e2f-3263-4ecc-b095-7c789779ba02")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] - -[assembly: AssemblyVersion("0.7.1.*")] \ No newline at end of file diff --git a/LazyCache/Providers/MemoryCacheProvider.cs b/LazyCache/Providers/MemoryCacheProvider.cs new file mode 100644 index 0000000..9bdfdc9 --- /dev/null +++ b/LazyCache/Providers/MemoryCacheProvider.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Memory; + +namespace LazyCache.Providers +{ + public class MemoryCacheProvider : ICacheProvider + { + internal readonly IMemoryCache cache; + + public MemoryCacheProvider(IMemoryCache cache) + { + this.cache = cache; + } + + public void Set(string key, object item, MemoryCacheEntryOptions policy) + { + cache.Set(key, item, policy); + } + + public object Get(string key) + { + return cache.Get(key); + } + + public object GetOrCreate(string key, Func factory) + { + return cache.GetOrCreate(key, factory); + } + + public void Remove(string key) + { + cache.Remove(key); + } + + public Task GetOrCreateAsync(string key, Func> factory) + { + return cache.GetOrCreateAsync(key, factory); + } + + public void Dispose() + { + cache?.Dispose(); + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index f81edea..2e5284d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Lazy cache is a simple in-memory caching service. It has a developer friendly generics based API, and provides a thread safe cache implementation that guarantees to only execute your cachable delegates once (it's lazy!). Under -the hood it leverages ObjectCache and Lazy to provide performance and +the hood it leverages Microsoft.Extensions.Caching and Lazy to provide performance and reliability in heavy load scenarios. ## Download ## @@ -17,11 +17,15 @@ LazyCache is available using [nuget](https://www.nuget.org/packages/LazyCache/). PM> Install-Package LazyCache ``` -## Sample code ## +## Quick start + +See the [quick start wiki](https://github.com/alastairtree/LazyCache/wiki/Quickstart) + +## Sample code ```csharp // Create our cache service using the defaults (Dependency injection ready). -// Uses MemoryCache.Default under the hood so cache is shared out of the box +// By default it uses a single shared cache under the hood so cache is shared out of the box (but you can configure this) IAppCache cache = new CachingService(); // Declare (but don't execute) a func/delegate whose result we want to cache @@ -36,9 +40,21 @@ As you can see the magic happens in the `GetOrAdd()` method which gives the cons It means you avoid the usual "Check the cache - execute the factory function - add results to the cache" pattern, saves you writing the double locking cache pattern and means you can be a lazy developer! -## Use case ## +## What should I use it for? + +LazyCache suits the caching of database calls, complex object graph building routines and web service calls that should be cached for performance. +Allows items to be cached for long or short periods, but defaults to 20 mins. + +## .Net framework and dotnet core support? + +The latest version targets netstandard 2.0. See [.net standard implementation support](https://docs.microsoft.com/en-us/dotnet/standard/net-standard#net-implementation-support) + +For dotnet core 2, .net framwork net461 or above, netstandard 2+, use LazyCache 2 or above. + +For .net framework without netstandard 2 support such as net45 net451 net46 use LazyCache 0.7 - 1.x + +For .net framework 4.0 use LazyCache 0.6 -Suits the caching of database calls, complex object graph building routines and web service calls that should be cached for performance. Allows items to be cached for long or short periods, but defaults to 20 mins. ## Features ## @@ -48,18 +64,16 @@ Suits the caching of database calls, complex object graph building routines and - Thread safe, concurrency ready - Async compatible - lazy single evaluation of async delegates using `GetOrAddAsync()` - Interface based API and built in `MockCache` to support test driven development and dependency injection -- Leverages ObjectCache under the hood and can be extended with your own implementation of ObjectCache -- The main class `CachingSevice` is a single class and so could be easily embedded in your application or library +- Leverages a provider model on top of IMemoryCache under the hood and can be extended with your own implementation - Good test coverage -- net45 upwards. (for .net4 use Lazycache 0.6) ## Documentation -* [the wiki](https://github.com/alastairtree/LazyCache/wiki) +* [The wiki](https://github.com/alastairtree/LazyCache/wiki) * [Adding caching to a .net application and make it faster](https://alastaircrabtree.com/the-easy-way-to-add-caching-to-net-application-and-make-it-faster-is-called-lazycache/) ## Sample Application -See `/Samples/ApiAsyncCachingSample` for an example of how to use LazyCache to cache the results of an Entity framework async query in +See [CacheDatabaseQueriesApiSample](/CacheDatabaseQueriesApiSample) for an example of how to use LazyCache to cache the results of an Entity framework query in a web api controller. Watch how the cache saves trips to the database and results are returned to the client far quicker from the -in-memory cache +in-memory cache \ No newline at end of file diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 1a61396..290379e 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,28 @@ # Release notes for LazyCache # +## Version 2.0.0 +- *BREAKING CHANGE* Upgrade to netstandard2.0 +- *BREAKING CHANGE* Change underlying cache from System.Runtime.Caching to Microsft.Extension.Caching.Memory +- *BREAKING CHANGE* Removed IAppCache.ObjectCache and changed to a cache provider model. + To access the provider use IAppCache.CacheProvider. By default we use a singleton shared in-memory cache but add your own cache provider by implmenting the simple `ICacheProvider`. +- *BREAKING CHANGE* changed from CacheItemPolicy to MemoryCacheEntryOptions. RemovedCallback is now PostEvictionCallbacks. +- Added a new replaceable global static default cache provider + + `Func DefaultCacheProvider { get; }` + + By default we use a shared in-memory cache but each instance can have it's underlying cache provider overridden from it's constructor. +- Make methods on CachingService virtual/protected to enable +- Add LazyCache.AspNetCore for dependency injection registration - ServiceCollection.AddLazyCache(); +- Update sample to use aspnet core and LazyCache.AspNetCore +- New IAppCache.DefaultCachePolicy to replace CachingService.DefaultCacheDuration +- Moved most CachingService method overloads to extension methods on IAppCache in AppCacheExtensions. API should be backwards compatible but as now extension methods this is technically an API breaking changing. +- Added new methods on IAppCache to allow you to specify cache expiry options on executution of the item factory + + `GetOrAdd(string key, Func addItemFactory)` + + `Task GetOrAddAsync(string key, Func> addItemFactory)` + + ## Version 0.7.1 - Fix async/sync interopability bug, see https://github.com/alastairtree/LazyCache/issues/12 diff --git a/Samples/ApiAsyncCachingSample/ApiAsyncCachingSample.csproj b/Samples/ApiAsyncCachingSample/ApiAsyncCachingSample.csproj deleted file mode 100644 index b1e3c2f..0000000 --- a/Samples/ApiAsyncCachingSample/ApiAsyncCachingSample.csproj +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - Debug - AnyCPU - - - 2.0 - {FB8EDC6D-CD41-47AA-9758-D8D796F7728C} - {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} - Library - Properties - ApiAsyncCachingSample - ApiAsyncCachingSample - v4.5 - true - - - - - - - - true - full - false - bin\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\ - TRACE - prompt - 4 - - - - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll - True - - - ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - True - - - ..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.0\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll - True - - - - - - - - - - - - - - - - - - - - - - - ..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll - - - ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - - - ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - - - ..\..\packages\Microsoft.AspNet.WebApi.WebHost.5.2.3\lib\net45\System.Web.Http.WebHost.dll - - - - - - - Designer - - - - - - - - Global.asax - - - - - - - - - Web.config - - - Web.config - - - - - - {E6A1EF20-94AD-4A1C-9A89-3B2FA8AD8EC7} - LazyCache - - - - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - - - - - True - True - 15586 - / - http://localhost:15586/ - False - False - - - False - - - - - - \ No newline at end of file diff --git a/Samples/ApiAsyncCachingSample/App_Start/WebApiConfig.cs b/Samples/ApiAsyncCachingSample/App_Start/WebApiConfig.cs deleted file mode 100644 index fecdb7a..0000000 --- a/Samples/ApiAsyncCachingSample/App_Start/WebApiConfig.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web.Http; - -namespace ApiAsyncCachingSample -{ - public static class WebApiConfig - { - public static void Register(HttpConfiguration config) - { - config.MapHttpAttributeRoutes(); - } - } -} diff --git a/Samples/ApiAsyncCachingSample/Controllers/DbTimeController.cs b/Samples/ApiAsyncCachingSample/Controllers/DbTimeController.cs deleted file mode 100644 index a5742df..0000000 --- a/Samples/ApiAsyncCachingSample/Controllers/DbTimeController.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Web.Http; -using ApiAsyncCachingSample.Models; -using LazyCache; - -namespace ApiAsyncCachingSample.Controllers -{ - public class DbTimeController : ApiController - { - private readonly IAppCache cache; - private readonly string cacheKey = "DbTimeController.Get"; - private readonly DbTimeContext dbContext; - - - public DbTimeController() - { - // these could be passed in using dependency injection - dbContext = new DbTimeContext(); - cache = new CachingService(); - } - - [HttpGet] - [Route("api/dbtime")] - public async Task Get() - { - Func> cacheableAsyncFunc = () => dbContext.GeDbTime(); - - var cachedDatabaseTime = await cache.GetOrAddAsync(cacheKey, cacheableAsyncFunc); - - return cachedDatabaseTime; - - // Or instead just do it all in one line if you prefer - // return await cache.GetOrAddAsync(cacheKey, dbContext.GeDbTime); - } - - [HttpDelete] - [Route("api/dbtime")] - public IHttpActionResult DeleteFromCache() - { - cache.Remove(cacheKey); - var friendlyMessage = new {Message = $"Item with key '{cacheKey}' removed from server in-memory cache"}; - return Ok(friendlyMessage); - } - } -} \ No newline at end of file diff --git a/Samples/ApiAsyncCachingSample/Global.asax b/Samples/ApiAsyncCachingSample/Global.asax deleted file mode 100644 index ecc932c..0000000 --- a/Samples/ApiAsyncCachingSample/Global.asax +++ /dev/null @@ -1 +0,0 @@ -<%@ Application Codebehind="Global.asax.cs" Inherits="ApiAsyncCachingSample.WebApiApplication" Language="C#" %> diff --git a/Samples/ApiAsyncCachingSample/Global.asax.cs b/Samples/ApiAsyncCachingSample/Global.asax.cs deleted file mode 100644 index a4351a2..0000000 --- a/Samples/ApiAsyncCachingSample/Global.asax.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Http; -using System.Web.Routing; - -namespace ApiAsyncCachingSample -{ - public class WebApiApplication : System.Web.HttpApplication - { - protected void Application_Start() - { - GlobalConfiguration.Configure(WebApiConfig.Register); - } - } -} diff --git a/Samples/ApiAsyncCachingSample/Models/DbTime.cs b/Samples/ApiAsyncCachingSample/Models/DbTime.cs deleted file mode 100644 index 5d7c4ac..0000000 --- a/Samples/ApiAsyncCachingSample/Models/DbTime.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace ApiAsyncCachingSample.Models -{ - public class DbTime - { - public DbTime(DateTime now) - { - TimeNowInTheDatabase = now; - } - - public DbTime() - { - - } - - public DateTime TimeNowInTheDatabase { get; set; } - - - } -} \ No newline at end of file diff --git a/Samples/ApiAsyncCachingSample/Models/DbTimeContext.cs b/Samples/ApiAsyncCachingSample/Models/DbTimeContext.cs deleted file mode 100644 index 12dbfb0..0000000 --- a/Samples/ApiAsyncCachingSample/Models/DbTimeContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Data.Entity; -using System.Threading.Tasks; - -namespace ApiAsyncCachingSample.Models -{ - public class DbTimeContext : DbContext - { - private static int databaseRequestCounter = 0; //just for demo - don't use static fields for statistics! - - public static int DatabaseRequestCounter() - { - return databaseRequestCounter; - } - - public async Task GeDbTime() - { - // get the current time from SQL server right now asynchronously (simulating a slow query) - var result = await Database - .SqlQuery("WAITFOR DELAY '00:00:00:500'; SELECT GETDATE() as [TimeNowInTheDatabase]") - .SingleAsync(); - - databaseRequestCounter++; - - return result; - } - } -} \ No newline at end of file diff --git a/Samples/ApiAsyncCachingSample/Properties/AssemblyInfo.cs b/Samples/ApiAsyncCachingSample/Properties/AssemblyInfo.cs deleted file mode 100644 index 577a575..0000000 --- a/Samples/ApiAsyncCachingSample/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ApiAsyncCachingSample")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ApiAsyncCachingSample")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fb8edc6d-cd41-47aa-9758-d8d796f7728c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Samples/ApiAsyncCachingSample/Web.Debug.config b/Samples/ApiAsyncCachingSample/Web.Debug.config deleted file mode 100644 index 24f336c..0000000 --- a/Samples/ApiAsyncCachingSample/Web.Debug.config +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Samples/ApiAsyncCachingSample/Web.Release.config b/Samples/ApiAsyncCachingSample/Web.Release.config deleted file mode 100644 index a657981..0000000 --- a/Samples/ApiAsyncCachingSample/Web.Release.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/Samples/ApiAsyncCachingSample/Web.config b/Samples/ApiAsyncCachingSample/Web.config deleted file mode 100644 index 0598d02..0000000 --- a/Samples/ApiAsyncCachingSample/Web.config +++ /dev/null @@ -1,63 +0,0 @@ - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Samples/ApiAsyncCachingSample/packages.config b/Samples/ApiAsyncCachingSample/packages.config deleted file mode 100644 index d98fa06..0000000 --- a/Samples/ApiAsyncCachingSample/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..97ec692 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,11 @@ +version: '2.0.0.{build}' +image: Visual Studio 2017 +configuration: Release +environment: + LazyCacheVersion: 2.0.0 + LazyCacheVersionSuffix: + LazyCacheAspNetCoreVersion: 2.0.0 + LazyCacheAspNetCoreVersionSuffix: +build_script: +- ps: '& .\build.ps1' +deploy: off \ No newline at end of file diff --git a/artwork/favicon.ico b/artwork/favicon.ico new file mode 100644 index 0000000..b252115 Binary files /dev/null and b/artwork/favicon.ico differ diff --git a/artwork/logo-128.png b/artwork/logo-128.png index e2ee16a..6ea7916 100644 Binary files a/artwork/logo-128.png and b/artwork/logo-128.png differ diff --git a/artwork/logo-32.png b/artwork/logo-32.png index 052a7cb..0b70988 100644 Binary files a/artwork/logo-32.png and b/artwork/logo-32.png differ diff --git a/artwork/logo-64.png b/artwork/logo-64.png index 42e50d7..8dcf4a3 100644 Binary files a/artwork/logo-64.png and b/artwork/logo-64.png differ diff --git a/artwork/logo.pdn b/artwork/logo.pdn index a347443..42c3613 100644 Binary files a/artwork/logo.pdn and b/artwork/logo.pdn differ diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..d0f8839 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,63 @@ +Set-StrictMode -Version latest +$ErrorActionPreference = "Stop" + + +# Taken from psake https://github.com/psake/psake + +<# +.SYNOPSIS + This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode + to see if an error occcured. If an error is detected then an exception is thrown. + This function allows you to run command-line programs without having to + explicitly check the $lastexitcode variable. +.EXAMPLE + exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" +#> +function Exec +{ + [CmdletBinding()] + param( + [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, + [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ("Error executing command {0}" -f $cmd) + ) + & $cmd + if ($lastexitcode -ne 0) { + throw ("Exec: " + $errorMessage) + } +} + +$config = "release" + +Try { + + # Get dependencies from nuget and compile + Exec { dotnet restore } + Exec { nuget restore Console.Net461 -SolutionDirectory . } + Exec { dotnet build --configuration $config --no-restore } + + # Find each test project and run tests. upload results to AppVeyor + Get-ChildItem .\**\*.csproj -Recurse | + Where-Object { $_.Name -match ".*Test(s)?.csproj$"} | + ForEach-Object { + + Exec { dotnet test $_.FullName --configuration $config --no-build --no-restore --logger "trx;LogFileName=..\..\test-result.trx" } + + # if on build server upload results to AppVeyor + if ("${ENV:APPVEYOR_JOB_ID}" -ne "") { + $wc = New-Object 'System.Net.WebClient' + $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\test-result.trx)) + } + + Remove-Item .\test-result.trx -ErrorAction SilentlyContinue + } + + # Publish the nupkg artifacts + if (Get-Command "Push-AppveyorArtifact" -errorAction SilentlyContinue) + { + Get-ChildItem .\*\bin\$config\*.nupkg -Recurse | ForEach-Object { Push-AppveyorArtifact $_.FullName -FileName $_.Name } + } + +} Catch { + $host.SetShouldExit(-1) + throw +} \ No newline at end of file