├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── GitVersionConfig.yaml ├── LICENSE.txt ├── README.md ├── appveyor.yml ├── build.bat ├── build.cake ├── build.ps1 └── src ├── Directory.Build.props ├── Polly.Caching.Memory.Specs ├── Integration │ ├── CacheRoundTripSpecsBase.cs │ ├── CacheRoundTripSpecsSyncBase.cs │ ├── CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Async.cs │ ├── CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Sync.cs │ ├── CacheRoundTripspecsAsyncBase.cs │ ├── ICachePolicyFactory.cs │ └── MemoryCachePolicyFactory.cs ├── Polly.Caching.Memory.Specs.csproj └── Unit │ └── MemoryCacheProviderSpecs.cs ├── Polly.Caching.Memory.sln ├── Polly.Caching.Memory ├── MemoryCacheProvider.cs └── Polly.Caching.Memory.csproj └── Polly.snk /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | *.md text=auto 7 | 8 | ############################################################################### 9 | # Set default behavior for command prompt diff. 10 | # 11 | # This is need for earlier builds of msysgit that does not have it on by 12 | # default for csharp files. 13 | # Note: This is only used by command line 14 | ############################################################################### 15 | #*.cs diff=csharp 16 | 17 | ############################################################################### 18 | # Set the merge driver for project and solution files 19 | # 20 | # Merging from the command prompt will add diff markers to the files if there 21 | # are conflicts (Merging from VS is not affected by the settings below, in VS 22 | # the diff markers are never inserted). Diff markers may cause the following 23 | # file extensions to fail to load in VS. An alternative would be to treat 24 | # these files as binary and thus will always conflict and require user 25 | # intervention with every merge. To do so, just uncomment the entries below 26 | ############################################################################### 27 | #*.sln merge=binary 28 | #*.csproj merge=binary 29 | #*.vbproj merge=binary 30 | #*.vcxproj merge=binary 31 | #*.vcproj merge=binary 32 | #*.dbproj merge=binary 33 | #*.fsproj merge=binary 34 | #*.lsproj merge=binary 35 | #*.wixproj merge=binary 36 | #*.modelproj merge=binary 37 | #*.sqlproj merge=binary 38 | #*.wwaproj merge=binary 39 | 40 | ############################################################################### 41 | # behavior for image files 42 | # 43 | # image files are treated as binary by default. 44 | ############################################################################### 45 | #*.jpg binary 46 | #*.png binary 47 | #*.gif binary 48 | 49 | ############################################################################### 50 | # diff behavior for common document formats 51 | # 52 | # Convert binary document formats to text before diffing them. This feature 53 | # is only available from the command line. Turn it on by uncommenting the 54 | # entries below. 55 | ############################################################################### 56 | #*.doc diff=astextplain 57 | #*.DOC diff=astextplain 58 | #*.docx diff=astextplain 59 | #*.DOCX diff=astextplain 60 | #*.dot diff=astextplain 61 | #*.DOT diff=astextplain 62 | #*.pdf diff=astextplain 63 | #*.PDF diff=astextplain 64 | #*.rtf diff=astextplain 65 | #*.RTF diff=astextplain 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | .vs/ 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | x64/ 21 | *_i.c 22 | *_p.c 23 | *.ilk 24 | *.meta 25 | *.obj 26 | *.pch 27 | *.pdb 28 | *.pgc 29 | *.pgd 30 | *.rsp 31 | *.sbr 32 | *.tlb 33 | *.tli 34 | *.tlh 35 | *.tmp 36 | *.log 37 | *.vspscc 38 | *.vssscc 39 | .builds 40 | 41 | # Visual C++ cache files 42 | ipch/ 43 | *.aps 44 | *.ncb 45 | *.opensdf 46 | *.sdf 47 | 48 | # Visual Studio profiler 49 | *.psess 50 | *.vsp 51 | *.vspx 52 | 53 | # Guidance Automation Toolkit 54 | *.gpState 55 | 56 | # ReSharper is a .NET coding add-in 57 | _ReSharper* 58 | 59 | # NCrunch 60 | *.ncrunch* 61 | .*crunch*.local.xml 62 | 63 | # GhostDoc 64 | *.GhostDoc.xml 65 | 66 | # Installshield output folder 67 | [Ee]xpress 68 | 69 | # DocProject is a documentation generator add-in 70 | DocProject/buildhelp/ 71 | DocProject/Help/*.HxT 72 | DocProject/Help/*.HxC 73 | DocProject/Help/*.hhc 74 | DocProject/Help/*.hhk 75 | DocProject/Help/*.hhp 76 | DocProject/Help/Html2 77 | DocProject/Help/html 78 | 79 | # Click-Once directory 80 | publish 81 | 82 | # Publish Web Output 83 | *.Publish.xml 84 | 85 | # NuGet Packages Directory 86 | packages 87 | 88 | # Windows Azure Build Output 89 | csx 90 | *.build.csdef 91 | 92 | # Windows Store app package directory 93 | AppPackages/ 94 | 95 | # Others 96 | [Bb]in 97 | [Oo]bj 98 | sql 99 | TestResults 100 | [Tt]est[Rr]esult* 101 | *.Cache 102 | ClientBin 103 | [Ss]tyle[Cc]op.* 104 | ~$* 105 | *.dbmdl 106 | Generated_Code #added for RIA/Silverlight projects 107 | 108 | # Backup & report files from converting an old project file to a newer 109 | # Visual Studio version. Backup files are not needed, because we have git ;-) 110 | _UpgradeReport_Files/ 111 | Backup*/ 112 | UpgradeLog*.XML 113 | 114 | artifacts 115 | build 116 | tools 117 | 118 | *.lock.json 119 | *.nuget.targets 120 | *.nuget.props -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Polly.Caching.Memory change log 2 | 3 | ## 3.0.2 4 | - No functional changes 5 | - Updated Polly dependency to latest, v7.1.1 6 | - Consolidated solution and fixed build 7 | - Added NetStandard 2.1 target (for .NET Core3.0 consumption) 8 | - Added test runs in netcoreapp3.0; .NET Framework 4.6.1; and .NET Framework 4.7.2 9 | - Updated FluentAssertions and xUnit dependencies 10 | - Updated NetStandard2.0 dependency to avoid vulnerability for Net Standard 2.0 consumption target in underlying Microsoft.Extensions.Caching.Memory package. Detail: https://securitytracker.com/id/1040152 ; https://github.com/App-vNext/Polly.Caching.MemoryCache/issues/37. 11 | 12 | _Note:_ This vulnerability was in the underlying Microsoft.Extensions.Caching.Memory package, not in Polly.Caching.MemoryCache. Consumers should have been aware of this vulnerabilty via their normal Microsoft vulnerability alert mechanisms, and updating Microsoft.Extensions.Caching.Memory in their solutions would have caused Polly.Caching.MemoryCache also to reference the updated version. However, this update patches Polly.Caching.Memory such that it is not possible to install the latest version v3.0.2 for the .Net Standard 2.0 target and reference a vulnerable underlying version of Microsoft.Extensions.Caching.Memory. 13 | 14 | ## 3.0.1 15 | - No functional changes 16 | - Updated Polly dependency to >> v7.0.2, which includes a bug fix for PolicyRegistry 17 | 18 | ## 3.0.0 19 | - Allow caching of `default(TResult)` 20 | - Compatible with Polly >= v7 21 | 22 | ## 2.0.2 23 | - No functional changes 24 | - Indicate compatibility with Polly < v7 25 | 26 | ## 2.0.2 27 | - No functional changes 28 | - Indicate compatibility with Polly < v7 29 | 30 | ## 2.0.1 31 | - Upgrade for compatibility with Polly v6.1.1 32 | 33 | ## 2.0.0 34 | - Provide a single signed package only. 35 | - Reference Polly v6.0.1. 36 | - Remove .net 4 and 4.5 support. 37 | - Add .net standard 2.0 as a target framework. 38 | - Change namespaces from Polly.Caching.MemoryCache to Polly.Caching.Memory to avoid clashes. 39 | 40 | ## 1.1.0 41 | 42 | - Polly.Caching.MemoryCache-Signed now references Polly-Signed 43 | - Reference Polly v5.9.0 to bring in cache fixes 44 | 45 | ## 1.0-RC 46 | 47 | - Upgrade to Polly v5.4.0 48 | - Correctly state RC dependency (Polly v5.4.0) 49 | 50 | ## 0.2-beta 51 | 52 | - Beta implementation 53 | - Rebase against Polly v5.3.x 54 | - Upgrade to msbuild15 build process 55 | 56 | ## 0.1-alpha 57 | 58 | - Stub repo for Polly.Caching.MemoryCache with first build script 59 | -------------------------------------------------------------------------------- /GitVersionConfig.yaml: -------------------------------------------------------------------------------- 1 | next-version: 3.0.2 -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | New BSD License 2 | = 3 | Copyright (c) 2015, App vNext 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | * Neither the name of App vNext nor the 14 | names of its contributors may be used to endorse or promote products 15 | derived from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 21 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Polly.Caching.Memory 2 | 3 | This repo contains the MemoryCache plugin for the [Polly](https://github.com/App-vNext/Polly) [Cache policy](https://github.com/App-vNext/Polly/wiki/Cache). The current version targets .NET Standard 1.3 (for .NET Core1.x), .NET Standard 2.0 (for .NET Core 2.x and .NET Framework 4.x), and .NET Standard 2.1 (for .NET Core 3.x). 4 | 5 | [![NuGet version](https://badge.fury.io/nu/Polly.Caching.Memory.svg)](https://badge.fury.io/nu/Polly.Caching.Memory) [![Build status](https://ci.appveyor.com/api/projects/status/pgd89nfdr9u4ig8m?svg=true)](https://ci.appveyor.com/project/joelhulen/polly-caching-Memory) [![Slack Status](http://www.pollytalk.org/badge.svg)](http://www.pollytalk.org) 6 | 7 | ## What is Polly? 8 | 9 | [Polly](https://github.com/App-vNext/Polly) is a .NET resilience and transient-fault-handling library that allows developers to express policies such as Retry, Circuit Breaker, Timeout, Bulkhead Isolation, Cache aside and Fallback in a fluent and thread-safe manner. 10 | 11 | Polly is a member of the [.NET Foundation](https://www.dotnetfoundation.org/about)! 12 | 13 | **Keep up to date with new feature announcements, tips & tricks, and other news through [www.thepollyproject.org](http://www.thepollyproject.org)** 14 | 15 | ![](https://raw.github.com/App-vNext/Polly/master/Polly-Logo.png) 16 | 17 | # Installing Polly.Caching.Memory via NuGet 18 | 19 | Install-Package Polly.Caching.Memory 20 | 21 | 22 | # Supported targets 23 | 24 | Polly.Caching.Memory >= v3.0.2 supports .NET Standard 1.3, .NET Standard 2.0 and .NET Standard 2.1. 25 | 26 | Polly.Caching.Memory >= v2.0 supports .NET Standard 1.3 and .NET Standard 2.0. 27 | 28 | Polly.Caching.MemoryCache <v2.0 supports .NET4.0, .NET4.5 and .NetStandard 1.3 29 | 30 | ## Dependency compatibility with Polly 31 | 32 | Polly.Caching.Memory >=v3.0.2 requires: 33 | 34 | + [Polly](https://nuget.org/packages/polly) >= v7.1.1. 35 | 36 | Polly.Caching.Memory >=v3.0 amd <v3.0.2 requires: 37 | 38 | + [Polly](https://nuget.org/packages/polly) >= v7.0.0. 39 | 40 | Polly.Caching.Memory >=v2.0.1 and <v3 requires: 41 | 42 | + [Polly](https://nuget.org/packages/polly) >= v6.1.1 and <v7. 43 | 44 | Polly.Caching.Memory v2.0.0 requires: 45 | 46 | + [Polly](https://nuget.org/packages/polly) >= v6.0.1 and <=v6.1.0. 47 | 48 | Polly.Caching.MemoryCache v1.* requires: 49 | 50 | + [Polly](https://nuget.org/packages/polly) >=v5.9.0 and <v6. 51 | 52 | # How to use the Polly.Caching.Memory plugin 53 | 54 | ### Example: Direct creation of CachePolicy (no DI) 55 | 56 | ```csharp 57 | // This approach creates a CachePolicy directly, with its own Microsoft.Extensions.Caching.Memory.MemoryCache instance: 58 | Microsoft.Extensions.Caching.Memory.IMemoryCache memoryCache 59 | = new Microsoft.Extensions.Caching.Memory.MemoryCache(new Microsoft.Extensions.Caching.Memory.MemoryCacheOptions()); 60 | Polly.Caching.Memory.MemoryCacheProvider memoryCacheProvider 61 | = new Polly.Caching.Memory.MemoryCacheProvider(memoryCache); 62 | 63 | // Create a Polly cache policy using that Polly.Caching.Memory.MemoryCacheProvider instance. 64 | var cachePolicy = Policy.Cache(memoryCacheProvider, TimeSpan.FromMinutes(5)); 65 | ``` 66 | 67 | ### Example: Configure CachePolicy via MemoryCacheProvider in StartUp, for DI 68 | 69 | ```csharp 70 | // (We pass a whole PolicyRegistry by dependency injection rather than the individual policy, 71 | // on the assumption the app will probably use multiple policies.) 72 | 73 | public class Startup 74 | { 75 | public void ConfigureServices(IServiceCollection services) 76 | { 77 | services.AddMemoryCache(); 78 | services.AddSingleton(); 79 | 80 | services.AddSingleton, Polly.Registry.PolicyRegistry>((serviceProvider) => 81 | { 82 | PolicyRegistry registry = new PolicyRegistry(); 83 | registry.Add("myCachePolicy", 84 | Policy.CacheAsync( 85 | serviceProvider 86 | .GetRequiredService() 87 | .AsyncFor(), 88 | TimeSpan.FromMinutes(5))); 89 | return registry; 90 | }); 91 | 92 | // ... 93 | } 94 | } 95 | 96 | // At the point of use, inject the policyRegistry and retrieve the policy: 97 | // (magic string "myCachePolicy" only hard-coded here to keep the example simple) 98 | public MyController(IReadOnlyPolicyRegistry policyRegistry) 99 | { 100 | var _cachePolicy = policyRegistry.Get>("myCachePolicy"); 101 | // ... 102 | } 103 | 104 | ``` 105 | 106 | For many more configuration options and usage examples of the main Polly `CachePolicy`, see the [main Polly readme](https://github.com/App-vNext/Polly#cache) and [deep doco on the Polly wiki](https://github.com/App-vNext/Polly/wiki/Cache). 107 | 108 | ## Note 109 | 110 | `Polly.Caching.Memory.MemoryCacheProvider : ISyncCacheProvider, IAsyncCacheProvider` is non-generic as the underlying `Microsoft.Extensions.Caching.Memory.IMemoryCache` is non-generic. However, when defining a generic Polly cache policy `Policy.Cache/CacheAsync(...)`, some overloads require a generic `ISyncCacheProvider` or `IAsyncCacheProvider`. 111 | 112 | In this case, use the extensions methods `MemoryCacheProvider.For()` or `MemoryCacheProvider.AsyncFor()`, as shown in the ASP.NET Core example above, to obtain a generic `ISyncCacheProvider` or `IAsyncCacheProvider`. 113 | 114 | # Release notes 115 | 116 | For details of changes by release see the [change log](CHANGELOG.md). 117 | 118 | 119 | # Acknowledgements 120 | 121 | * [@reisenberger](https://github.com/reisenberger) - MemoryCache implementation 122 | * [@seanfarrow](https://github.com/seanfarrow) and [@reisenberger](https://github.com/reisenberger) - Initial caching architecture in the main Polly repo 123 | * [@kesmy](https://github.com/kesmy) - original structuring of the build for msbuild15, in the main Polly repo 124 | * [@seanfarrow](https://github.com/seanfarrow) - v2.0 update to Signed packages only to correspond with Polly v6.0.1 125 | * [@reisenberger](https://github.com/reisenberger) - Update to Polly v7.0.0 126 | 127 | 128 | # Instructions for Contributing 129 | 130 | Please check out our [Wiki](https://github.com/App-vNext/Polly/wiki/Git-Workflow) for contributing guidelines. We are following the excellent GitHub Flow process, and would like to make sure you have all of the information needed to be a world-class contributor! 131 | 132 | Since Polly is part of the .NET Foundation, we ask our contributors to abide by their [Code of Conduct](https://www.dotnetfoundation.org/code-of-conduct). 133 | 134 | Also, we've stood up a [Slack](http://www.pollytalk.org) channel for easier real-time discussion of ideas and the general direction of Polly as a whole. Be sure to [join the conversation](http://www.pollytalk.org) today! 135 | 136 | # License 137 | 138 | Licensed under the terms of the [New BSD License](http://opensource.org/licenses/BSD-3-Clause) 139 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2019 2 | 3 | # Build script 4 | build_script: 5 | - ps: .\build.ps1 6 | 7 | # Tests 8 | test: off 9 | 10 | artifacts: 11 | - path: artifacts\nuget-package\*.nupkg 12 | - path: artifacts\nuget-package\*.snupkg 13 | 14 | environment: 15 | # Skip dotnet package caching on build servers 16 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 17 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PUSHD %~dp0 3 | PowerShell.exe -NoProfile -ExecutionPolicy Bypass -Command "& './build.ps1'" 4 | 5 | PAUSE 6 | 7 | -------------------------------------------------------------------------------- /build.cake: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // ARGUMENTS 3 | /////////////////////////////////////////////////////////////////////////////// 4 | 5 | var target = Argument("target", "Default"); 6 | var configuration = Argument("configuration", "Release"); 7 | 8 | ////////////////////////////////////////////////////////////////////// 9 | // EXTERNAL NUGET TOOLS 10 | ////////////////////////////////////////////////////////////////////// 11 | 12 | #Tool "xunit.runner.console" 13 | #Tool "GitVersion.CommandLine" 14 | 15 | ////////////////////////////////////////////////////////////////////// 16 | // EXTERNAL NUGET LIBRARIES 17 | ////////////////////////////////////////////////////////////////////// 18 | 19 | #addin "Cake.FileHelpers" 20 | #addin nuget:?package=Cake.Yaml 21 | #addin nuget:?package=YamlDotNet&version=5.2.1 22 | 23 | /////////////////////////////////////////////////////////////////////////////// 24 | // GLOBAL VARIABLES 25 | /////////////////////////////////////////////////////////////////////////////// 26 | 27 | var projectName = "Polly.Caching.Memory"; 28 | 29 | var solutions = GetFiles("./**/*.sln"); 30 | var solutionPaths = solutions.Select(solution => solution.GetDirectory()); 31 | 32 | var srcDir = Directory("./src"); 33 | var artifactsDir = Directory("./artifacts"); 34 | var testResultsDir = artifactsDir + Directory("test-results"); 35 | 36 | // NuGet 37 | var nupkgDestDir = artifactsDir + Directory("nuget-package"); 38 | 39 | // Gitversion 40 | var gitVersionPath = ToolsExePath("GitVersion.exe"); 41 | Dictionary gitVersionOutput; 42 | var gitVersionConfigFilePath = "./GitVersionConfig.yaml"; 43 | 44 | // Versioning 45 | string nugetVersion; 46 | string appveyorBuildNumber; 47 | string assemblyVersion; 48 | string assemblySemver; 49 | 50 | /////////////////////////////////////////////////////////////////////////////// 51 | // INNER CLASSES 52 | /////////////////////////////////////////////////////////////////////////////// 53 | class GitVersionConfigYaml 54 | { 55 | public string NextVersion { get; set; } 56 | } 57 | 58 | /////////////////////////////////////////////////////////////////////////////// 59 | // SETUP / TEARDOWN 60 | /////////////////////////////////////////////////////////////////////////////// 61 | 62 | Setup(_ => 63 | { 64 | Information(""); 65 | Information("----------------------------------------"); 66 | Information("Starting the cake build script"); 67 | Information("Building: " + projectName); 68 | Information("----------------------------------------"); 69 | Information(""); 70 | }); 71 | 72 | Teardown(_ => 73 | { 74 | Information("Finished running tasks."); 75 | }); 76 | 77 | ////////////////////////////////////////////////////////////////////// 78 | // PRIVATE TASKS 79 | ////////////////////////////////////////////////////////////////////// 80 | 81 | Task("__Clean") 82 | .Does(() => 83 | { 84 | DirectoryPath[] cleanDirectories = new DirectoryPath[] { 85 | testResultsDir, 86 | nupkgDestDir, 87 | artifactsDir 88 | }; 89 | 90 | CleanDirectories(cleanDirectories); 91 | 92 | foreach(var path in cleanDirectories) { EnsureDirectoryExists(path); } 93 | 94 | foreach(var path in solutionPaths) 95 | { 96 | Information("Cleaning {0}", path); 97 | DotNetCoreClean(path.ToString()); 98 | } 99 | }); 100 | 101 | Task("__RestoreNugetPackages") 102 | .Does(() => 103 | { 104 | foreach(var solution in solutions) 105 | { 106 | Information("Restoring NuGet Packages for {0}", solution); 107 | DotNetCoreRestore(solution.ToString()); 108 | } 109 | }); 110 | 111 | Task("__UpdateAssemblyVersionInformation") 112 | .Does(() => 113 | { 114 | var gitVersionSettings = new ProcessSettings() 115 | .SetRedirectStandardOutput(true); 116 | 117 | try { 118 | IEnumerable outputLines; 119 | StartProcess(gitVersionPath, gitVersionSettings, out outputLines); 120 | 121 | var output = string.Join("\n", outputLines); 122 | gitVersionOutput = Newtonsoft.Json.JsonConvert.DeserializeObject>(output); 123 | } 124 | catch 125 | { 126 | Information("Error reading git version information. Build may be running outside of a git repo. Falling back to version specified in " + gitVersionConfigFilePath); 127 | 128 | string gitVersionYamlString = System.IO.File.ReadAllText(gitVersionConfigFilePath); 129 | GitVersionConfigYaml deserialized = DeserializeYaml(gitVersionYamlString.Replace("next-version", "NextVersion")); 130 | string gitVersionConfig = deserialized.NextVersion; 131 | 132 | gitVersionOutput = new Dictionary{ 133 | { "NuGetVersion", gitVersionConfig + "-NotFromGitRepo" }, 134 | { "FullSemVer", gitVersionConfig }, 135 | { "AssemblySemVer", gitVersionConfig }, 136 | { "Major", gitVersionConfig.Split('.')[0] }, 137 | }; 138 | 139 | } 140 | 141 | Information(""); 142 | Information("Obtained raw version info for package versioning:"); 143 | Information("NuGetVersion -> {0}", gitVersionOutput["NuGetVersion"]); 144 | Information("FullSemVer -> {0}", gitVersionOutput["FullSemVer"]); 145 | Information("AssemblySemVer -> {0}", gitVersionOutput["AssemblySemVer"]); 146 | 147 | appveyorBuildNumber = gitVersionOutput["FullSemVer"].ToString(); 148 | nugetVersion = gitVersionOutput["NuGetVersion"].ToString(); 149 | assemblyVersion = gitVersionOutput["Major"].ToString() + ".0.0.0"; 150 | assemblySemver = gitVersionOutput["AssemblySemVer"].ToString(); 151 | 152 | Information(""); 153 | Information("Mapping versioning information to:"); 154 | Information("Appveyor build number -> {0}", appveyorBuildNumber); 155 | Information("Nuget package version -> {0}", nugetVersion); 156 | Information("AssemblyVersion -> {0}", assemblyVersion); 157 | Information("AssemblyFileVersion -> {0}", assemblySemver); 158 | Information("AssemblyInformationalVersion -> {0}", assemblySemver); 159 | }); 160 | 161 | Task("__UpdateDotNetStandardAssemblyVersionNumber") 162 | .Does(() => 163 | { 164 | Information("Updating Assembly Version Information"); 165 | 166 | var attributeToValueMap = new Dictionary() { 167 | { "AssemblyVersion", assemblyVersion }, 168 | { "FileVersion", assemblySemver }, 169 | { "InformationalVersion", assemblySemver }, 170 | { "Version", nugetVersion }, 171 | { "PackageVersion", nugetVersion }, 172 | }; 173 | 174 | var csproj = File("./src/" + projectName + "/" + projectName + ".csproj"); 175 | 176 | foreach(var attributeMap in attributeToValueMap) { 177 | var attribute = attributeMap.Key; 178 | var value = attributeMap.Value; 179 | 180 | var replacedFiles = ReplaceRegexInFiles(csproj, $@"\<{attribute}\>[^\<]*\", $@"<{attribute}>{value}"); 181 | if (!replacedFiles.Any()) 182 | { 183 | throw new Exception($"{attribute} version could not be updated in {csproj}."); 184 | } 185 | } 186 | 187 | }); 188 | 189 | Task("__UpdateAppVeyorBuildNumber") 190 | .WithCriteria(() => AppVeyor.IsRunningOnAppVeyor) 191 | .Does(() => 192 | { 193 | AppVeyor.UpdateBuildVersion(appveyorBuildNumber); 194 | }); 195 | 196 | Task("__BuildSolutions") 197 | .Does(() => 198 | { 199 | foreach(var solution in solutions) 200 | { 201 | Information("Building {0}", solution); 202 | 203 | var dotNetCoreBuildSettings = new DotNetCoreBuildSettings { 204 | Configuration = configuration, 205 | Verbosity = DotNetCoreVerbosity.Minimal, 206 | NoRestore = true, 207 | MSBuildSettings = new DotNetCoreMSBuildSettings { TreatAllWarningsAs = MSBuildTreatAllWarningsAs.Error } 208 | }; 209 | 210 | DotNetCoreBuild(solution.ToString(), dotNetCoreBuildSettings); 211 | } 212 | }); 213 | 214 | Task("__RunTests") 215 | .Does(() => 216 | { 217 | foreach(var specsProj in GetFiles("./src/**/*.Specs.csproj")) { 218 | DotNetCoreTest(specsProj.FullPath, new DotNetCoreTestSettings { 219 | Configuration = configuration, 220 | NoBuild = true 221 | }); 222 | } 223 | }); 224 | 225 | Task("__CreateSignedNugetPackage") 226 | .Does(() => 227 | { 228 | var packageName = projectName; 229 | 230 | Information("Building {0}.{1}.nupkg", packageName, nugetVersion); 231 | 232 | var dotNetCorePackSettings = new DotNetCorePackSettings { 233 | Configuration = configuration, 234 | NoBuild = true, 235 | OutputDirectory = nupkgDestDir 236 | }; 237 | 238 | DotNetCorePack($@"{srcDir}\{projectName}.sln", dotNetCorePackSettings); 239 | }); 240 | 241 | ////////////////////////////////////////////////////////////////////// 242 | // BUILD TASKS 243 | ////////////////////////////////////////////////////////////////////// 244 | 245 | Task("Build") 246 | .IsDependentOn("__Clean") 247 | .IsDependentOn("__RestoreNugetPackages") 248 | .IsDependentOn("__UpdateAssemblyVersionInformation") 249 | .IsDependentOn("__UpdateDotNetStandardAssemblyVersionNumber") 250 | .IsDependentOn("__UpdateAppVeyorBuildNumber") 251 | .IsDependentOn("__BuildSolutions") 252 | .IsDependentOn("__RunTests") 253 | .IsDependentOn("__CreateSignedNugetPackage"); 254 | 255 | /////////////////////////////////////////////////////////////////////////////// 256 | // PRIMARY TARGETS 257 | /////////////////////////////////////////////////////////////////////////////// 258 | 259 | Task("Default") 260 | .IsDependentOn("Build"); 261 | 262 | /////////////////////////////////////////////////////////////////////////////// 263 | // EXECUTION 264 | /////////////////////////////////////////////////////////////////////////////// 265 | 266 | RunTarget(target); 267 | 268 | ////////////////////////////////////////////////////////////////////// 269 | // HELPER FUNCTIONS 270 | ////////////////////////////////////////////////////////////////////// 271 | 272 | string ToolsExePath(string exeFileName) { 273 | var exePath = System.IO.Directory.GetFiles(@"./tools", exeFileName, SearchOption.AllDirectories).FirstOrDefault(); 274 | return exePath; 275 | } 276 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | This is a Powershell script to bootstrap a Cake build. 5 | 6 | .DESCRIPTION 7 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 8 | and execute your Cake build script with the parameters you provide. 9 | 10 | .PARAMETER Script 11 | The build script to execute. 12 | .PARAMETER Target 13 | The build script target to run. 14 | .PARAMETER Configuration 15 | The build configuration to use. 16 | .PARAMETER Verbosity 17 | Specifies the amount of information to be displayed. 18 | .PARAMETER Experimental 19 | Tells Cake to use the latest Roslyn release. 20 | .PARAMETER WhatIf 21 | Performs a dry run of the build script. 22 | No tasks will be executed. 23 | .PARAMETER Mono 24 | Tells Cake to use the Mono scripting engine. 25 | 26 | .LINK 27 | http://cakebuild.net 28 | #> 29 | 30 | Param( 31 | [string]$Script = "build.cake", 32 | [string]$Target = "Default", 33 | [string]$Configuration = "Release", 34 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 35 | [string]$Verbosity = "Verbose", 36 | [switch]$Experimental, 37 | [Alias("DryRun","Noop")] 38 | [switch]$WhatIf, 39 | [switch]$Mono, 40 | [switch]$SkipToolPackageRestore, 41 | [switch]$Verbose 42 | ) 43 | 44 | Write-Host "Preparing to run build script..." 45 | 46 | # Should we show verbose messages? 47 | if($Verbose.IsPresent) 48 | { 49 | $VerbosePreference = "continue" 50 | } 51 | 52 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 53 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 54 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 55 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 56 | 57 | # Should we use mono? 58 | $UseMono = ""; 59 | if($Mono.IsPresent) { 60 | Write-Verbose -Message "Using the Mono based scripting engine." 61 | $UseMono = "-mono" 62 | } 63 | 64 | # Should we use the new Roslyn? 65 | $UseExperimental = ""; 66 | if($Experimental.IsPresent -and !($Mono.IsPresent)) { 67 | Write-Verbose -Message "Using experimental version of Roslyn." 68 | $UseExperimental = "-experimental" 69 | } 70 | 71 | # Is this a dry run? 72 | $UseDryRun = ""; 73 | if($WhatIf.IsPresent) { 74 | $UseDryRun = "-dryrun" 75 | } 76 | 77 | # Make sure tools folder exists 78 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 79 | New-Item -Path $TOOLS_DIR -Type directory | out-null 80 | } 81 | 82 | # Try download NuGet.exe if not exists 83 | if (!(Test-Path $NUGET_EXE)) { 84 | Write-Verbose -Message "Downloading NuGet.exe..." 85 | Invoke-WebRequest -Uri https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile $NUGET_EXE 86 | } 87 | 88 | # Make sure NuGet exists where we expect it. 89 | if (!(Test-Path $NUGET_EXE)) { 90 | Throw "Could not find NuGet.exe" 91 | } 92 | 93 | # Save nuget.exe path to environment to be available to child processed 94 | $ENV:NUGET_EXE = $NUGET_EXE 95 | 96 | # Restore tools from NuGet? 97 | if(-Not $SkipToolPackageRestore.IsPresent) 98 | { 99 | # Restore tools from NuGet. 100 | Push-Location 101 | Set-Location $TOOLS_DIR 102 | 103 | Write-Verbose -Message "Restoring tools from NuGet..." 104 | 105 | # Restore packages 106 | if (Test-Path $PACKAGES_CONFIG) 107 | { 108 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion" 109 | Write-Verbose ($NuGetOutput | Out-String) 110 | } 111 | # Install just Cake if missing config 112 | else 113 | { 114 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install Cake -ExcludeVersion" 115 | Write-Verbose ($NuGetOutput | Out-String) 116 | } 117 | Pop-Location 118 | if ($LASTEXITCODE -ne 0) 119 | { 120 | exit $LASTEXITCODE 121 | } 122 | } 123 | 124 | # Make sure that Cake has been installed. 125 | if (!(Test-Path $CAKE_EXE)) { 126 | Throw "Could not find Cake.exe" 127 | } 128 | 129 | # Start Cake 130 | Write-Host "Running build script..." 131 | Invoke-Expression "$CAKE_EXE `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental" 132 | exit $LASTEXITCODE 133 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ..\Polly.snk 5 | true 6 | 7 | -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Integration/CacheRoundTripSpecsBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Xunit; 4 | 5 | namespace Polly.Caching.Memory.Specs.Integration 6 | { 7 | public abstract class CacheRoundTripSpecsBase 8 | { 9 | protected CacheRoundTripSpecsBase(ICachePolicyFactory cachePolicyFactory) 10 | { 11 | CachePolicyFactory = cachePolicyFactory; 12 | } 13 | 14 | protected ICachePolicyFactory CachePolicyFactory { get; } 15 | 16 | protected const string OperationKey = "SomeOperationKey"; 17 | 18 | public abstract Task Should_roundtrip_this_variant_of(TResult testValue); 19 | 20 | [Theory] 21 | [MemberData(nameof(SampleClassData))] 22 | public async Task Should_roundtrip_all_variants_of_reference_type(SampleClass testValue) 23 | { 24 | await Should_roundtrip_this_variant_of(testValue); 25 | } 26 | 27 | [Theory] 28 | [MemberData(nameof(SampleStringData))] 29 | public async Task Should_roundtrip_all_variants_of_string(String testValue) 30 | { 31 | await Should_roundtrip_this_variant_of(testValue); 32 | } 33 | 34 | [Theory] 35 | [MemberData(nameof(SampleNumericData))] 36 | public async Task Should_roundtrip_all_variants_of_numeric(int testValue) 37 | { 38 | await Should_roundtrip_this_variant_of(testValue); 39 | } 40 | 41 | [Theory] 42 | [MemberData(nameof(SampleEnumData))] 43 | public async Task Should_roundtrip_all_variants_of_enum(SampleEnum testValue) 44 | { 45 | await Should_roundtrip_this_variant_of(testValue); 46 | } 47 | 48 | [Theory] 49 | [MemberData(nameof(SampleBoolData))] 50 | public async Task Should_roundtrip_all_variants_of_bool(bool testValue) 51 | { 52 | await Should_roundtrip_this_variant_of(testValue); 53 | } 54 | 55 | [Theory] 56 | [MemberData(nameof(SampleNullableBoolData))] 57 | public async Task Should_roundtrip_all_variants_of_nullable_bool(bool? testValue) 58 | { 59 | await Should_roundtrip_this_variant_of(testValue); 60 | } 61 | 62 | public static TheoryData SampleClassData => 63 | new TheoryData 64 | { 65 | new SampleClass(), 66 | new SampleClass() 67 | { 68 | StringProperty = "", 69 | IntProperty = 1 70 | }, 71 | (SampleClass)null, 72 | default(SampleClass) 73 | }; 74 | 75 | public static TheoryData SampleStringData => 76 | new TheoryData 77 | { 78 | "some string", 79 | "", 80 | null, // == default(string), 81 | "null" 82 | }; 83 | 84 | public static TheoryData SampleNumericData => 85 | new TheoryData 86 | { 87 | -1, 88 | 0, // == default(int) 89 | 1 90 | }; 91 | 92 | public static TheoryData SampleEnumData => 93 | new TheoryData 94 | { 95 | SampleEnum.FirstValue, 96 | SampleEnum.SecondValue, 97 | default(SampleEnum), 98 | }; 99 | 100 | public static TheoryData SampleBoolData => 101 | new TheoryData 102 | { 103 | true, 104 | false, // == default(bool), 105 | }; 106 | 107 | public static TheoryData SampleNullableBoolData => 108 | new TheoryData 109 | { 110 | true, 111 | false, 112 | null, // == default(bool?), 113 | }; 114 | 115 | public class SampleClass 116 | { 117 | public string StringProperty { get; set; } 118 | public int IntProperty { get; set; } 119 | } 120 | 121 | public enum SampleEnum 122 | { 123 | Default, 124 | FirstValue, 125 | SecondValue, 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Integration/CacheRoundTripSpecsSyncBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using FluentAssertions; 4 | 5 | namespace Polly.Caching.Memory.Specs.Integration 6 | { 7 | public abstract class CacheRoundTripSpecsSyncBase : CacheRoundTripSpecsBase 8 | { 9 | protected CacheRoundTripSpecsSyncBase(ICachePolicyFactory cachePolicyFactory) : base(cachePolicyFactory) 10 | { 11 | } 12 | 13 | public override Task Should_roundtrip_this_variant_of(TResult testValue) 14 | { 15 | // Arrange 16 | var (cacheProvider, cache) = CachePolicyFactory.CreateSyncCachePolicy(); 17 | 18 | // Assert - should not be in cache 19 | (bool cacheHit1, TResult fromCache1) = cacheProvider.TryGet(OperationKey); 20 | cacheHit1.Should().BeFalse(); 21 | fromCache1.Should().Be(default(TResult)); 22 | 23 | // Act - should execute underlying delegate and place in cache 24 | int underlyingDelegateExecuteCount = 0; 25 | cache.Execute(ctx => 26 | { 27 | underlyingDelegateExecuteCount++; 28 | return testValue; 29 | }, new Context(OperationKey)) 30 | .Should().BeEquivalentTo(testValue); 31 | 32 | // Assert - should have executed underlying delegate 33 | underlyingDelegateExecuteCount.Should().Be(1); 34 | 35 | // Assert - should be in cache 36 | (bool cacheHit2, TResult fromCache2) = cacheProvider.TryGet(OperationKey); 37 | cacheHit2.Should().BeTrue(); 38 | fromCache2.Should().BeEquivalentTo(testValue); 39 | 40 | // Act - should execute underlying delegate and place in cache 41 | cache.Execute(ctx => 42 | { 43 | underlyingDelegateExecuteCount++; 44 | throw new Exception("Cache should be used so this should not get invoked."); 45 | }, new Context(OperationKey)) 46 | .Should().BeEquivalentTo(testValue); 47 | underlyingDelegateExecuteCount.Should().Be(1); 48 | 49 | return Task.CompletedTask; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Integration/CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Async.cs: -------------------------------------------------------------------------------- 1 | namespace Polly.Caching.Memory.Specs.Integration 2 | { 3 | public class CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Async : CacheRoundTripSpecsAsyncBase { 4 | public CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Async() : base(new MemoryCachePolicyFactory()) 5 | { 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Integration/CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Sync.cs: -------------------------------------------------------------------------------- 1 | namespace Polly.Caching.Memory.Specs.Integration 2 | { 3 | public class CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Sync : CacheRoundTripSpecsSyncBase { 4 | public CacheRoundTripSpecs_NetStandardMemoryCacheProvider_Sync() : base(new MemoryCachePolicyFactory()) 5 | { 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Integration/CacheRoundTripspecsAsyncBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using FluentAssertions; 5 | 6 | namespace Polly.Caching.Memory.Specs.Integration 7 | { 8 | public abstract class CacheRoundTripSpecsAsyncBase : CacheRoundTripSpecsBase 9 | { 10 | protected CacheRoundTripSpecsAsyncBase(ICachePolicyFactory cachePolicyFactory) : base(cachePolicyFactory) 11 | { 12 | } 13 | 14 | public override async Task Should_roundtrip_this_variant_of(TResult testValue) 15 | { 16 | // Arrange 17 | var (cacheProvider, cache) = CachePolicyFactory.CreateAsyncCachePolicy(); 18 | 19 | // Assert - should not be in cache 20 | (bool cacheHit1, TResult fromCache1) = await cacheProvider.TryGetAsync(OperationKey, CancellationToken.None, false); 21 | cacheHit1.Should().BeFalse(); 22 | fromCache1.Should().Be(default(TResult)); 23 | 24 | // Act - should execute underlying delegate and place in cache 25 | int underlyingDelegateExecuteCount = 0; 26 | (await cache.ExecuteAsync(ctx => 27 | { 28 | underlyingDelegateExecuteCount++; 29 | return Task.FromResult(testValue); 30 | }, new Context(OperationKey))) 31 | .Should().BeEquivalentTo(testValue); 32 | 33 | // Assert - should have executed underlying delegate 34 | underlyingDelegateExecuteCount.Should().Be(1); 35 | 36 | // Assert - should be in cache 37 | (bool cacheHit2, TResult fromCache2) = await cacheProvider.TryGetAsync(OperationKey, CancellationToken.None, false); 38 | cacheHit2.Should().BeTrue(); 39 | fromCache2.Should().BeEquivalentTo(testValue); 40 | 41 | // Act - should execute underlying delegate and place in cache 42 | (await cache.ExecuteAsync(ctx => 43 | { 44 | underlyingDelegateExecuteCount++; 45 | throw new Exception("Cache should be used so this should not get invoked."); 46 | }, new Context(OperationKey))) 47 | .Should().BeEquivalentTo(testValue); 48 | underlyingDelegateExecuteCount.Should().Be(1); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Integration/ICachePolicyFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Polly.Caching.Memory.Specs.Integration 2 | { 3 | public interface ICachePolicyFactory 4 | { 5 | (ISyncCacheProvider, ISyncPolicy) CreateSyncCachePolicy(); 6 | (IAsyncCacheProvider, IAsyncPolicy) CreateAsyncCachePolicy(); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Integration/MemoryCachePolicyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Caching.Memory; 3 | 4 | namespace Polly.Caching.Memory.Specs.Integration 5 | { 6 | public class MemoryCachePolicyFactory : ICachePolicyFactory 7 | { 8 | public (ISyncCacheProvider, ISyncPolicy) CreateSyncCachePolicy() 9 | { 10 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 11 | ISyncCacheProvider provider = new MemoryCacheProvider(memoryCache).For(); 12 | 13 | var policy = Policy.Cache(provider, TimeSpan.FromHours(1)); 14 | return (provider, policy); 15 | } 16 | 17 | public (IAsyncCacheProvider, IAsyncPolicy) CreateAsyncCachePolicy() 18 | { 19 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 20 | IAsyncCacheProvider provider = new MemoryCacheProvider(memoryCache).AsyncFor(); 21 | 22 | var policy = Policy.CacheAsync(provider, TimeSpan.FromHours(1)); 23 | return (provider, policy); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Polly.Caching.Memory.Specs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp1.1;netcoreapp2.0;net461;net472;netcoreapp3.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.Specs/Unit/MemoryCacheProviderSpecs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using FluentAssertions; 4 | using Polly.Caching; 5 | using Xunit; 6 | using Polly.Caching.Memory; 7 | using Microsoft.Extensions.Caching.Memory; 8 | 9 | namespace Polly.Caching.Memory.Specs.Unit 10 | { 11 | public class MemoryCacheProviderSpecs 12 | { 13 | #region Configuration 14 | 15 | [Fact] 16 | public void Should_throw_when_MemoryCacheImplementation_is_null() 17 | { 18 | Action configure = () => new MemoryCacheProvider(null); 19 | 20 | configure.Should().Throw().And.ParamName.Should().Be("memoryCache"); 21 | 22 | } 23 | 24 | [Fact] 25 | public void Should_not_throw_when_MemoryCacheImplementation_is_not_null() 26 | { 27 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 28 | 29 | Action configure = () => new MemoryCacheProvider(memoryCache); 30 | 31 | configure.Should().NotThrow(); 32 | } 33 | 34 | #endregion 35 | 36 | #region Get 37 | 38 | [Fact] 39 | public void Get_should_return_instance_previously_stored_in_cache() 40 | { 41 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 42 | 43 | string key = Guid.NewGuid().ToString(); 44 | object value = new object(); 45 | using (ICacheEntry entry = memoryCache.CreateEntry(key)) { 46 | entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10); 47 | entry.Value = value; 48 | } 49 | 50 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 51 | (bool cacheHit, object payload) = provider.TryGet(key); 52 | cacheHit.Should().BeTrue(); 53 | payload.Should().BeSameAs(value); 54 | } 55 | 56 | [Fact] 57 | public void Get_should_return_false_on_unknown_key() 58 | { 59 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 60 | 61 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 62 | (bool cacheHit, object payload) = provider.TryGet(Guid.NewGuid().ToString()); 63 | cacheHit.Should().BeFalse(); 64 | payload.Should().BeNull(); 65 | } 66 | 67 | #endregion 68 | 69 | #region Put 70 | 71 | [Fact] 72 | public void Put_should_put_item_into_configured_MemoryCacheImplementation() 73 | { 74 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 75 | 76 | string key = Guid.NewGuid().ToString(); 77 | object value = new object(); 78 | 79 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 80 | Ttl ttl = new Ttl(TimeSpan.FromSeconds(10)); 81 | provider.Put(key, value, ttl); 82 | 83 | object got; 84 | memoryCache.TryGetValue(key, out got); 85 | 86 | got.Should().BeSameAs(value); 87 | } 88 | 89 | [Fact] 90 | public void Put_should_put_item_using_passed_nonsliding_ttl() 91 | { 92 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 93 | 94 | TimeSpan shimTimeSpan = TimeSpan.FromSeconds(0.1); // If test fails transiently in different environments, consider increasing shimTimeSpan. 95 | 96 | string key = Guid.NewGuid().ToString(); 97 | object value = new object(); 98 | 99 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 100 | Ttl ttl = new Ttl(shimTimeSpan, false); 101 | provider.Put(key, value, ttl); 102 | 103 | // Initially (before ttl expires), should be able to get value from cache. 104 | object got; 105 | memoryCache.TryGetValue(key, out got); 106 | got.Should().BeSameAs(value); 107 | 108 | // Wait until the TTL on the cache item should have expired. 109 | Thread.Sleep(shimTimeSpan + shimTimeSpan); 110 | 111 | memoryCache.TryGetValue(key, out got); 112 | 113 | got.Should().NotBeSameAs(value); 114 | got.Should().BeNull(); 115 | } 116 | 117 | [Fact] 118 | public void Put_should_put_item_using_passed_sliding_ttl() 119 | { 120 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 121 | 122 | TimeSpan shimTimeSpan = TimeSpan.FromSeconds(1); // If test fails transiently in different environments, consider increasing shimTimeSpan. 123 | 124 | string key = Guid.NewGuid().ToString(); 125 | object value = new object(); 126 | 127 | // Place an item in the cache that should last for only 2x shimTimespan 128 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 129 | Ttl ttl = new Ttl(shimTimeSpan + shimTimeSpan, true); 130 | provider.Put(key, value, ttl); 131 | 132 | // Prove that we can repeatedly get it from the cache over a 5x shimTimespan period, due to repeated access. 133 | 134 | for (int i = 0; i < 5; i++) 135 | { 136 | object got; 137 | memoryCache.TryGetValue(key, out got); 138 | 139 | got.Should().BeSameAs(value, $"at iteration {i}"); 140 | 141 | Thread.Sleep(shimTimeSpan); 142 | } 143 | } 144 | 145 | #region Boundary tests 146 | 147 | [Fact] 148 | public void Put_should_put_item_using_passed_nonsliding_ttl_maxvalue() 149 | { 150 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 151 | 152 | string key = "anything"; 153 | object value = new object(); 154 | 155 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 156 | Ttl ttl = new Ttl(TimeSpan.MaxValue, false); 157 | provider.Put(key, value, ttl); 158 | 159 | object got; 160 | memoryCache.TryGetValue(key, out got); 161 | got.Should().BeSameAs(value); 162 | } 163 | 164 | [Fact] 165 | public void Put_should_put_item_using_passed_sliding_ttl_maxvalue() 166 | { 167 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 168 | 169 | string key = "anything"; 170 | object value = new object(); 171 | 172 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 173 | 174 | TimeSpan maxSlidingExpiration = TimeSpan.MaxValue; 175 | 176 | Ttl ttl = new Ttl(maxSlidingExpiration, true); 177 | provider.Put(key, value, ttl); 178 | 179 | object got; 180 | memoryCache.TryGetValue(key, out got); 181 | got.Should().BeSameAs(value); 182 | } 183 | 184 | [Fact] 185 | public void Put_should_put_item_using_passed_nonsliding_ttl_zero() 186 | { 187 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 188 | 189 | string key = "anything"; 190 | object value = new object(); 191 | 192 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 193 | 194 | TimeSpan minExpiration = TimeSpan.FromMilliseconds(1); // This is the minimum permitted non-sliding ttl for .NetStandard 195 | 196 | Ttl ttl = new Ttl(minExpiration, false); 197 | provider.Put(key, value, ttl); 198 | 199 | Thread.Sleep(TimeSpan.FromMilliseconds(10)); 200 | 201 | object got; 202 | memoryCache.TryGetValue(key, out got); 203 | got.Should().BeNull(); 204 | } 205 | 206 | [Fact] 207 | public void Put_should_put_item_using_passed_sliding_ttl_zero() 208 | { 209 | IMemoryCache memoryCache = new MemoryCache(new MemoryCacheOptions()); 210 | 211 | string key = "anything"; 212 | object value = new object(); 213 | 214 | MemoryCacheProvider provider = new MemoryCacheProvider(memoryCache); 215 | 216 | TimeSpan minExpiration = TimeSpan.FromMilliseconds(1); // This is the minimum permitted sliding ttl for .NetStandard 217 | 218 | Ttl ttl = new Ttl(minExpiration, false); 219 | provider.Put(key, value, ttl); 220 | 221 | Thread.Sleep(TimeSpan.FromMilliseconds(10)); 222 | 223 | object got; 224 | memoryCache.TryGetValue(key, out got); 225 | got.Should().BeNull(); 226 | } 227 | #endregion 228 | 229 | #endregion 230 | } 231 | } -------------------------------------------------------------------------------- /src/Polly.Caching.Memory.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29403.142 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Caching.Memory", "Polly.Caching.Memory\Polly.Caching.Memory.csproj", "{04A6D5EC-D728-4776-A8B3-3614825EB80E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Polly.Caching.Memory.Specs", "Polly.Caching.Memory.Specs\Polly.Caching.Memory.Specs.csproj", "{9A630F8D-EC57-420D-886E-56B771C67FFD}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {04A6D5EC-D728-4776-A8B3-3614825EB80E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {04A6D5EC-D728-4776-A8B3-3614825EB80E}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {04A6D5EC-D728-4776-A8B3-3614825EB80E}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {04A6D5EC-D728-4776-A8B3-3614825EB80E}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {9A630F8D-EC57-420D-886E-56B771C67FFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {9A630F8D-EC57-420D-886E-56B771C67FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {9A630F8D-EC57-420D-886E-56B771C67FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {9A630F8D-EC57-420D-886E-56B771C67FFD}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {225C7584-19D4-4811-B41C-C4788F609986} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/Polly.Caching.Memory/MemoryCacheProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Polly.Utilities; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Microsoft.Extensions.Caching.Memory; 6 | 7 | namespace Polly.Caching.Memory 8 | { 9 | /// 10 | /// A cache provider for the Polly CachePolicy, using a passed-in instance of as the store. 11 | /// 12 | public class MemoryCacheProvider : ISyncCacheProvider, IAsyncCacheProvider 13 | { 14 | private readonly IMemoryCache _cache; 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | /// The memory cache instance in which to store cached items. 20 | public MemoryCacheProvider(IMemoryCache memoryCache) 21 | { 22 | _cache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache)); 23 | } 24 | 25 | /// 26 | /// Gets a value from cache. 27 | /// 28 | /// The cache key. 29 | /// 30 | /// A tuple whose first element is a value indicating whether the key was found in the cache, 31 | /// and whose second element is the value from the cache (null if not found). 32 | /// 33 | public (bool, object) TryGet(string key) 34 | { 35 | bool cacheHit = _cache.TryGetValue(key, out var value); 36 | return (cacheHit, value); 37 | } 38 | 39 | /// 40 | /// Puts the specified value in the cache. 41 | /// 42 | /// The cache key. 43 | /// The value to put into the cache. 44 | /// The time-to-live for the cache entry. 45 | public void Put(string key, object value, Ttl ttl) 46 | { 47 | TimeSpan remaining = DateTimeOffset.MaxValue - SystemClock.DateTimeOffsetUtcNow(); 48 | MemoryCacheEntryOptions options = new MemoryCacheEntryOptions(); 49 | 50 | if (ttl.SlidingExpiration) 51 | { 52 | options.SlidingExpiration = ttl.Timespan < remaining ? ttl.Timespan : remaining; 53 | } 54 | else 55 | { 56 | if (ttl.Timespan == TimeSpan.MaxValue) 57 | { 58 | options.AbsoluteExpiration = DateTimeOffset.MaxValue; 59 | } 60 | else 61 | { 62 | options.AbsoluteExpirationRelativeToNow = ttl.Timespan < remaining ? ttl.Timespan : remaining; 63 | } 64 | } 65 | 66 | _cache.Set(key, value, options); 67 | } 68 | 69 | /// 70 | /// Gets a value from the cache asynchronously. 71 | /// The implementation is synchronous as there is no advantage to an asynchronous implementation for an in-memory cache. 72 | /// 73 | /// The cache key. 74 | /// The cancellation token. 75 | /// Whether async calls should continue on a captured synchronization context. Note: if the underlying cache's async API does not support controlling whether to continue on a captured context, async Policy executions with continueOnCapturedContext == true cannot be guaranteed to remain on the captured context. 76 | /// 77 | /// A promising as Result a tuple whose first element is a value indicating whether 78 | /// the key was found in the cache, and whose second element is the value from the cache (null if not found). 79 | /// 80 | public Task<(bool, object)> TryGetAsync(string key, CancellationToken cancellationToken, bool continueOnCapturedContext) 81 | { 82 | cancellationToken.ThrowIfCancellationRequested(); 83 | return Task.FromResult(TryGet(key)); 84 | } 85 | 86 | /// 87 | /// Puts the specified value in the cache as part of an asynchronous execution. 88 | /// The implementation is synchronous as there is no advantage to an asynchronous implementation for an in-memory cache. 89 | /// 90 | /// The cache key. 91 | /// The value to put into the cache. 92 | /// The time-to-live for the cache entry. 93 | /// The cancellation token. 94 | /// Whether async calls should continue on a captured synchronization context. For , this parameter is irrelevant and is ignored, as the implementation is synchronous. 95 | /// A which completes when the value has been cached. 96 | public Task PutAsync(string key, object value, Ttl ttl, CancellationToken cancellationToken, bool continueOnCapturedContext) 97 | { 98 | cancellationToken.ThrowIfCancellationRequested(); 99 | Put(key, value, ttl); 100 | return Task.CompletedTask; 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Polly.Caching.Memory/Polly.Caching.Memory.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard1.3;netstandard2.0;netstandard2.1 4 | Polly.Caching.Memory 5 | Polly.Caching.Memory 6 | 3.0.2 7 | 3.0.0.0 8 | 3.0.2.0 9 | 3.0.2.0 10 | 3.0.2 11 | App vNext 12 | Copyright (c) 2019, App vNext 13 | Polly.Caching.Memory is a plug-in for the .NET OSS resilience library Polly, supporting Microsoft.Extensions.Caching.Memory.MemoryCache as a provider for Polly's CachePolicy. 14 | en-US 15 | true 16 | true 17 | App vNext 18 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 19 | 20 | 21 | true 22 | true 23 | snupkg 24 | 25 | 26 | 27 | 28 | 29 | true 30 | 31 | 32 | 1.6.1 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | en-US 49 | Polly.Caching.Memory 50 | BSD-3-Clause 51 | https://raw.github.com/App-vNext/Polly/master/Polly.png 52 | https://github.com/App-vNext/Polly.Caching.MemoryCache 53 | Polly Cache Caching Cache-aside 54 | See https://github.com/App-vNext/Polly.Caching.MemoryCache/blob/master/CHANGELOG.md 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/Polly.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/App-vNext/Polly.Caching.MemoryCache/0b7cbc6ea4070006f8c23a18f5fe9a0f1eef1e17/src/Polly.snk --------------------------------------------------------------------------------