├── .appveyor.yml ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .vsts-pipelines └── builds │ ├── ci-internal.yml │ └── ci-public.yml ├── CONTRIBUTING.md ├── Caching.sln ├── Directory.Build.props ├── Directory.Build.targets ├── LICENSE.txt ├── NuGet.config ├── NuGetPackageVerifier.json ├── README.md ├── build.cmd ├── build.sh ├── build ├── Key.snk ├── dependencies.props ├── repo.props └── sources.props ├── korebuild-lock.txt ├── korebuild.json ├── run.cmd ├── run.ps1 ├── run.sh ├── samples ├── MemoryCacheConcurencySample │ ├── MemoryCacheConcurencySample.csproj │ └── Program.cs ├── MemoryCacheFileWatchSample │ ├── MemoryCacheFileWatchSample.csproj │ ├── Program.cs │ └── WatchedFiles │ │ └── example.txt ├── MemoryCacheSample │ ├── MemoryCacheSample.csproj │ ├── MemoryCacheWeakReferenceExtensions.cs │ ├── Program.cs │ └── WeakToken.cs ├── ProfilingSample │ ├── ProfilingSample.csproj │ └── Program.cs ├── RedisCacheSample │ ├── Program.cs │ └── RedisCacheSample.csproj ├── SqlServerCacheConcurencySample │ ├── Program.cs │ ├── SqlServerCacheConcurencySample.csproj │ └── config.json └── SqlServerCacheSample │ ├── Program.cs │ ├── SqlServerCacheSample.csproj │ └── config.json ├── src ├── Directory.Build.props ├── Microsoft.Extensions.Caching.Abstractions │ ├── CacheEntryExtensions.cs │ ├── CacheItemPriority.cs │ ├── DistributedCacheEntryExtensions.cs │ ├── DistributedCacheEntryOptions.cs │ ├── DistributedCacheExtensions.cs │ ├── EvictionReason.cs │ ├── ICacheEntry.cs │ ├── IDistributedCache.cs │ ├── IMemoryCache.cs │ ├── Internal │ │ ├── ISystemClock.cs │ │ └── SystemClock.cs │ ├── MemoryCacheEntryExtensions.cs │ ├── MemoryCacheEntryOptions.cs │ ├── MemoryCacheExtensions.cs │ ├── Microsoft.Extensions.Caching.Abstractions.csproj │ ├── PostEvictionCallbackRegistration.cs │ ├── PostEvictionDelegate.cs │ └── baseline.netcore.json ├── Microsoft.Extensions.Caching.Memory │ ├── CacheEntry.cs │ ├── CacheEntryHelper.cs │ ├── CacheEntryStack.cs │ ├── MemoryCache.cs │ ├── MemoryCacheOptions.cs │ ├── MemoryCacheServiceCollectionExtensions.cs │ ├── MemoryDistributedCache.cs │ ├── MemoryDistributedCacheOptions.cs │ ├── Microsoft.Extensions.Caching.Memory.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── baseline.netcore.json ├── Microsoft.Extensions.Caching.SqlServer │ ├── Columns.cs │ ├── DatabaseOperations.cs │ ├── IDatabaseOperations.cs │ ├── Microsoft.Extensions.Caching.SqlServer.csproj │ ├── MonoDatabaseOperations.cs │ ├── MonoSqlParameterCollectionExtensions.cs │ ├── PlatformHelper.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── SqlParameterCollectionExtensions.cs │ ├── SqlQueries.cs │ ├── SqlServerCache.cs │ ├── SqlServerCacheOptions.cs │ ├── SqlServerCacheServiceCollectionExtensions.cs │ └── baseline.netcore.json └── Microsoft.Extensions.Caching.StackExchangeRedis │ ├── Microsoft.Extensions.Caching.StackExchangeRedis.csproj │ ├── RedisCache.cs │ ├── RedisCacheOptions.cs │ ├── RedisCacheServiceCollectionExtensions.cs │ └── RedisExtensions.cs ├── test ├── Directory.Build.props ├── Microsoft.Extensions.Caching.Memory.Tests │ ├── CacheEntryScopeExpirationTests.cs │ ├── CacheServiceExtensionsTests.cs │ ├── CapacityTests.cs │ ├── CompactTests.cs │ ├── Infrastructure │ │ ├── TestClock.cs │ │ ├── TestExpirationToken.cs │ │ └── TokenCallbackRegistration.cs │ ├── MemoryCacheSetAndRemoveTests.cs │ ├── Microsoft.Extensions.Caching.Memory.Tests.csproj │ ├── TimeExpirationTests.cs │ └── TokenExpirationTests.cs ├── Microsoft.Extensions.Caching.SqlServer.Tests │ ├── CacheItemInfo.cs │ ├── Microsoft.Extensions.Caching.SqlServer.Tests.csproj │ ├── SqlServerCacheServicesExtensionsTest.cs │ ├── SqlServerCacheWithDatabaseTest.cs │ ├── TestClock.cs │ ├── TestOptions.cs │ └── config.json └── Microsoft.Extensions.Caching.StackExchangeRedis.Tests │ ├── CacheServiceExtensionsTests.cs │ ├── Infrastructure │ ├── RedisTestConfig.cs │ ├── RedisXunitTestExecutor.cs │ └── RedisXunitTestFramework.cs │ ├── Microsoft.Extensions.Caching.StackExchangeRedis.Tests.csproj │ ├── RedisCacheSetAndRemoveTests.cs │ └── TimeExpirationTests.cs └── version.props /.appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf true 3 | branches: 4 | only: 5 | - master 6 | - /^release\/.*$/ 7 | - /^(.*\/)?ci-.*$/ 8 | build_script: 9 | - ps: .\run.ps1 default-build 10 | clone_depth: 1 11 | environment: 12 | global: 13 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 14 | DOTNET_CLI_TELEMETRY_OPTOUT: 1 15 | test: 'off' 16 | deploy: 'off' 17 | os: Visual Studio 2017 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.resx text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | 46 | *.csproj text=auto 47 | *.vbproj text=auto 48 | *.fsproj text=auto 49 | *.dbproj text=auto 50 | *.sln text=auto eol=crlf 51 | 52 | *.sh eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | TestResults/ 4 | .nuget/ 5 | _ReSharper.*/ 6 | packages/ 7 | artifacts/ 8 | PublishProfiles/ 9 | *.user 10 | *.suo 11 | *.cache 12 | *.docstates 13 | _ReSharper.* 14 | nuget.exe 15 | *net45.csproj 16 | *net451.csproj 17 | *k10.csproj 18 | *.psess 19 | *.vsp 20 | *.pidb 21 | *.userprefs 22 | *DS_Store 23 | *.ncrunchsolution 24 | *.*sdf 25 | *.ipch 26 | *.sln.ide 27 | project.lock.json 28 | .vs 29 | .vscode/ 30 | .build/ 31 | .testPublish/ 32 | global.json 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | sudo: false 3 | dist: trusty 4 | env: 5 | global: 6 | - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true 7 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1 8 | mono: none 9 | os: 10 | - linux 11 | - osx 12 | osx_image: xcode8.2 13 | addons: 14 | apt: 15 | packages: 16 | - libunwind8 17 | branches: 18 | only: 19 | - master 20 | - /^release\/.*$/ 21 | - /^(.*\/)?ci-.*$/ 22 | before_install: 23 | - if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s 24 | /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib 25 | /usr/local/lib/; fi 26 | script: 27 | - ./build.sh 28 | -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-internal.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | resources: 6 | repositories: 7 | - repository: buildtools 8 | type: git 9 | name: aspnet-BuildTools 10 | ref: refs/heads/master 11 | 12 | phases: 13 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools 14 | -------------------------------------------------------------------------------- /.vsts-pipelines/builds/ci-public.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | - release/* 4 | 5 | # See https://github.com/aspnet/BuildTools 6 | resources: 7 | repositories: 8 | - repository: buildtools 9 | type: github 10 | endpoint: DotNet-Bot GitHub Connection 11 | name: aspnet/BuildTools 12 | ref: refs/heads/master 13 | 14 | phases: 15 | - template: .vsts-pipelines/templates/project-ci.yml@buildtools 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ====== 3 | 4 | Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. 5 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Microsoft .NET Extensions 12 | https://github.com/aspnet/Caching 13 | git 14 | $(MSBuildThisFileDirectory) 15 | $(MSBuildThisFileDirectory)build\Key.snk 16 | true 17 | true 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(MicrosoftNETCoreApp20PackageVersion) 4 | $(MicrosoftNETCoreApp21PackageVersion) 5 | $(MicrosoftNETCoreApp22PackageVersion) 6 | $(NETStandardLibrary20PackageVersion) 7 | 8 | 99.9 9 | 10 | 11 | -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NuGetPackageVerifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "Default": { 3 | "rules": [ 4 | "DefaultCompositeRule" 5 | ] 6 | } 7 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Caching [Archived] 2 | ================== 3 | 4 | **This GitHub project has been archived.** Ongoing development on this project can be found in . 5 | 6 | Contains libraries for in-memory caching and distributed caching. Includes distributed cache implementations for in-memory, Microsoft SQL Server, and Redis. 7 | 8 | This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [AspNetCore](https://github.com/aspnet/AspNetCore) repo. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | 6 | # Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) 7 | chmod +x "$DIR/run.sh"; sync 8 | "$DIR/run.sh" default-build "$@" 9 | -------------------------------------------------------------------------------- /build/Key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnet/Caching/9db2c381b19ff2aeb8d6783f145c3c41e1529b78/build/Key.snk -------------------------------------------------------------------------------- /build/dependencies.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 4 | 5 | 6 | 3.0.0-alpha1-20181004.7 7 | 3.0.0-alpha1-10584 8 | 3.0.0-alpha1-10584 9 | 3.0.0-alpha1-10584 10 | 3.0.0-alpha1-10584 11 | 3.0.0-alpha1-10584 12 | 3.0.0-alpha1-10584 13 | 3.0.0-alpha1-10584 14 | 3.0.0-alpha1-10584 15 | 2.0.9 16 | 2.1.3 17 | 2.2.0-preview3-27001-02 18 | 15.6.1 19 | 4.9.0 20 | 2.0.3 21 | 2.0.513 22 | 4.6.0-preview1-26907-04 23 | 2.3.1 24 | 2.4.0 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /build/repo.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Internal.AspNetCore.Universe.Lineup 7 | https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /build/sources.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $(DotNetRestoreSources) 6 | 7 | $(RestoreSources); 8 | https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; 9 | https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; 10 | https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; 11 | 12 | 13 | $(RestoreSources); 14 | https://api.nuget.org/v3/index.json; 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /korebuild-lock.txt: -------------------------------------------------------------------------------- 1 | version:3.0.0-alpha1-20181004.7 2 | commithash:27fabdaf2b1d4753c3d2749581694ca65d78f7f2 3 | -------------------------------------------------------------------------------- /korebuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 3 | "channel": "master" 4 | } 5 | -------------------------------------------------------------------------------- /run.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" 3 | -------------------------------------------------------------------------------- /run.ps1: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env powershell 2 | #requires -version 4 3 | 4 | <# 5 | .SYNOPSIS 6 | Executes KoreBuild commands. 7 | 8 | .DESCRIPTION 9 | Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. 10 | 11 | .PARAMETER Command 12 | The KoreBuild command to run. 13 | 14 | .PARAMETER Path 15 | The folder to build. Defaults to the folder containing this script. 16 | 17 | .PARAMETER Channel 18 | The channel of KoreBuild to download. Overrides the value from the config file. 19 | 20 | .PARAMETER DotNetHome 21 | The directory where .NET Core tools will be stored. 22 | 23 | .PARAMETER ToolsSource 24 | The base url where build tools can be downloaded. Overrides the value from the config file. 25 | 26 | .PARAMETER Update 27 | Updates KoreBuild to the latest version even if a lock file is present. 28 | 29 | .PARAMETER Reinstall 30 | Re-installs KoreBuild 31 | 32 | .PARAMETER ConfigFile 33 | The path to the configuration file that stores values. Defaults to korebuild.json. 34 | 35 | .PARAMETER ToolsSourceSuffix 36 | The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. 37 | 38 | .PARAMETER CI 39 | Sets up CI specific settings and variables. 40 | 41 | .PARAMETER Arguments 42 | Arguments to be passed to the command 43 | 44 | .NOTES 45 | This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. 46 | When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. 47 | 48 | The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set 49 | in the file are overridden by command line parameters. 50 | 51 | .EXAMPLE 52 | Example config file: 53 | ```json 54 | { 55 | "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", 56 | "channel": "master", 57 | "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" 58 | } 59 | ``` 60 | #> 61 | [CmdletBinding(PositionalBinding = $false)] 62 | param( 63 | [Parameter(Mandatory = $true, Position = 0)] 64 | [string]$Command, 65 | [string]$Path = $PSScriptRoot, 66 | [Alias('c')] 67 | [string]$Channel, 68 | [Alias('d')] 69 | [string]$DotNetHome, 70 | [Alias('s')] 71 | [string]$ToolsSource, 72 | [Alias('u')] 73 | [switch]$Update, 74 | [switch]$Reinstall, 75 | [string]$ToolsSourceSuffix, 76 | [string]$ConfigFile = $null, 77 | [switch]$CI, 78 | [Parameter(ValueFromRemainingArguments = $true)] 79 | [string[]]$Arguments 80 | ) 81 | 82 | Set-StrictMode -Version 2 83 | $ErrorActionPreference = 'Stop' 84 | 85 | # 86 | # Functions 87 | # 88 | 89 | function Get-KoreBuild { 90 | 91 | $lockFile = Join-Path $Path 'korebuild-lock.txt' 92 | 93 | if (!(Test-Path $lockFile) -or $Update) { 94 | Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix 95 | } 96 | 97 | $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 98 | if (!$version) { 99 | Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" 100 | } 101 | $version = $version.TrimStart('version:').Trim() 102 | $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) 103 | 104 | if ($Reinstall -and (Test-Path $korebuildPath)) { 105 | Remove-Item -Force -Recurse $korebuildPath 106 | } 107 | 108 | if (!(Test-Path $korebuildPath)) { 109 | Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" 110 | New-Item -ItemType Directory -Path $korebuildPath | Out-Null 111 | $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" 112 | 113 | try { 114 | $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" 115 | Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix 116 | if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { 117 | # Use built-in commands where possible as they are cross-plat compatible 118 | Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath 119 | } 120 | else { 121 | # Fallback to old approach for old installations of PowerShell 122 | Add-Type -AssemblyName System.IO.Compression.FileSystem 123 | [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) 124 | } 125 | } 126 | catch { 127 | Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore 128 | throw 129 | } 130 | finally { 131 | Remove-Item $tmpfile -ErrorAction Ignore 132 | } 133 | } 134 | 135 | return $korebuildPath 136 | } 137 | 138 | function Join-Paths([string]$path, [string[]]$childPaths) { 139 | $childPaths | ForEach-Object { $path = Join-Path $path $_ } 140 | return $path 141 | } 142 | 143 | function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { 144 | if ($RemotePath -notlike 'http*') { 145 | Copy-Item $RemotePath $LocalPath 146 | return 147 | } 148 | 149 | $retries = 10 150 | while ($retries -gt 0) { 151 | $retries -= 1 152 | try { 153 | Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath 154 | return 155 | } 156 | catch { 157 | Write-Verbose "Request failed. $retries retries remaining" 158 | } 159 | } 160 | 161 | Write-Error "Download failed: '$RemotePath'." 162 | } 163 | 164 | # 165 | # Main 166 | # 167 | 168 | # Load configuration or set defaults 169 | 170 | $Path = Resolve-Path $Path 171 | if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } 172 | 173 | if (Test-Path $ConfigFile) { 174 | try { 175 | $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json 176 | if ($config) { 177 | if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } 178 | if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} 179 | } 180 | } 181 | catch { 182 | Write-Host -ForegroundColor Red $Error[0] 183 | Write-Error "$ConfigFile contains invalid JSON." 184 | exit 1 185 | } 186 | } 187 | 188 | if (!$DotNetHome) { 189 | $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` 190 | elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` 191 | elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` 192 | else { Join-Path $PSScriptRoot '.dotnet'} 193 | } 194 | 195 | if (!$Channel) { $Channel = 'master' } 196 | if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } 197 | 198 | # Execute 199 | 200 | $korebuildPath = Get-KoreBuild 201 | Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') 202 | 203 | try { 204 | Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI 205 | Invoke-KoreBuildCommand $Command @Arguments 206 | } 207 | finally { 208 | Remove-Module 'KoreBuild' -ErrorAction Ignore 209 | } 210 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | # 6 | # variables 7 | # 8 | 9 | RESET="\033[0m" 10 | RED="\033[0;31m" 11 | YELLOW="\033[0;33m" 12 | MAGENTA="\033[0;95m" 13 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 14 | [ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" 15 | verbose=false 16 | update=false 17 | reinstall=false 18 | repo_path="$DIR" 19 | channel='' 20 | tools_source='' 21 | tools_source_suffix='' 22 | ci=false 23 | 24 | # 25 | # Functions 26 | # 27 | __usage() { 28 | echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" 29 | echo "" 30 | echo "Arguments:" 31 | echo " command The command to be run." 32 | echo " ... Arguments passed to the command. Variable number of arguments allowed." 33 | echo "" 34 | echo "Options:" 35 | echo " --verbose Show verbose output." 36 | echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." 37 | echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." 38 | echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." 39 | echo " --path The directory to build. Defaults to the directory containing the script." 40 | echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." 41 | echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." 42 | echo " -u|--update Update to the latest KoreBuild even if the lock file is present." 43 | echo " --reinstall Reinstall KoreBuild." 44 | echo " --ci Apply CI specific settings and environment variables." 45 | echo "" 46 | echo "Description:" 47 | echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." 48 | echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." 49 | 50 | if [[ "${1:-}" != '--no-exit' ]]; then 51 | exit 2 52 | fi 53 | } 54 | 55 | get_korebuild() { 56 | local version 57 | local lock_file="$repo_path/korebuild-lock.txt" 58 | if [ ! -f "$lock_file" ] || [ "$update" = true ]; then 59 | __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" 60 | fi 61 | version="$(grep 'version:*' -m 1 "$lock_file")" 62 | if [[ "$version" == '' ]]; then 63 | __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" 64 | return 1 65 | fi 66 | version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" 67 | local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" 68 | 69 | if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then 70 | rm -rf "$korebuild_path" 71 | fi 72 | 73 | { 74 | if [ ! -d "$korebuild_path" ]; then 75 | mkdir -p "$korebuild_path" 76 | local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" 77 | tmpfile="$(mktemp)" 78 | echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" 79 | if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then 80 | unzip -q -d "$korebuild_path" "$tmpfile" 81 | fi 82 | rm "$tmpfile" || true 83 | fi 84 | 85 | source "$korebuild_path/KoreBuild.sh" 86 | } || { 87 | if [ -d "$korebuild_path" ]; then 88 | echo "Cleaning up after failed installation" 89 | rm -rf "$korebuild_path" || true 90 | fi 91 | return 1 92 | } 93 | } 94 | 95 | __error() { 96 | echo -e "${RED}error: $*${RESET}" 1>&2 97 | } 98 | 99 | __warn() { 100 | echo -e "${YELLOW}warning: $*${RESET}" 101 | } 102 | 103 | __machine_has() { 104 | hash "$1" > /dev/null 2>&1 105 | return $? 106 | } 107 | 108 | __get_remote_file() { 109 | local remote_path=$1 110 | local local_path=$2 111 | local remote_path_suffix=$3 112 | 113 | if [[ "$remote_path" != 'http'* ]]; then 114 | cp "$remote_path" "$local_path" 115 | return 0 116 | fi 117 | 118 | local failed=false 119 | if __machine_has wget; then 120 | wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true 121 | else 122 | failed=true 123 | fi 124 | 125 | if [ "$failed" = true ] && __machine_has curl; then 126 | failed=false 127 | curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true 128 | fi 129 | 130 | if [ "$failed" = true ]; then 131 | __error "Download failed: $remote_path" 1>&2 132 | return 1 133 | fi 134 | } 135 | 136 | # 137 | # main 138 | # 139 | 140 | command="${1:-}" 141 | shift 142 | 143 | while [[ $# -gt 0 ]]; do 144 | case $1 in 145 | -\?|-h|--help) 146 | __usage --no-exit 147 | exit 0 148 | ;; 149 | -c|--channel|-Channel) 150 | shift 151 | channel="${1:-}" 152 | [ -z "$channel" ] && __usage 153 | ;; 154 | --config-file|-ConfigFile) 155 | shift 156 | config_file="${1:-}" 157 | [ -z "$config_file" ] && __usage 158 | if [ ! -f "$config_file" ]; then 159 | __error "Invalid value for --config-file. $config_file does not exist." 160 | exit 1 161 | fi 162 | ;; 163 | -d|--dotnet-home|-DotNetHome) 164 | shift 165 | DOTNET_HOME="${1:-}" 166 | [ -z "$DOTNET_HOME" ] && __usage 167 | ;; 168 | --path|-Path) 169 | shift 170 | repo_path="${1:-}" 171 | [ -z "$repo_path" ] && __usage 172 | ;; 173 | -s|--tools-source|-ToolsSource) 174 | shift 175 | tools_source="${1:-}" 176 | [ -z "$tools_source" ] && __usage 177 | ;; 178 | --tools-source-suffix|-ToolsSourceSuffix) 179 | shift 180 | tools_source_suffix="${1:-}" 181 | [ -z "$tools_source_suffix" ] && __usage 182 | ;; 183 | -u|--update|-Update) 184 | update=true 185 | ;; 186 | --reinstall|-[Rr]einstall) 187 | reinstall=true 188 | ;; 189 | --ci|-[Cc][Ii]) 190 | ci=true 191 | ;; 192 | --verbose|-Verbose) 193 | verbose=true 194 | ;; 195 | --) 196 | shift 197 | break 198 | ;; 199 | *) 200 | break 201 | ;; 202 | esac 203 | shift 204 | done 205 | 206 | if ! __machine_has unzip; then 207 | __error 'Missing required command: unzip' 208 | exit 1 209 | fi 210 | 211 | if ! __machine_has curl && ! __machine_has wget; then 212 | __error 'Missing required command. Either wget or curl is required.' 213 | exit 1 214 | fi 215 | 216 | [ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" 217 | if [ -f "$config_file" ]; then 218 | if __machine_has jq ; then 219 | if jq '.' "$config_file" >/dev/null ; then 220 | config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" 221 | config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" 222 | else 223 | __error "$config_file contains invalid JSON." 224 | exit 1 225 | fi 226 | elif __machine_has python ; then 227 | if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then 228 | config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" 229 | config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" 230 | else 231 | __error "$config_file contains invalid JSON." 232 | exit 1 233 | fi 234 | elif __machine_has python3 ; then 235 | if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then 236 | config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" 237 | config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" 238 | else 239 | __error "$config_file contains invalid JSON." 240 | exit 1 241 | fi 242 | else 243 | __error 'Missing required command: jq or python. Could not parse the JSON file.' 244 | exit 1 245 | fi 246 | 247 | [ ! -z "${config_channel:-}" ] && channel="$config_channel" 248 | [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" 249 | fi 250 | 251 | [ -z "$channel" ] && channel='master' 252 | [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' 253 | 254 | get_korebuild 255 | set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" 256 | invoke_korebuild_command "$command" "$@" 257 | -------------------------------------------------------------------------------- /samples/MemoryCacheConcurencySample/MemoryCacheConcurencySample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.2 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/MemoryCacheConcurencySample/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Caching.Memory; 7 | 8 | namespace MemoryCacheSample 9 | { 10 | public class Program 11 | { 12 | private const string Key = "MyKey"; 13 | private static readonly Random Random = new Random(); 14 | private static MemoryCacheEntryOptions _cacheEntryOptions; 15 | 16 | public static void Main() 17 | { 18 | _cacheEntryOptions = GetCacheEntryOptions(); 19 | 20 | IMemoryCache cache = new MemoryCache(new MemoryCacheOptions()); 21 | 22 | SetKey(cache, "0"); 23 | 24 | PeriodicallyReadKey(cache, TimeSpan.FromSeconds(1)); 25 | 26 | PeriodicallyRemoveKey(cache, TimeSpan.FromSeconds(11)); 27 | 28 | PeriodicallySetKey(cache, TimeSpan.FromSeconds(13)); 29 | 30 | Console.ReadLine(); 31 | Console.WriteLine("Shutting down"); 32 | } 33 | 34 | private static void SetKey(IMemoryCache cache, string value) 35 | { 36 | Console.WriteLine("Setting: " + value); 37 | cache.Set(Key, value, _cacheEntryOptions); 38 | } 39 | 40 | private static MemoryCacheEntryOptions GetCacheEntryOptions() 41 | { 42 | return new MemoryCacheEntryOptions() 43 | .SetAbsoluteExpiration(TimeSpan.FromSeconds(7)) 44 | .SetSlidingExpiration(TimeSpan.FromSeconds(3)) 45 | .RegisterPostEvictionCallback(AfterEvicted, state: null); 46 | } 47 | 48 | private static void AfterEvicted(object key, object value, EvictionReason reason, object state) 49 | { 50 | Console.WriteLine("Evicted. Value: " + value + ", Reason: " + reason); 51 | } 52 | 53 | private static void PeriodicallySetKey(IMemoryCache cache, TimeSpan interval) 54 | { 55 | Task.Run(async () => 56 | { 57 | while (true) 58 | { 59 | await Task.Delay(interval); 60 | 61 | SetKey(cache, "A"); 62 | } 63 | }); 64 | } 65 | 66 | private static void PeriodicallyReadKey(IMemoryCache cache, TimeSpan interval) 67 | { 68 | Task.Run(async () => 69 | { 70 | while (true) 71 | { 72 | await Task.Delay(interval); 73 | 74 | if (Random.Next(3) == 0) // 1/3 chance 75 | { 76 | // Allow values to expire due to sliding refresh. 77 | Console.WriteLine("Read skipped, random choice."); 78 | } 79 | else 80 | { 81 | Console.Write("Reading..."); 82 | if (!cache.TryGetValue(Key, out object result)) 83 | { 84 | result = cache.Set(Key, "B", _cacheEntryOptions); 85 | } 86 | Console.WriteLine("Read: " + (result ?? "(null)")); 87 | } 88 | } 89 | }); 90 | } 91 | 92 | private static void PeriodicallyRemoveKey(IMemoryCache cache, TimeSpan interval) 93 | { 94 | Task.Run(async () => 95 | { 96 | while (true) 97 | { 98 | await Task.Delay(interval); 99 | 100 | Console.WriteLine("Removing..."); 101 | cache.Remove(Key); 102 | } 103 | }); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /samples/MemoryCacheFileWatchSample/MemoryCacheFileWatchSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.2 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /samples/MemoryCacheFileWatchSample/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using Microsoft.Extensions.Caching.Memory; 7 | using Microsoft.Extensions.FileProviders; 8 | 9 | namespace MemoryCacheFileWatchSample 10 | { 11 | public class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | var cache = new MemoryCache(new MemoryCacheOptions()); 16 | var greeting = ""; 17 | var cacheKey = "cache_key"; 18 | var fileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "WatchedFiles")); 19 | 20 | do 21 | { 22 | if (!cache.TryGetValue(cacheKey, out greeting)) 23 | { 24 | using (var streamReader = new StreamReader(fileProvider.GetFileInfo("example.txt").CreateReadStream())) 25 | { 26 | greeting = streamReader.ReadToEnd(); 27 | cache.Set(cacheKey, greeting, new MemoryCacheEntryOptions() 28 | //Telling the cache to depend on the IChangeToken from watching examples.txt 29 | .AddExpirationToken(fileProvider.Watch("example.txt")) 30 | .RegisterPostEvictionCallback( 31 | (echoKey, value, reason, substate) => 32 | { 33 | Console.WriteLine($"{echoKey} : {value} was evicted due to {reason}"); 34 | })); 35 | Console.WriteLine($"{cacheKey} updated from source."); 36 | } 37 | } 38 | else 39 | { 40 | Console.WriteLine($"{cacheKey} retrieved from cache."); 41 | } 42 | 43 | Console.WriteLine(greeting); 44 | Console.WriteLine("Press any key to continue. Press the ESC key to exit"); 45 | } 46 | while (Console.ReadKey(true).Key != ConsoleKey.Escape); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /samples/MemoryCacheFileWatchSample/WatchedFiles/example.txt: -------------------------------------------------------------------------------- 1 | Hello World -------------------------------------------------------------------------------- /samples/MemoryCacheSample/MemoryCacheSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.2 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/MemoryCacheSample/MemoryCacheWeakReferenceExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.Memory 7 | { 8 | public static class MemoryCacheWeakReferenceExtensions 9 | { 10 | public static TItem GetWeak(this IMemoryCache cache, object key) where TItem : class 11 | { 12 | if (cache.TryGetValue>(key, out WeakReference reference)) 13 | { 14 | reference.TryGetTarget(out TItem value); 15 | return value; 16 | } 17 | return null; 18 | } 19 | 20 | public static TItem SetWeak(this IMemoryCache cache, object key, TItem value) where TItem : class 21 | { 22 | using (var entry = cache.CreateEntry(key)) 23 | { 24 | var reference = new WeakReference(value); 25 | entry.AddExpirationToken(new WeakToken(reference)); 26 | entry.Value = reference; 27 | } 28 | 29 | return value; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/MemoryCacheSample/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using Microsoft.Extensions.Caching.Memory; 7 | using Microsoft.Extensions.Primitives; 8 | 9 | namespace MemoryCacheSample 10 | { 11 | public class Program 12 | { 13 | public static void Main() 14 | { 15 | IMemoryCache cache = new MemoryCache(new MemoryCacheOptions()); 16 | object result; 17 | string key = "Key"; 18 | object newObject = new object(); 19 | object state = new object(); 20 | 21 | // Basic CRUD operations: 22 | 23 | // Create / Overwrite 24 | result = cache.Set(key, newObject); 25 | result = cache.Set(key, new object()); 26 | 27 | // Retrieve, null if not found 28 | result = cache.Get(key); 29 | 30 | // Retrieve 31 | bool found = cache.TryGetValue(key, out result); 32 | 33 | // Store and Get using weak references 34 | result = cache.SetWeak(key, newObject); 35 | result = cache.GetWeak(key); 36 | 37 | // Delete 38 | cache.Remove(key); 39 | 40 | // Cache entry configuration: 41 | 42 | // Stays in the cache as long as possible 43 | result = cache.Set( 44 | key, 45 | new object(), 46 | new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.NeverRemove)); 47 | 48 | // Automatically remove if not accessed in the given time 49 | result = cache.Set( 50 | key, 51 | new object(), 52 | new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(5))); 53 | 54 | // Automatically remove at a certain time 55 | result = cache.Set( 56 | key, 57 | new object(), 58 | new MemoryCacheEntryOptions().SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddDays(2))); 59 | 60 | // Automatically remove at a certain time, which is relative to UTC now 61 | result = cache.Set( 62 | key, 63 | new object(), 64 | new MemoryCacheEntryOptions().SetAbsoluteExpiration(relative: TimeSpan.FromMinutes(10))); 65 | 66 | // Automatically remove if not accessed in the given time 67 | // Automatically remove at a certain time (if it lives that long) 68 | result = cache.Set( 69 | key, 70 | new object(), 71 | new MemoryCacheEntryOptions() 72 | .SetSlidingExpiration(TimeSpan.FromMinutes(5)) 73 | .SetAbsoluteExpiration(DateTimeOffset.UtcNow.AddDays(2))); 74 | 75 | // Callback when evicted 76 | var options = new MemoryCacheEntryOptions() 77 | .RegisterPostEvictionCallback( 78 | (echoKey, value, reason, substate) => 79 | { 80 | Console.WriteLine(echoKey + ": '" + value + "' was evicted due to " + reason); 81 | }); 82 | result = cache.Set(key, new object(), options); 83 | 84 | // Remove on token expiration 85 | var cts = new CancellationTokenSource(); 86 | options = new MemoryCacheEntryOptions() 87 | .AddExpirationToken(new CancellationChangeToken(cts.Token)) 88 | .RegisterPostEvictionCallback( 89 | (echoKey, value, reason, substate) => 90 | { 91 | Console.WriteLine(echoKey + ": '" + value + "' was evicted due to " + reason); 92 | }); 93 | result = cache.Set(key, new object(), options); 94 | 95 | // Fire the token to see the registered callback being invoked 96 | cts.Cancel(); 97 | 98 | // Expire an entry if the dependent entry expires 99 | using (var entry = cache.CreateEntry("key1")) 100 | { 101 | // expire this entry if the entry with key "key2" expires. 102 | entry.Value = "value1"; 103 | cts = new CancellationTokenSource(); 104 | entry.RegisterPostEvictionCallback( 105 | (echoKey, value, reason, substate) => 106 | { 107 | Console.WriteLine(echoKey + ": '" + value + "' was evicted due to " + reason); 108 | }); 109 | 110 | cache.Set("key2", "value2", new CancellationChangeToken(cts.Token)); 111 | } 112 | 113 | // Fire the token to see the registered callback being invoked 114 | cts.Cancel(); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /samples/MemoryCacheSample/WeakToken.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace Microsoft.Extensions.Caching.Memory 8 | { 9 | public class WeakToken : IChangeToken where T : class 10 | { 11 | private WeakReference _reference; 12 | 13 | public WeakToken(WeakReference reference) 14 | { 15 | _reference = reference; 16 | } 17 | 18 | public bool ActiveChangeCallbacks 19 | { 20 | get { return false; } 21 | } 22 | 23 | public bool HasChanged 24 | { 25 | get 26 | { 27 | return !_reference.TryGetTarget(out T ignored); 28 | } 29 | } 30 | 31 | public IDisposable RegisterChangeCallback(Action callback, object state) 32 | { 33 | throw new NotSupportedException(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /samples/ProfilingSample/ProfilingSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.2 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/ProfilingSample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Caching.Memory; 5 | 6 | namespace ProfilingSample 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | // Runs several concurrent threads that access an item that periodically expires and is re-created. 13 | MemoryCache cache = new MemoryCache(new MemoryCacheOptions()); 14 | string key = "MyKey"; 15 | 16 | var options = new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMilliseconds(50)); 17 | 18 | var tasks = new List(); 19 | for (int threads = 0; threads < 100; threads++) 20 | { 21 | var task = Task.Run(() => 22 | { 23 | for (int i = 0; i < 110000; i++) 24 | { 25 | if (!cache.TryGetValue(key, out object value)) 26 | { 27 | // Fake expensive object creation. 28 | for (int j = 0; j < 1000000; j++) 29 | { 30 | } 31 | 32 | cache.Set(key, new object(), options); 33 | } 34 | } 35 | }); 36 | tasks.Add(task); 37 | } 38 | 39 | Console.WriteLine("Running"); 40 | Task.WaitAll(tasks.ToArray()); 41 | Console.WriteLine("Done"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /samples/RedisCacheSample/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Distributed; 8 | using Microsoft.Extensions.Caching.StackExchangeRedis; 9 | 10 | namespace RedisCacheSample 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | RunSampleAsync().Wait(); 17 | } 18 | 19 | /// 20 | /// This sample assumes that a redis server is running on the local machine. You can set this up by doing the following: 21 | /// Install this chocolatey package: http://chocolatey.org/packages/redis-64/ 22 | /// run "redis-server" from command prompt. 23 | /// 24 | /// 25 | public static async Task RunSampleAsync() 26 | { 27 | var key = "myKey"; 28 | var message = "Hello, World!"; 29 | var value = Encoding.UTF8.GetBytes(message); 30 | 31 | Console.WriteLine("Connecting to cache"); 32 | var cache = new RedisCache(new RedisCacheOptions 33 | { 34 | Configuration = "localhost", 35 | InstanceName = "SampleInstance" 36 | }); 37 | Console.WriteLine("Connected"); 38 | 39 | Console.WriteLine($"Setting value '{message}' in cache"); 40 | await cache.SetAsync(key, value, new DistributedCacheEntryOptions()); 41 | Console.WriteLine("Set"); 42 | 43 | Console.WriteLine("Getting value from cache"); 44 | value = await cache.GetAsync(key); 45 | if (value != null) 46 | { 47 | Console.WriteLine("Retrieved: " + Encoding.UTF8.GetString(value)); 48 | } 49 | else 50 | { 51 | Console.WriteLine("Not Found"); 52 | } 53 | 54 | Console.WriteLine("Refreshing value in cache"); 55 | await cache.RefreshAsync(key); 56 | Console.WriteLine("Refreshed"); 57 | 58 | Console.WriteLine("Removing value from cache"); 59 | await cache.RemoveAsync(key); 60 | Console.WriteLine("Removed"); 61 | 62 | Console.WriteLine("Getting value from cache again"); 63 | value = await cache.GetAsync(key); 64 | if (value != null) 65 | { 66 | Console.WriteLine("Retrieved: " + Encoding.UTF8.GetString(value)); 67 | } 68 | else 69 | { 70 | Console.WriteLine("Not Found"); 71 | } 72 | 73 | Console.ReadLine(); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /samples/RedisCacheSample/RedisCacheSample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net461;netcoreapp2.2 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /samples/SqlServerCacheConcurencySample/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Distributed; 8 | using Microsoft.Extensions.Caching.Memory; 9 | using Microsoft.Extensions.Caching.SqlServer; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.Options; 12 | 13 | namespace SqlServerCacheConcurrencySample 14 | { 15 | /// 16 | /// This sample requires setting up a Microsoft SQL Server based cache database. 17 | /// 1. Install the .NET Core sql-cache tool globally by installing the dotnet-sql-cache package. 18 | /// 2. Create a new database in the SQL Server or use an existing one. 19 | /// 3. Run the command "dotnet sql-cache create " to setup the table and indexes. 20 | /// 4. Run this sample by doing "dotnet run" 21 | /// 22 | public class Program 23 | { 24 | private const string Key = "MyKey"; 25 | private static readonly Random Random = new Random(); 26 | private static DistributedCacheEntryOptions _cacheEntryOptions; 27 | 28 | public static void Main() 29 | { 30 | var configurationBuilder = new ConfigurationBuilder(); 31 | configurationBuilder 32 | .AddJsonFile("config.json") 33 | .AddEnvironmentVariables(); 34 | var configuration = configurationBuilder.Build(); 35 | 36 | _cacheEntryOptions = new DistributedCacheEntryOptions(); 37 | _cacheEntryOptions.SetSlidingExpiration(TimeSpan.FromSeconds(10)); 38 | 39 | var cache = new SqlServerCache(new SqlServerCacheOptions() 40 | { 41 | ConnectionString = configuration["ConnectionString"], 42 | SchemaName = configuration["SchemaName"], 43 | TableName = configuration["TableName"] 44 | }); 45 | 46 | SetKey(cache, "0"); 47 | 48 | PeriodicallyReadKey(cache, TimeSpan.FromSeconds(1)); 49 | 50 | PeriodciallyRemoveKey(cache, TimeSpan.FromSeconds(11)); 51 | 52 | PeriodciallySetKey(cache, TimeSpan.FromSeconds(13)); 53 | 54 | Console.ReadLine(); 55 | Console.WriteLine("Shutting down"); 56 | } 57 | 58 | private static void SetKey(IDistributedCache cache, string value) 59 | { 60 | Console.WriteLine("Setting: " + value); 61 | cache.Set(Key, Encoding.UTF8.GetBytes(value), _cacheEntryOptions); 62 | } 63 | 64 | private static void PeriodciallySetKey(IDistributedCache cache, TimeSpan interval) 65 | { 66 | Task.Run(async () => 67 | { 68 | while (true) 69 | { 70 | await Task.Delay(interval); 71 | 72 | SetKey(cache, "A"); 73 | } 74 | }); 75 | } 76 | 77 | private static void PeriodicallyReadKey(IDistributedCache cache, TimeSpan interval) 78 | { 79 | Task.Run(async () => 80 | { 81 | while (true) 82 | { 83 | await Task.Delay(interval); 84 | 85 | if (Random.Next(3) == 0) // 1/3 chance 86 | { 87 | // Allow values to expire due to sliding refresh. 88 | Console.WriteLine("Read skipped, random choice."); 89 | } 90 | else 91 | { 92 | Console.Write("Reading..."); 93 | object result = cache.Get(Key); 94 | if (result != null) 95 | { 96 | cache.Set(Key, Encoding.UTF8.GetBytes("B"), _cacheEntryOptions); 97 | } 98 | Console.WriteLine("Read: " + (result ?? "(null)")); 99 | } 100 | } 101 | }); 102 | } 103 | 104 | private static void PeriodciallyRemoveKey(IDistributedCache cache, TimeSpan interval) 105 | { 106 | Task.Run(async () => 107 | { 108 | while (true) 109 | { 110 | await Task.Delay(interval); 111 | 112 | Console.WriteLine("Removing..."); 113 | cache.Remove(Key); 114 | } 115 | }); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /samples/SqlServerCacheConcurencySample/SqlServerCacheConcurencySample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.2 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /samples/SqlServerCacheConcurencySample/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionString": "Server=localhost;Database=CacheConcurrencySampleDb;Trusted_Connection=True;", 3 | "SchemaName": "dbo", 4 | "TableName": "CacheConcurrencySample" 5 | } -------------------------------------------------------------------------------- /samples/SqlServerCacheSample/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Distributed; 8 | using Microsoft.Extensions.Caching.SqlServer; 9 | using Microsoft.Extensions.Configuration; 10 | 11 | namespace SqlServerCacheSample 12 | { 13 | /// 14 | /// This sample requires setting up a Microsoft SQL Server based cache database. 15 | /// 1. Install the .NET Core sql-cache tool globally by installing the dotnet-sql-cache package. 16 | /// 2. Create a new database in the SQL Server or use an existing one. 17 | /// 3. Run the command "dotnet sql-cache create " to setup the table and indexes. 18 | /// 4. Run this sample by doing "dotnet run" 19 | /// 20 | public class Program 21 | { 22 | public static void Main() 23 | { 24 | RunSampleAsync().Wait(); 25 | } 26 | 27 | public static async Task RunSampleAsync() 28 | { 29 | var configurationBuilder = new ConfigurationBuilder(); 30 | var configuration = configurationBuilder 31 | .AddJsonFile("config.json") 32 | .AddEnvironmentVariables() 33 | .Build(); 34 | 35 | var key = Guid.NewGuid().ToString(); 36 | var message = "Hello, World!"; 37 | var value = Encoding.UTF8.GetBytes(message); 38 | 39 | Console.WriteLine("Connecting to cache"); 40 | var cache = new SqlServerCache(new SqlServerCacheOptions() 41 | { 42 | ConnectionString = configuration["ConnectionString"], 43 | SchemaName = configuration["SchemaName"], 44 | TableName = configuration["TableName"] 45 | }); 46 | 47 | Console.WriteLine("Connected"); 48 | 49 | Console.WriteLine("Cache item key: {0}", key); 50 | Console.WriteLine($"Setting value '{message}' in cache"); 51 | await cache.SetAsync( 52 | key, 53 | value, 54 | new DistributedCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromSeconds(10))); 55 | Console.WriteLine("Set"); 56 | 57 | Console.WriteLine("Getting value from cache"); 58 | value = await cache.GetAsync(key); 59 | if (value != null) 60 | { 61 | Console.WriteLine("Retrieved: " + Encoding.UTF8.GetString(value, 0, value.Length)); 62 | } 63 | else 64 | { 65 | Console.WriteLine("Not Found"); 66 | } 67 | 68 | Console.WriteLine("Refreshing value in cache"); 69 | await cache.RefreshAsync(key); 70 | Console.WriteLine("Refreshed"); 71 | 72 | Console.WriteLine("Removing value from cache"); 73 | await cache.RemoveAsync(key); 74 | Console.WriteLine("Removed"); 75 | 76 | Console.WriteLine("Getting value from cache again"); 77 | value = await cache.GetAsync(key); 78 | if (value != null) 79 | { 80 | Console.WriteLine("Retrieved: " + Encoding.UTF8.GetString(value, 0, value.Length)); 81 | } 82 | else 83 | { 84 | Console.WriteLine("Not Found"); 85 | } 86 | 87 | Console.ReadLine(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /samples/SqlServerCacheSample/SqlServerCacheSample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net461;netcoreapp2.2 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /samples/SqlServerCacheSample/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionString": "Server=localhost;Database=CacheSampleDb;Trusted_Connection=True;", 3 | "SchemaName": "dbo", 4 | "TableName": "CacheSample" 5 | } -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/CacheEntryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace Microsoft.Extensions.Caching.Memory 8 | { 9 | public static class CacheEntryExtensions 10 | { 11 | /// 12 | /// Sets the priority for keeping the cache entry in the cache during a memory pressure tokened cleanup. 13 | /// 14 | /// 15 | /// 16 | public static ICacheEntry SetPriority( 17 | this ICacheEntry entry, 18 | CacheItemPriority priority) 19 | { 20 | entry.Priority = priority; 21 | return entry; 22 | } 23 | 24 | /// 25 | /// Expire the cache entry if the given expires. 26 | /// 27 | /// The . 28 | /// The that causes the cache entry to expire. 29 | public static ICacheEntry AddExpirationToken( 30 | this ICacheEntry entry, 31 | IChangeToken expirationToken) 32 | { 33 | if (expirationToken == null) 34 | { 35 | throw new ArgumentNullException(nameof(expirationToken)); 36 | } 37 | 38 | entry.ExpirationTokens.Add(expirationToken); 39 | return entry; 40 | } 41 | 42 | /// 43 | /// Sets an absolute expiration time, relative to now. 44 | /// 45 | /// 46 | /// 47 | public static ICacheEntry SetAbsoluteExpiration( 48 | this ICacheEntry entry, 49 | TimeSpan relative) 50 | { 51 | entry.AbsoluteExpirationRelativeToNow = relative; 52 | return entry; 53 | } 54 | 55 | /// 56 | /// Sets an absolute expiration date for the cache entry. 57 | /// 58 | /// 59 | /// 60 | public static ICacheEntry SetAbsoluteExpiration( 61 | this ICacheEntry entry, 62 | DateTimeOffset absolute) 63 | { 64 | entry.AbsoluteExpiration = absolute; 65 | return entry; 66 | } 67 | 68 | /// 69 | /// Sets how long the cache entry can be inactive (e.g. not accessed) before it will be removed. 70 | /// This will not extend the entry lifetime beyond the absolute expiration (if set). 71 | /// 72 | /// 73 | /// 74 | public static ICacheEntry SetSlidingExpiration( 75 | this ICacheEntry entry, 76 | TimeSpan offset) 77 | { 78 | entry.SlidingExpiration = offset; 79 | return entry; 80 | } 81 | 82 | /// 83 | /// The given callback will be fired after the cache entry is evicted from the cache. 84 | /// 85 | /// 86 | /// 87 | public static ICacheEntry RegisterPostEvictionCallback( 88 | this ICacheEntry entry, 89 | PostEvictionDelegate callback) 90 | { 91 | if (callback == null) 92 | { 93 | throw new ArgumentNullException(nameof(callback)); 94 | } 95 | 96 | return entry.RegisterPostEvictionCallback(callback, state: null); 97 | } 98 | 99 | /// 100 | /// The given callback will be fired after the cache entry is evicted from the cache. 101 | /// 102 | /// 103 | /// 104 | /// 105 | public static ICacheEntry RegisterPostEvictionCallback( 106 | this ICacheEntry entry, 107 | PostEvictionDelegate callback, 108 | object state) 109 | { 110 | if (callback == null) 111 | { 112 | throw new ArgumentNullException(nameof(callback)); 113 | } 114 | 115 | entry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration() 116 | { 117 | EvictionCallback = callback, 118 | State = state 119 | }); 120 | return entry; 121 | } 122 | 123 | /// 124 | /// Sets the value of the cache entry. 125 | /// 126 | /// 127 | /// 128 | public static ICacheEntry SetValue( 129 | this ICacheEntry entry, 130 | object value) 131 | { 132 | entry.Value = value; 133 | return entry; 134 | } 135 | 136 | /// 137 | /// Sets the size of the cache entry value. 138 | /// 139 | /// 140 | /// 141 | public static ICacheEntry SetSize( 142 | this ICacheEntry entry, 143 | long size) 144 | { 145 | if (size < 0) 146 | { 147 | throw new ArgumentOutOfRangeException(nameof(size), size, $"{nameof(size)} must be non-negative."); 148 | } 149 | 150 | entry.Size = size; 151 | return entry; 152 | } 153 | 154 | /// 155 | /// Applies the values of an existing to the entry. 156 | /// 157 | /// 158 | /// 159 | public static ICacheEntry SetOptions(this ICacheEntry entry, MemoryCacheEntryOptions options) 160 | { 161 | if (options == null) 162 | { 163 | throw new ArgumentNullException(nameof(options)); 164 | } 165 | 166 | entry.AbsoluteExpiration = options.AbsoluteExpiration; 167 | entry.AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow; 168 | entry.SlidingExpiration = options.SlidingExpiration; 169 | entry.Priority = options.Priority; 170 | entry.Size = options.Size; 171 | 172 | foreach (var expirationToken in options.ExpirationTokens) 173 | { 174 | entry.AddExpirationToken(expirationToken); 175 | } 176 | 177 | foreach (var postEvictionCallback in options.PostEvictionCallbacks) 178 | { 179 | entry.RegisterPostEvictionCallback(postEvictionCallback.EvictionCallback, postEvictionCallback.State); 180 | } 181 | 182 | return entry; 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/CacheItemPriority.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Extensions.Caching.Memory 5 | { 6 | // TODO: Granularity? 7 | /// 8 | /// Specifies how items are prioritized for preservation during a memory pressure triggered cleanup. 9 | /// 10 | public enum CacheItemPriority 11 | { 12 | Low, 13 | Normal, 14 | High, 15 | NeverRemove, 16 | } 17 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/DistributedCacheEntryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.Distributed 7 | { 8 | public static class DistributedCacheEntryExtensions 9 | { 10 | /// 11 | /// Sets an absolute expiration time, relative to now. 12 | /// 13 | /// 14 | /// 15 | public static DistributedCacheEntryOptions SetAbsoluteExpiration( 16 | this DistributedCacheEntryOptions options, 17 | TimeSpan relative) 18 | { 19 | options.AbsoluteExpirationRelativeToNow = relative; 20 | return options; 21 | } 22 | 23 | /// 24 | /// Sets an absolute expiration date for the cache entry. 25 | /// 26 | /// 27 | /// 28 | public static DistributedCacheEntryOptions SetAbsoluteExpiration( 29 | this DistributedCacheEntryOptions options, 30 | DateTimeOffset absolute) 31 | { 32 | options.AbsoluteExpiration = absolute; 33 | return options; 34 | } 35 | 36 | /// 37 | /// Sets how long the cache entry can be inactive (e.g. not accessed) before it will be removed. 38 | /// This will not extend the entry lifetime beyond the absolute expiration (if set). 39 | /// 40 | /// 41 | /// 42 | public static DistributedCacheEntryOptions SetSlidingExpiration( 43 | this DistributedCacheEntryOptions options, 44 | TimeSpan offset) 45 | { 46 | options.SlidingExpiration = offset; 47 | return options; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/DistributedCacheEntryOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.Distributed 7 | { 8 | /// 9 | /// Provides the cache options for an entry in . 10 | /// 11 | public class DistributedCacheEntryOptions 12 | { 13 | private DateTimeOffset? _absoluteExpiration; 14 | private TimeSpan? _absoluteExpirationRelativeToNow; 15 | private TimeSpan? _slidingExpiration; 16 | 17 | /// 18 | /// Gets or sets an absolute expiration date for the cache entry. 19 | /// 20 | public DateTimeOffset? AbsoluteExpiration 21 | { 22 | get 23 | { 24 | return _absoluteExpiration; 25 | } 26 | set 27 | { 28 | _absoluteExpiration = value; 29 | } 30 | } 31 | 32 | /// 33 | /// Gets or sets an absolute expiration time, relative to now. 34 | /// 35 | public TimeSpan? AbsoluteExpirationRelativeToNow 36 | { 37 | get 38 | { 39 | return _absoluteExpirationRelativeToNow; 40 | } 41 | set 42 | { 43 | if (value <= TimeSpan.Zero) 44 | { 45 | throw new ArgumentOutOfRangeException( 46 | nameof(AbsoluteExpirationRelativeToNow), 47 | value, 48 | "The relative expiration value must be positive."); 49 | } 50 | 51 | _absoluteExpirationRelativeToNow = value; 52 | } 53 | } 54 | 55 | /// 56 | /// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. 57 | /// This will not extend the entry lifetime beyond the absolute expiration (if set). 58 | /// 59 | public TimeSpan? SlidingExpiration 60 | { 61 | get 62 | { 63 | return _slidingExpiration; 64 | } 65 | set 66 | { 67 | if (value <= TimeSpan.Zero) 68 | { 69 | throw new ArgumentOutOfRangeException( 70 | nameof(SlidingExpiration), 71 | value, 72 | "The sliding expiration value must be positive."); 73 | } 74 | _slidingExpiration = value; 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/DistributedCacheExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace Microsoft.Extensions.Caching.Distributed 10 | { 11 | /// 12 | /// Extension methods for setting data in an . 13 | /// 14 | public static class DistributedCacheExtensions 15 | { 16 | /// 17 | /// Sets a sequence of bytes in the specified cache with the specified key. 18 | /// 19 | /// The cache in which to store the data. 20 | /// The key to store the data in. 21 | /// The data to store in the cache. 22 | /// Thrown when or is null. 23 | public static void Set(this IDistributedCache cache, string key, byte[] value) 24 | { 25 | if (key == null) 26 | { 27 | throw new ArgumentNullException(nameof(key)); 28 | } 29 | if (value == null) 30 | { 31 | throw new ArgumentNullException(nameof(value)); 32 | } 33 | 34 | cache.Set(key, value, new DistributedCacheEntryOptions()); 35 | } 36 | 37 | /// 38 | /// Asynchronously sets a sequence of bytes in the specified cache with the specified key. 39 | /// 40 | /// The cache in which to store the data. 41 | /// The key to store the data in. 42 | /// The data to store in the cache. 43 | /// Optional. A to cancel the operation. 44 | /// A task that represents the asynchronous set operation. 45 | /// Thrown when or is null. 46 | public static Task SetAsync(this IDistributedCache cache, string key, byte[] value, CancellationToken token = default(CancellationToken)) 47 | { 48 | if (key == null) 49 | { 50 | throw new ArgumentNullException(nameof(key)); 51 | } 52 | if (value == null) 53 | { 54 | throw new ArgumentNullException(nameof(value)); 55 | } 56 | 57 | return cache.SetAsync(key, value, new DistributedCacheEntryOptions(), token); 58 | } 59 | 60 | /// 61 | /// Sets a string in the specified cache with the specified key. 62 | /// 63 | /// The cache in which to store the data. 64 | /// The key to store the data in. 65 | /// The data to store in the cache. 66 | /// Thrown when or is null. 67 | public static void SetString(this IDistributedCache cache, string key, string value) 68 | { 69 | cache.SetString(key, value, new DistributedCacheEntryOptions()); 70 | } 71 | 72 | /// 73 | /// Sets a string in the specified cache with the specified key. 74 | /// 75 | /// The cache in which to store the data. 76 | /// The key to store the data in. 77 | /// The data to store in the cache. 78 | /// The cache options for the entry. 79 | /// Thrown when or is null. 80 | public static void SetString(this IDistributedCache cache, string key, string value, DistributedCacheEntryOptions options) 81 | { 82 | if (key == null) 83 | { 84 | throw new ArgumentNullException(nameof(key)); 85 | } 86 | if (value == null) 87 | { 88 | throw new ArgumentNullException(nameof(value)); 89 | } 90 | cache.Set(key, Encoding.UTF8.GetBytes(value), options); 91 | } 92 | 93 | /// 94 | /// Asynchronously sets a string in the specified cache with the specified key. 95 | /// 96 | /// The cache in which to store the data. 97 | /// The key to store the data in. 98 | /// The data to store in the cache. 99 | /// Optional. A to cancel the operation. 100 | /// A task that represents the asynchronous set operation. 101 | /// Thrown when or is null. 102 | public static Task SetStringAsync(this IDistributedCache cache, string key, string value, CancellationToken token = default(CancellationToken)) 103 | { 104 | return cache.SetStringAsync(key, value, new DistributedCacheEntryOptions(), token); 105 | } 106 | 107 | /// 108 | /// Asynchronously sets a string in the specified cache with the specified key. 109 | /// 110 | /// The cache in which to store the data. 111 | /// The key to store the data in. 112 | /// The data to store in the cache. 113 | /// The cache options for the entry. 114 | /// Optional. A to cancel the operation. 115 | /// A task that represents the asynchronous set operation. 116 | /// Thrown when or is null. 117 | public static Task SetStringAsync(this IDistributedCache cache, string key, string value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) 118 | { 119 | if (key == null) 120 | { 121 | throw new ArgumentNullException(nameof(key)); 122 | } 123 | if (value == null) 124 | { 125 | throw new ArgumentNullException(nameof(value)); 126 | } 127 | return cache.SetAsync(key, Encoding.UTF8.GetBytes(value), options, token); 128 | } 129 | 130 | /// 131 | /// Gets a string from the specified cache with the specified key. 132 | /// 133 | /// The cache in which to store the data. 134 | /// The key to get the stored data for. 135 | /// The string value from the stored cache key. 136 | public static string GetString(this IDistributedCache cache, string key) 137 | { 138 | var data = cache.Get(key); 139 | if (data == null) 140 | { 141 | return null; 142 | } 143 | return Encoding.UTF8.GetString(data, 0, data.Length); 144 | } 145 | 146 | /// 147 | /// Asynchronously gets a string from the specified cache with the specified key. 148 | /// 149 | /// The cache in which to store the data. 150 | /// The key to get the stored data for. 151 | /// Optional. A to cancel the operation. 152 | /// A task that gets the string value from the stored cache key. 153 | public static async Task GetStringAsync(this IDistributedCache cache, string key, CancellationToken token = default(CancellationToken)) 154 | { 155 | var data = await cache.GetAsync(key, token); 156 | if (data == null) 157 | { 158 | return null; 159 | } 160 | return Encoding.UTF8.GetString(data, 0, data.Length); 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/EvictionReason.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Extensions.Caching.Memory 5 | { 6 | public enum EvictionReason 7 | { 8 | None, 9 | 10 | /// 11 | /// Manually 12 | /// 13 | Removed, 14 | 15 | /// 16 | /// Overwritten 17 | /// 18 | Replaced, 19 | 20 | /// 21 | /// Timed out 22 | /// 23 | Expired, 24 | 25 | /// 26 | /// Event 27 | /// 28 | TokenExpired, 29 | 30 | /// 31 | /// Overflow 32 | /// 33 | Capacity, 34 | } 35 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/ICacheEntry.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Microsoft.Extensions.Primitives; 7 | 8 | namespace Microsoft.Extensions.Caching.Memory 9 | { 10 | /// 11 | /// Represents an entry in the implementation. 12 | /// 13 | public interface ICacheEntry : IDisposable 14 | { 15 | /// 16 | /// Gets the key of the cache entry. 17 | /// 18 | object Key { get; } 19 | 20 | /// 21 | /// Gets or set the value of the cache entry. 22 | /// 23 | object Value { get; set; } 24 | 25 | /// 26 | /// Gets or sets an absolute expiration date for the cache entry. 27 | /// 28 | DateTimeOffset? AbsoluteExpiration { get; set; } 29 | 30 | /// 31 | /// Gets or sets an absolute expiration time, relative to now. 32 | /// 33 | TimeSpan? AbsoluteExpirationRelativeToNow { get; set; } 34 | 35 | /// 36 | /// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. 37 | /// This will not extend the entry lifetime beyond the absolute expiration (if set). 38 | /// 39 | TimeSpan? SlidingExpiration { get; set; } 40 | 41 | /// 42 | /// Gets the instances which cause the cache entry to expire. 43 | /// 44 | IList ExpirationTokens { get; } 45 | 46 | /// 47 | /// Gets or sets the callbacks will be fired after the cache entry is evicted from the cache. 48 | /// 49 | IList PostEvictionCallbacks { get; } 50 | 51 | /// 52 | /// Gets or sets the priority for keeping the cache entry in the cache during a 53 | /// cleanup. The default is . 54 | /// 55 | CacheItemPriority Priority { get; set; } 56 | 57 | /// 58 | /// Gets or set the size of the cache entry value. 59 | /// 60 | long? Size { get; set; } 61 | } 62 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/IDistributedCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace Microsoft.Extensions.Caching.Distributed 8 | { 9 | /// 10 | /// Represents a distributed cache of serialized values. 11 | /// 12 | public interface IDistributedCache 13 | { 14 | /// 15 | /// Gets a value with the given key. 16 | /// 17 | /// A string identifying the requested value. 18 | /// The located value or null. 19 | byte[] Get(string key); 20 | 21 | /// 22 | /// Gets a value with the given key. 23 | /// 24 | /// A string identifying the requested value. 25 | /// Optional. The used to propagate notifications that the operation should be canceled. 26 | /// The that represents the asynchronous operation, containing the located value or null. 27 | Task GetAsync(string key, CancellationToken token = default(CancellationToken)); 28 | 29 | /// 30 | /// Sets a value with the given key. 31 | /// 32 | /// A string identifying the requested value. 33 | /// The value to set in the cache. 34 | /// The cache options for the value. 35 | void Set(string key, byte[] value, DistributedCacheEntryOptions options); 36 | 37 | /// 38 | /// Sets the value with the given key. 39 | /// 40 | /// A string identifying the requested value. 41 | /// The value to set in the cache. 42 | /// The cache options for the value. 43 | /// Optional. The used to propagate notifications that the operation should be canceled. 44 | /// The that represents the asynchronous operation. 45 | Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)); 46 | 47 | /// 48 | /// Refreshes a value in the cache based on its key, resetting its sliding expiration timeout (if any). 49 | /// 50 | /// A string identifying the requested calue. 51 | void Refresh(string key); 52 | 53 | /// 54 | /// Refreshes a value in the cache based on its key, resetting its sliding expiration timeout (if any). 55 | /// 56 | /// A string identifying the requested value. 57 | /// Optional. The used to propagate notifications that the operation should be canceled. 58 | /// The that represents the asynchronous operation. 59 | Task RefreshAsync(string key, CancellationToken token = default(CancellationToken)); 60 | 61 | /// 62 | /// Removes the value with the given key. 63 | /// 64 | /// A string identifying the requested value. 65 | void Remove(string key); 66 | 67 | /// 68 | /// Removes the value with the given key. 69 | /// 70 | /// A string identifying the requested value. 71 | /// Optional. The used to propagate notifications that the operation should be canceled. 72 | /// The that represents the asynchronous operation. 73 | Task RemoveAsync(string key, CancellationToken token = default(CancellationToken)); 74 | } 75 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/IMemoryCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.Memory 7 | { 8 | /// 9 | /// Represents a local in-memory cache whose values are not serialized. 10 | /// 11 | public interface IMemoryCache : IDisposable 12 | { 13 | /// 14 | /// Gets the item associated with this key if present. 15 | /// 16 | /// An object identifying the requested entry. 17 | /// The located value or null. 18 | /// True if the key was found. 19 | bool TryGetValue(object key, out object value); 20 | 21 | /// 22 | /// Create or overwrite an entry in the cache. 23 | /// 24 | /// An object identifying the entry. 25 | /// The newly created instance. 26 | ICacheEntry CreateEntry(object key); 27 | 28 | /// 29 | /// Removes the object associated with the given key. 30 | /// 31 | /// An object identifying the entry. 32 | void Remove(object key); 33 | } 34 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/Internal/ISystemClock.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Internal 7 | { 8 | /// 9 | /// Abstracts the system clock to facilitate testing. 10 | /// 11 | public interface ISystemClock 12 | { 13 | /// 14 | /// Retrieves the current system time in UTC. 15 | /// 16 | DateTimeOffset UtcNow { get; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/Internal/SystemClock.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Internal 7 | { 8 | /// 9 | /// Provides access to the normal system clock. 10 | /// 11 | public class SystemClock : ISystemClock 12 | { 13 | /// 14 | /// Retrieves the current system time in UTC. 15 | /// 16 | public DateTimeOffset UtcNow 17 | { 18 | get 19 | { 20 | return DateTimeOffset.UtcNow; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheEntryExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace Microsoft.Extensions.Caching.Memory 8 | { 9 | public static class MemoryCacheEntryExtensions 10 | { 11 | /// 12 | /// Sets the priority for keeping the cache entry in the cache during a memory pressure tokened cleanup. 13 | /// 14 | /// 15 | /// 16 | public static MemoryCacheEntryOptions SetPriority( 17 | this MemoryCacheEntryOptions options, 18 | CacheItemPriority priority) 19 | { 20 | options.Priority = priority; 21 | return options; 22 | } 23 | 24 | /// 25 | /// Sets the size of the cache entry value. 26 | /// 27 | /// 28 | /// 29 | public static MemoryCacheEntryOptions SetSize( 30 | this MemoryCacheEntryOptions options, 31 | long size) 32 | { 33 | if (size < 0) 34 | { 35 | throw new ArgumentOutOfRangeException(nameof(size), size, $"{nameof(size)} must be non-negative."); 36 | } 37 | 38 | options.Size = size; 39 | return options; 40 | } 41 | 42 | /// 43 | /// Expire the cache entry if the given expires. 44 | /// 45 | /// The . 46 | /// The that causes the cache entry to expire. 47 | public static MemoryCacheEntryOptions AddExpirationToken( 48 | this MemoryCacheEntryOptions options, 49 | IChangeToken expirationToken) 50 | { 51 | if (expirationToken == null) 52 | { 53 | throw new ArgumentNullException(nameof(expirationToken)); 54 | } 55 | 56 | options.ExpirationTokens.Add(expirationToken); 57 | return options; 58 | } 59 | 60 | /// 61 | /// Sets an absolute expiration time, relative to now. 62 | /// 63 | /// 64 | /// 65 | public static MemoryCacheEntryOptions SetAbsoluteExpiration( 66 | this MemoryCacheEntryOptions options, 67 | TimeSpan relative) 68 | { 69 | options.AbsoluteExpirationRelativeToNow = relative; 70 | return options; 71 | } 72 | 73 | /// 74 | /// Sets an absolute expiration date for the cache entry. 75 | /// 76 | /// 77 | /// 78 | public static MemoryCacheEntryOptions SetAbsoluteExpiration( 79 | this MemoryCacheEntryOptions options, 80 | DateTimeOffset absolute) 81 | { 82 | options.AbsoluteExpiration = absolute; 83 | return options; 84 | } 85 | 86 | /// 87 | /// Sets how long the cache entry can be inactive (e.g. not accessed) before it will be removed. 88 | /// This will not extend the entry lifetime beyond the absolute expiration (if set). 89 | /// 90 | /// 91 | /// 92 | public static MemoryCacheEntryOptions SetSlidingExpiration( 93 | this MemoryCacheEntryOptions options, 94 | TimeSpan offset) 95 | { 96 | options.SlidingExpiration = offset; 97 | return options; 98 | } 99 | 100 | /// 101 | /// The given callback will be fired after the cache entry is evicted from the cache. 102 | /// 103 | /// 104 | /// 105 | public static MemoryCacheEntryOptions RegisterPostEvictionCallback( 106 | this MemoryCacheEntryOptions options, 107 | PostEvictionDelegate callback) 108 | { 109 | if (callback == null) 110 | { 111 | throw new ArgumentNullException(nameof(callback)); 112 | } 113 | 114 | return options.RegisterPostEvictionCallback(callback, state: null); 115 | } 116 | 117 | /// 118 | /// The given callback will be fired after the cache entry is evicted from the cache. 119 | /// 120 | /// 121 | /// 122 | /// 123 | public static MemoryCacheEntryOptions RegisterPostEvictionCallback( 124 | this MemoryCacheEntryOptions options, 125 | PostEvictionDelegate callback, 126 | object state) 127 | { 128 | if (callback == null) 129 | { 130 | throw new ArgumentNullException(nameof(callback)); 131 | } 132 | 133 | options.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration() 134 | { 135 | EvictionCallback = callback, 136 | State = state 137 | }); 138 | return options; 139 | } 140 | } 141 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheEntryOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Microsoft.Extensions.Primitives; 7 | 8 | namespace Microsoft.Extensions.Caching.Memory 9 | { 10 | /// 11 | /// Represents the cache options applied to an entry of the instance. 12 | /// 13 | public class MemoryCacheEntryOptions 14 | { 15 | private DateTimeOffset? _absoluteExpiration; 16 | private TimeSpan? _absoluteExpirationRelativeToNow; 17 | private TimeSpan? _slidingExpiration; 18 | private long? _size; 19 | 20 | /// 21 | /// Gets or sets an absolute expiration date for the cache entry. 22 | /// 23 | public DateTimeOffset? AbsoluteExpiration 24 | { 25 | get 26 | { 27 | return _absoluteExpiration; 28 | } 29 | set 30 | { 31 | _absoluteExpiration = value; 32 | } 33 | } 34 | 35 | /// 36 | /// Gets or sets an absolute expiration time, relative to now. 37 | /// 38 | public TimeSpan? AbsoluteExpirationRelativeToNow 39 | { 40 | get 41 | { 42 | return _absoluteExpirationRelativeToNow; 43 | } 44 | set 45 | { 46 | if (value <= TimeSpan.Zero) 47 | { 48 | throw new ArgumentOutOfRangeException( 49 | nameof(AbsoluteExpirationRelativeToNow), 50 | value, 51 | "The relative expiration value must be positive."); 52 | } 53 | 54 | _absoluteExpirationRelativeToNow = value; 55 | } 56 | } 57 | 58 | /// 59 | /// Gets or sets how long a cache entry can be inactive (e.g. not accessed) before it will be removed. 60 | /// This will not extend the entry lifetime beyond the absolute expiration (if set). 61 | /// 62 | public TimeSpan? SlidingExpiration 63 | { 64 | get 65 | { 66 | return _slidingExpiration; 67 | } 68 | set 69 | { 70 | if (value <= TimeSpan.Zero) 71 | { 72 | throw new ArgumentOutOfRangeException( 73 | nameof(SlidingExpiration), 74 | value, 75 | "The sliding expiration value must be positive."); 76 | } 77 | _slidingExpiration = value; 78 | } 79 | } 80 | 81 | /// 82 | /// Gets the instances which cause the cache entry to expire. 83 | /// 84 | public IList ExpirationTokens { get; } = new List(); 85 | 86 | /// 87 | /// Gets or sets the callbacks will be fired after the cache entry is evicted from the cache. 88 | /// 89 | public IList PostEvictionCallbacks { get; } 90 | = new List(); 91 | 92 | /// 93 | /// Gets or sets the priority for keeping the cache entry in the cache during a 94 | /// memory pressure triggered cleanup. The default is . 95 | /// 96 | public CacheItemPriority Priority { get; set; } = CacheItemPriority.Normal; 97 | 98 | /// 99 | /// Gets or sets the size of the cache entry value. 100 | /// 101 | public long? Size 102 | { 103 | get => _size; 104 | set 105 | { 106 | if (value < 0) 107 | { 108 | throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(value)} must be non-negative."); 109 | } 110 | 111 | _size = value; 112 | } 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/MemoryCacheExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Primitives; 7 | 8 | namespace Microsoft.Extensions.Caching.Memory 9 | { 10 | public static class CacheExtensions 11 | { 12 | public static object Get(this IMemoryCache cache, object key) 13 | { 14 | cache.TryGetValue(key, out object value); 15 | return value; 16 | } 17 | 18 | public static TItem Get(this IMemoryCache cache, object key) 19 | { 20 | return (TItem)(cache.Get(key) ?? default(TItem)); 21 | } 22 | 23 | public static bool TryGetValue(this IMemoryCache cache, object key, out TItem value) 24 | { 25 | if (cache.TryGetValue(key, out object result)) 26 | { 27 | if (result is TItem item) 28 | { 29 | value = item; 30 | return true; 31 | } 32 | } 33 | 34 | value = default; 35 | return false; 36 | } 37 | 38 | public static TItem Set(this IMemoryCache cache, object key, TItem value) 39 | { 40 | var entry = cache.CreateEntry(key); 41 | entry.Value = value; 42 | entry.Dispose(); 43 | 44 | return value; 45 | } 46 | 47 | public static TItem Set(this IMemoryCache cache, object key, TItem value, DateTimeOffset absoluteExpiration) 48 | { 49 | var entry = cache.CreateEntry(key); 50 | entry.AbsoluteExpiration = absoluteExpiration; 51 | entry.Value = value; 52 | entry.Dispose(); 53 | 54 | return value; 55 | } 56 | 57 | public static TItem Set(this IMemoryCache cache, object key, TItem value, TimeSpan absoluteExpirationRelativeToNow) 58 | { 59 | var entry = cache.CreateEntry(key); 60 | entry.AbsoluteExpirationRelativeToNow = absoluteExpirationRelativeToNow; 61 | entry.Value = value; 62 | entry.Dispose(); 63 | 64 | return value; 65 | } 66 | 67 | public static TItem Set(this IMemoryCache cache, object key, TItem value, IChangeToken expirationToken) 68 | { 69 | var entry = cache.CreateEntry(key); 70 | entry.AddExpirationToken(expirationToken); 71 | entry.Value = value; 72 | entry.Dispose(); 73 | 74 | return value; 75 | } 76 | 77 | public static TItem Set(this IMemoryCache cache, object key, TItem value, MemoryCacheEntryOptions options) 78 | { 79 | using (var entry = cache.CreateEntry(key)) 80 | { 81 | if (options != null) 82 | { 83 | entry.SetOptions(options); 84 | } 85 | 86 | entry.Value = value; 87 | } 88 | 89 | return value; 90 | } 91 | 92 | public static TItem GetOrCreate(this IMemoryCache cache, object key, Func factory) 93 | { 94 | if (!cache.TryGetValue(key, out object result)) 95 | { 96 | var entry = cache.CreateEntry(key); 97 | result = factory(entry); 98 | entry.SetValue(result); 99 | // need to manually call dispose instead of having a using 100 | // in case the factory passed in throws, in which case we 101 | // do not want to add the entry to the cache 102 | entry.Dispose(); 103 | } 104 | 105 | return (TItem)result; 106 | } 107 | 108 | public static async Task GetOrCreateAsync(this IMemoryCache cache, object key, Func> factory) 109 | { 110 | if (!cache.TryGetValue(key, out object result)) 111 | { 112 | var entry = cache.CreateEntry(key); 113 | result = await factory(entry); 114 | entry.SetValue(result); 115 | // need to manually call dispose instead of having a using 116 | // in case the factory passed in throws, in which case we 117 | // do not want to add the entry to the cache 118 | entry.Dispose(); 119 | } 120 | 121 | return (TItem)result; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/Microsoft.Extensions.Caching.Abstractions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Caching abstractions for in-memory cache and distributed cache. 5 | Commonly used types: 6 | Microsoft.Extensions.Caching.Distributed.IDistributedCache 7 | Microsoft.Extensions.Caching.Memory.IMemoryCache 8 | netstandard2.0 9 | $(NoWarn);CS1591 10 | true 11 | cache;memorycache;distributedcache 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/PostEvictionCallbackRegistration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Extensions.Caching.Memory 5 | { 6 | public class PostEvictionCallbackRegistration 7 | { 8 | public PostEvictionDelegate EvictionCallback { get; set; } 9 | 10 | public object State { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Abstractions/PostEvictionDelegate.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Extensions.Caching.Memory 5 | { 6 | /// 7 | /// Signature of the callback which gets called when a cache entry expires. 8 | /// 9 | /// 10 | /// 11 | /// The . 12 | /// The information that was passed when registering the callback. 13 | public delegate void PostEvictionDelegate(object key, object value, EvictionReason reason, object state); 14 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/CacheEntryHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | 7 | namespace Microsoft.Extensions.Caching.Memory 8 | { 9 | internal class CacheEntryHelper 10 | { 11 | private static readonly AsyncLocal _scopes = new AsyncLocal(); 12 | 13 | internal static CacheEntryStack Scopes 14 | { 15 | get { return _scopes.Value; } 16 | set { _scopes.Value = value; } 17 | } 18 | 19 | internal static CacheEntry Current 20 | { 21 | get 22 | { 23 | var scopes = GetOrCreateScopes(); 24 | return scopes.Peek(); 25 | } 26 | } 27 | 28 | internal static IDisposable EnterScope(CacheEntry entry) 29 | { 30 | var scopes = GetOrCreateScopes(); 31 | 32 | var scopeLease = new ScopeLease(scopes); 33 | Scopes = scopes.Push(entry); 34 | 35 | return scopeLease; 36 | } 37 | 38 | private static CacheEntryStack GetOrCreateScopes() 39 | { 40 | var scopes = Scopes; 41 | if (scopes == null) 42 | { 43 | scopes = CacheEntryStack.Empty; 44 | Scopes = scopes; 45 | } 46 | 47 | return scopes; 48 | } 49 | 50 | private sealed class ScopeLease : IDisposable 51 | { 52 | readonly CacheEntryStack _cacheEntryStack; 53 | 54 | public ScopeLease(CacheEntryStack cacheEntryStack) 55 | { 56 | _cacheEntryStack = cacheEntryStack; 57 | } 58 | 59 | public void Dispose() 60 | { 61 | Scopes = _cacheEntryStack; 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/CacheEntryStack.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.Memory 7 | { 8 | internal class CacheEntryStack 9 | { 10 | private readonly CacheEntryStack _previous; 11 | private readonly CacheEntry _entry; 12 | 13 | private CacheEntryStack() 14 | { 15 | } 16 | 17 | private CacheEntryStack(CacheEntryStack previous, CacheEntry entry) 18 | { 19 | if (previous == null) 20 | { 21 | throw new ArgumentNullException(nameof(previous)); 22 | } 23 | 24 | _previous = previous; 25 | _entry = entry; 26 | } 27 | 28 | public static CacheEntryStack Empty { get; } = new CacheEntryStack(); 29 | 30 | public CacheEntryStack Push(CacheEntry c) 31 | { 32 | return new CacheEntryStack(this, c); 33 | } 34 | 35 | public CacheEntry Peek() 36 | { 37 | return _entry; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/MemoryCacheOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Internal; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace Microsoft.Extensions.Caching.Memory 9 | { 10 | public class MemoryCacheOptions : IOptions 11 | { 12 | private long? _sizeLimit; 13 | private double _compactionPercentage = 0.05; 14 | 15 | public ISystemClock Clock { get; set; } 16 | 17 | [Obsolete("This is obsolete and will be removed in a future version.")] 18 | public bool CompactOnMemoryPressure { get; set; } 19 | 20 | /// 21 | /// Gets or sets the minimum length of time between successive scans for expired items. 22 | /// 23 | public TimeSpan ExpirationScanFrequency { get; set; } = TimeSpan.FromMinutes(1); 24 | 25 | /// 26 | /// Gets or sets the maximum size of the cache. 27 | /// 28 | public long? SizeLimit 29 | { 30 | get => _sizeLimit; 31 | set 32 | { 33 | if (value < 0) 34 | { 35 | throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(value)} must be non-negative."); 36 | } 37 | 38 | _sizeLimit = value; 39 | } 40 | } 41 | 42 | /// 43 | /// Gets or sets the amount to compact the cache by when the maximum size is exceeded. 44 | /// 45 | public double CompactionPercentage 46 | { 47 | get => _compactionPercentage; 48 | set 49 | { 50 | if (value < 0 || value > 1) 51 | { 52 | throw new ArgumentOutOfRangeException(nameof(value), value, $"{nameof(value)} must be between 0 and 1 inclusive."); 53 | } 54 | 55 | _compactionPercentage = value; 56 | } 57 | } 58 | 59 | MemoryCacheOptions IOptions.Value 60 | { 61 | get { return this; } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/MemoryCacheServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Caching.Distributed; 6 | using Microsoft.Extensions.Caching.Memory; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | 9 | namespace Microsoft.Extensions.DependencyInjection 10 | { 11 | /// 12 | /// Extension methods for setting up memory cache related services in an . 13 | /// 14 | public static class MemoryCacheServiceCollectionExtensions 15 | { 16 | /// 17 | /// Adds a non distributed in memory implementation of to the 18 | /// . 19 | /// 20 | /// The to add services to. 21 | /// The so that additional calls can be chained. 22 | public static IServiceCollection AddMemoryCache(this IServiceCollection services) 23 | { 24 | if (services == null) 25 | { 26 | throw new ArgumentNullException(nameof(services)); 27 | } 28 | 29 | services.AddOptions(); 30 | services.TryAdd(ServiceDescriptor.Singleton()); 31 | 32 | return services; 33 | } 34 | 35 | /// 36 | /// Adds a non distributed in memory implementation of to the 37 | /// . 38 | /// 39 | /// The to add services to. 40 | /// 41 | /// The to configure the provided . 42 | /// 43 | /// The so that additional calls can be chained. 44 | public static IServiceCollection AddMemoryCache(this IServiceCollection services, Action setupAction) 45 | { 46 | if (services == null) 47 | { 48 | throw new ArgumentNullException(nameof(services)); 49 | } 50 | 51 | if (setupAction == null) 52 | { 53 | throw new ArgumentNullException(nameof(setupAction)); 54 | } 55 | 56 | services.AddMemoryCache(); 57 | services.Configure(setupAction); 58 | 59 | return services; 60 | } 61 | 62 | /// 63 | /// Adds a default implementation of that stores items in memory 64 | /// to the . Frameworks that require a distributed cache to work 65 | /// can safely add this dependency as part of their dependency list to ensure that there is at least 66 | /// one implementation available. 67 | /// 68 | /// 69 | /// should only be used in single 70 | /// server scenarios as this cache stores items in memory and doesn't expand across multiple machines. 71 | /// For those scenarios it is recommended to use a proper distributed cache that can expand across 72 | /// multiple machines. 73 | /// 74 | /// The to add services to. 75 | /// The so that additional calls can be chained. 76 | public static IServiceCollection AddDistributedMemoryCache(this IServiceCollection services) 77 | { 78 | if (services == null) 79 | { 80 | throw new ArgumentNullException(nameof(services)); 81 | } 82 | 83 | services.AddOptions(); 84 | services.TryAdd(ServiceDescriptor.Singleton()); 85 | 86 | return services; 87 | } 88 | 89 | /// 90 | /// Adds a default implementation of that stores items in memory 91 | /// to the . Frameworks that require a distributed cache to work 92 | /// can safely add this dependency as part of their dependency list to ensure that there is at least 93 | /// one implementation available. 94 | /// 95 | /// 96 | /// should only be used in single 97 | /// server scenarios as this cache stores items in memory and doesn't expand across multiple machines. 98 | /// For those scenarios it is recommended to use a proper distributed cache that can expand across 99 | /// multiple machines. 100 | /// 101 | /// The to add services to. 102 | /// 103 | /// The to configure the provided . 104 | /// 105 | /// The so that additional calls can be chained. 106 | public static IServiceCollection AddDistributedMemoryCache(this IServiceCollection services, Action setupAction) 107 | { 108 | if (services == null) 109 | { 110 | throw new ArgumentNullException(nameof(services)); 111 | } 112 | 113 | if (setupAction == null) 114 | { 115 | throw new ArgumentNullException(nameof(setupAction)); 116 | } 117 | 118 | services.AddDistributedMemoryCache(); 119 | services.Configure(setupAction); 120 | 121 | return services; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/MemoryDistributedCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Memory; 8 | using Microsoft.Extensions.Options; 9 | 10 | namespace Microsoft.Extensions.Caching.Distributed 11 | { 12 | public class MemoryDistributedCache : IDistributedCache 13 | { 14 | private static readonly Task CompletedTask = Task.FromResult(null); 15 | 16 | private readonly IMemoryCache _memCache; 17 | 18 | public MemoryDistributedCache(IOptions optionsAccessor) 19 | { 20 | if (optionsAccessor == null) 21 | { 22 | throw new ArgumentNullException(nameof(optionsAccessor)); 23 | } 24 | 25 | _memCache = new MemoryCache(optionsAccessor.Value); 26 | } 27 | 28 | public byte[] Get(string key) 29 | { 30 | if (key == null) 31 | { 32 | throw new ArgumentNullException(nameof(key)); 33 | } 34 | 35 | return (byte[])_memCache.Get(key); 36 | } 37 | 38 | public Task GetAsync(string key, CancellationToken token = default(CancellationToken)) 39 | { 40 | if (key == null) 41 | { 42 | throw new ArgumentNullException(nameof(key)); 43 | } 44 | 45 | return Task.FromResult(Get(key)); 46 | } 47 | 48 | public void Set(string key, byte[] value, DistributedCacheEntryOptions options) 49 | { 50 | if (key == null) 51 | { 52 | throw new ArgumentNullException(nameof(key)); 53 | } 54 | 55 | if (value == null) 56 | { 57 | throw new ArgumentNullException(nameof(value)); 58 | } 59 | 60 | if (options == null) 61 | { 62 | throw new ArgumentNullException(nameof(options)); 63 | } 64 | 65 | var memoryCacheEntryOptions = new MemoryCacheEntryOptions(); 66 | memoryCacheEntryOptions.AbsoluteExpiration = options.AbsoluteExpiration; 67 | memoryCacheEntryOptions.AbsoluteExpirationRelativeToNow = options.AbsoluteExpirationRelativeToNow; 68 | memoryCacheEntryOptions.SlidingExpiration = options.SlidingExpiration; 69 | memoryCacheEntryOptions.Size = value.Length; 70 | 71 | _memCache.Set(key, value, memoryCacheEntryOptions); 72 | } 73 | 74 | public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) 75 | { 76 | if (key == null) 77 | { 78 | throw new ArgumentNullException(nameof(key)); 79 | } 80 | 81 | if (value == null) 82 | { 83 | throw new ArgumentNullException(nameof(value)); 84 | } 85 | 86 | if (options == null) 87 | { 88 | throw new ArgumentNullException(nameof(options)); 89 | } 90 | 91 | Set(key, value, options); 92 | return CompletedTask; 93 | } 94 | 95 | public void Refresh(string key) 96 | { 97 | if (key == null) 98 | { 99 | throw new ArgumentNullException(nameof(key)); 100 | } 101 | 102 | _memCache.TryGetValue(key, out object value); 103 | } 104 | 105 | public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken)) 106 | { 107 | if (key == null) 108 | { 109 | throw new ArgumentNullException(nameof(key)); 110 | } 111 | 112 | Refresh(key); 113 | return CompletedTask; 114 | } 115 | 116 | public void Remove(string key) 117 | { 118 | if (key == null) 119 | { 120 | throw new ArgumentNullException(nameof(key)); 121 | } 122 | 123 | _memCache.Remove(key); 124 | } 125 | 126 | public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken)) 127 | { 128 | if (key == null) 129 | { 130 | throw new ArgumentNullException(nameof(key)); 131 | } 132 | 133 | Remove(key); 134 | return CompletedTask; 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/MemoryDistributedCacheOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Extensions.Caching.Memory 5 | { 6 | public class MemoryDistributedCacheOptions : MemoryCacheOptions 7 | { 8 | public MemoryDistributedCacheOptions() 9 | : base() 10 | { 11 | // Default size limit of 200 MB 12 | SizeLimit = 200 * 1024 * 1024; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/Microsoft.Extensions.Caching.Memory.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | In-memory cache implementation of Microsoft.Extensions.Caching.Memory.IMemoryCache. 5 | netstandard2.0 6 | $(NoWarn);CS1591 7 | true 8 | cache;memorycache 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.Memory/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Runtime.CompilerServices; 5 | 6 | [assembly: InternalsVisibleTo("Microsoft.Extensions.Caching.Memory.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] 7 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/Columns.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | namespace Microsoft.Extensions.Caching.SqlServer 5 | { 6 | internal static class Columns 7 | { 8 | public static class Names 9 | { 10 | public const string CacheItemId = "Id"; 11 | public const string CacheItemValue = "Value"; 12 | public const string ExpiresAtTime = "ExpiresAtTime"; 13 | public const string SlidingExpirationInSeconds = "SlidingExpirationInSeconds"; 14 | public const string AbsoluteExpiration = "AbsoluteExpiration"; 15 | } 16 | 17 | public static class Indexes 18 | { 19 | // The value of the following index positions is dependent on how the SQL queries 20 | // are selecting the columns. 21 | public const int CacheItemIdIndex = 0; 22 | public const int ExpiresAtTimeIndex = 1; 23 | public const int SlidingExpirationInSecondsIndex = 2; 24 | public const int AbsoluteExpirationIndex = 3; 25 | public const int CacheItemValueIndex = 4; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/IDatabaseOperations.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Caching.Distributed; 7 | 8 | namespace Microsoft.Extensions.Caching.SqlServer 9 | { 10 | internal interface IDatabaseOperations 11 | { 12 | byte[] GetCacheItem(string key); 13 | 14 | Task GetCacheItemAsync(string key, CancellationToken token = default(CancellationToken)); 15 | 16 | void RefreshCacheItem(string key); 17 | 18 | Task RefreshCacheItemAsync(string key, CancellationToken token = default(CancellationToken)); 19 | 20 | void DeleteCacheItem(string key); 21 | 22 | Task DeleteCacheItemAsync(string key, CancellationToken token = default(CancellationToken)); 23 | 24 | void SetCacheItem(string key, byte[] value, DistributedCacheEntryOptions options); 25 | 26 | Task SetCacheItemAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)); 27 | 28 | void DeleteExpiredCacheItems(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/Microsoft.Extensions.Caching.SqlServer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Distributed cache implementation of Microsoft.Extensions.Caching.Distributed.IDistributedCache using Microsoft SQL Server. 5 | netstandard2.0 6 | $(NoWarn);CS1591 7 | true 8 | cache;distributedcache;sqlserver 9 | 10 | 11 | false 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/MonoDatabaseOperations.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Data; 6 | using System.Data.SqlClient; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Extensions.Caching.Distributed; 10 | using Microsoft.Extensions.Internal; 11 | 12 | namespace Microsoft.Extensions.Caching.SqlServer 13 | { 14 | internal class MonoDatabaseOperations : DatabaseOperations 15 | { 16 | public MonoDatabaseOperations( 17 | string connectionString, string schemaName, string tableName, ISystemClock systemClock) 18 | : base(connectionString, schemaName, tableName, systemClock) 19 | { 20 | } 21 | 22 | protected override byte[] GetCacheItem(string key, bool includeValue) 23 | { 24 | var utcNow = SystemClock.UtcNow; 25 | 26 | string query; 27 | if (includeValue) 28 | { 29 | query = SqlQueries.GetCacheItem; 30 | } 31 | else 32 | { 33 | query = SqlQueries.GetCacheItemWithoutValue; 34 | } 35 | 36 | byte[] value = null; 37 | TimeSpan? slidingExpiration = null; 38 | DateTimeOffset? absoluteExpiration = null; 39 | DateTimeOffset expirationTime; 40 | using (var connection = new SqlConnection(ConnectionString)) 41 | { 42 | var command = new SqlCommand(query, connection); 43 | command.Parameters 44 | .AddCacheItemId(key) 45 | .AddWithValue("UtcNow", SqlDbType.DateTime, utcNow.UtcDateTime); 46 | 47 | connection.Open(); 48 | 49 | var reader = command.ExecuteReader(CommandBehavior.SingleRow | CommandBehavior.SingleResult); 50 | 51 | if (reader.Read()) 52 | { 53 | var id = reader.GetString(Columns.Indexes.CacheItemIdIndex); 54 | 55 | expirationTime = DateTimeOffset.Parse(reader[Columns.Indexes.ExpiresAtTimeIndex].ToString()); 56 | 57 | if (!reader.IsDBNull(Columns.Indexes.SlidingExpirationInSecondsIndex)) 58 | { 59 | slidingExpiration = TimeSpan.FromSeconds( 60 | reader.GetInt64(Columns.Indexes.SlidingExpirationInSecondsIndex)); 61 | } 62 | 63 | if (!reader.IsDBNull(Columns.Indexes.AbsoluteExpirationIndex)) 64 | { 65 | absoluteExpiration = DateTimeOffset.Parse( 66 | reader[Columns.Indexes.AbsoluteExpirationIndex].ToString()); 67 | } 68 | 69 | if (includeValue) 70 | { 71 | value = (byte[])reader[Columns.Indexes.CacheItemValueIndex]; 72 | } 73 | } 74 | else 75 | { 76 | return null; 77 | } 78 | } 79 | 80 | return value; 81 | } 82 | 83 | protected override async Task GetCacheItemAsync(string key, bool includeValue, CancellationToken token = default(CancellationToken)) 84 | { 85 | token.ThrowIfCancellationRequested(); 86 | 87 | var utcNow = SystemClock.UtcNow; 88 | 89 | string query; 90 | if (includeValue) 91 | { 92 | query = SqlQueries.GetCacheItem; 93 | } 94 | else 95 | { 96 | query = SqlQueries.GetCacheItemWithoutValue; 97 | } 98 | 99 | byte[] value = null; 100 | TimeSpan? slidingExpiration = null; 101 | DateTimeOffset? absoluteExpiration = null; 102 | DateTimeOffset expirationTime; 103 | using (var connection = new SqlConnection(ConnectionString)) 104 | { 105 | var command = new SqlCommand(SqlQueries.GetCacheItem, connection); 106 | command.Parameters 107 | .AddCacheItemId(key) 108 | .AddWithValue("UtcNow", SqlDbType.DateTime, utcNow.UtcDateTime); 109 | 110 | await connection.OpenAsync(token); 111 | 112 | var reader = await command.ExecuteReaderAsync( 113 | CommandBehavior.SingleRow | CommandBehavior.SingleResult, 114 | token); 115 | 116 | if (await reader.ReadAsync(token)) 117 | { 118 | var id = reader.GetString(Columns.Indexes.CacheItemIdIndex); 119 | 120 | expirationTime = DateTimeOffset.Parse(reader[Columns.Indexes.ExpiresAtTimeIndex].ToString()); 121 | 122 | if (!await reader.IsDBNullAsync(Columns.Indexes.SlidingExpirationInSecondsIndex, token)) 123 | { 124 | slidingExpiration = TimeSpan.FromSeconds( 125 | Convert.ToInt64(reader[Columns.Indexes.SlidingExpirationInSecondsIndex].ToString())); 126 | } 127 | 128 | if (!await reader.IsDBNullAsync(Columns.Indexes.AbsoluteExpirationIndex, token)) 129 | { 130 | absoluteExpiration = DateTimeOffset.Parse( 131 | reader[Columns.Indexes.AbsoluteExpirationIndex].ToString()); 132 | } 133 | 134 | if (includeValue) 135 | { 136 | value = (byte[])reader[Columns.Indexes.CacheItemValueIndex]; 137 | } 138 | } 139 | else 140 | { 141 | return null; 142 | } 143 | } 144 | 145 | return value; 146 | } 147 | 148 | public override void SetCacheItem(string key, byte[] value, DistributedCacheEntryOptions options) 149 | { 150 | var utcNow = SystemClock.UtcNow; 151 | 152 | var absoluteExpiration = GetAbsoluteExpiration(utcNow, options); 153 | ValidateOptions(options.SlidingExpiration, absoluteExpiration); 154 | 155 | using (var connection = new SqlConnection(ConnectionString)) 156 | { 157 | var upsertCommand = new SqlCommand(SqlQueries.SetCacheItem, connection); 158 | upsertCommand.Parameters 159 | .AddCacheItemId(key) 160 | .AddCacheItemValue(value) 161 | .AddSlidingExpirationInSeconds(options.SlidingExpiration) 162 | .AddAbsoluteExpirationMono(absoluteExpiration) 163 | .AddWithValue("UtcNow", SqlDbType.DateTime, utcNow.UtcDateTime); 164 | 165 | connection.Open(); 166 | 167 | try 168 | { 169 | upsertCommand.ExecuteNonQuery(); 170 | } 171 | catch (SqlException ex) 172 | { 173 | if (IsDuplicateKeyException(ex)) 174 | { 175 | // There is a possibility that multiple requests can try to add the same item to the cache, in 176 | // which case we receive a 'duplicate key' exception on the primary key column. 177 | } 178 | else 179 | { 180 | throw; 181 | } 182 | } 183 | } 184 | } 185 | 186 | public override async Task SetCacheItemAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) 187 | { 188 | token.ThrowIfCancellationRequested(); 189 | 190 | var utcNow = SystemClock.UtcNow; 191 | 192 | var absoluteExpiration = GetAbsoluteExpiration(utcNow, options); 193 | ValidateOptions(options.SlidingExpiration, absoluteExpiration); 194 | 195 | using (var connection = new SqlConnection(ConnectionString)) 196 | { 197 | var upsertCommand = new SqlCommand(SqlQueries.SetCacheItem, connection); 198 | upsertCommand.Parameters 199 | .AddCacheItemId(key) 200 | .AddCacheItemValue(value) 201 | .AddSlidingExpirationInSeconds(options.SlidingExpiration) 202 | .AddAbsoluteExpirationMono(absoluteExpiration) 203 | .AddWithValue("UtcNow", SqlDbType.DateTime, utcNow.UtcDateTime); 204 | 205 | await connection.OpenAsync(token); 206 | 207 | try 208 | { 209 | await upsertCommand.ExecuteNonQueryAsync(token); 210 | } 211 | catch (SqlException ex) 212 | { 213 | if (IsDuplicateKeyException(ex)) 214 | { 215 | // There is a possibility that multiple requests can try to add the same item to the cache, in 216 | // which case we receive a 'duplicate key' exception on the primary key column. 217 | } 218 | else 219 | { 220 | throw; 221 | } 222 | } 223 | } 224 | } 225 | 226 | public override void DeleteExpiredCacheItems() 227 | { 228 | var utcNow = SystemClock.UtcNow; 229 | 230 | using (var connection = new SqlConnection(ConnectionString)) 231 | { 232 | var command = new SqlCommand(SqlQueries.DeleteExpiredCacheItems, connection); 233 | command.Parameters.AddWithValue("UtcNow", SqlDbType.DateTime, utcNow.UtcDateTime); 234 | 235 | connection.Open(); 236 | 237 | var effectedRowCount = command.ExecuteNonQuery(); 238 | } 239 | } 240 | } 241 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/MonoSqlParameterCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Data; 6 | using System.Data.SqlClient; 7 | 8 | namespace Microsoft.Extensions.Caching.SqlServer 9 | { 10 | // Since Mono currently does not have support for DateTimeOffset, we convert the time to UtcDateTime. 11 | // Even though the database column is of type 'datetimeoffset', we can store the UtcDateTime, in which case 12 | // the zone is set as 00:00. If you look at the below examples, DateTimeOffset.UtcNow 13 | // and DateTimeOffset.UtcDateTime are almost the same. 14 | // 15 | // Examples: 16 | // DateTimeOffset.Now: 6/29/2015 1:20:40 PM - 07:00 17 | // DateTimeOffset.UtcNow: 6/29/2015 8:20:40 PM + 00:00 18 | // DateTimeOffset.UtcDateTime: 6/29/2015 8:20:40 PM 19 | internal static class MonoSqlParameterCollectionExtensions 20 | { 21 | public static SqlParameterCollection AddExpiresAtTimeMono( 22 | this SqlParameterCollection parameters, 23 | DateTimeOffset utcTime) 24 | { 25 | return parameters.AddWithValue(Columns.Names.ExpiresAtTime, SqlDbType.DateTime, utcTime.UtcDateTime); 26 | } 27 | 28 | 29 | public static SqlParameterCollection AddAbsoluteExpirationMono( 30 | this SqlParameterCollection parameters, 31 | DateTimeOffset? utcTime) 32 | { 33 | if (utcTime.HasValue) 34 | { 35 | return parameters.AddWithValue( 36 | Columns.Names.AbsoluteExpiration, SqlDbType.DateTime, utcTime.Value.UtcDateTime); 37 | } 38 | else 39 | { 40 | return parameters.AddWithValue( 41 | Columns.Names.AbsoluteExpiration, SqlDbType.DateTime, DBNull.Value); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/PlatformHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.SqlServer 7 | { 8 | internal static class PlatformHelper 9 | { 10 | private static Lazy _isMono = new Lazy(() => Type.GetType("Mono.Runtime") != null); 11 | 12 | public static bool IsMono 13 | { 14 | get 15 | { 16 | return _isMono.Value; 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Reflection; 5 | using System.Resources; 6 | using System.Runtime.CompilerServices; 7 | 8 | [assembly: InternalsVisibleTo("Microsoft.Extensions.Caching.SqlServer.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] 9 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/SqlParameterCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Data; 6 | using System.Data.SqlClient; 7 | 8 | namespace Microsoft.Extensions.Caching.SqlServer 9 | { 10 | internal static class SqlParameterCollectionExtensions 11 | { 12 | // For all values where the length is less than the below value, try setting the size of the 13 | // parameter for better performance. 14 | public const int DefaultValueColumnWidth = 8000; 15 | 16 | // Maximum size of a primary key column is 900 bytes (898 bytes from the key + 2 additional bytes required by 17 | // the Sql Server). 18 | public const int CacheItemIdColumnWidth = 449; 19 | 20 | public static SqlParameterCollection AddCacheItemId(this SqlParameterCollection parameters, string value) 21 | { 22 | return parameters.AddWithValue(Columns.Names.CacheItemId, SqlDbType.NVarChar, CacheItemIdColumnWidth, value); 23 | } 24 | 25 | public static SqlParameterCollection AddCacheItemValue(this SqlParameterCollection parameters, byte[] value) 26 | { 27 | if (value != null && value.Length < DefaultValueColumnWidth) 28 | { 29 | return parameters.AddWithValue( 30 | Columns.Names.CacheItemValue, 31 | SqlDbType.VarBinary, 32 | DefaultValueColumnWidth, 33 | value); 34 | } 35 | else 36 | { 37 | // do not mention the size 38 | return parameters.AddWithValue(Columns.Names.CacheItemValue, SqlDbType.VarBinary, value); 39 | } 40 | } 41 | 42 | public static SqlParameterCollection AddSlidingExpirationInSeconds( 43 | this SqlParameterCollection parameters, 44 | TimeSpan? value) 45 | { 46 | if (value.HasValue) 47 | { 48 | return parameters.AddWithValue( 49 | Columns.Names.SlidingExpirationInSeconds, SqlDbType.BigInt, value.Value.TotalSeconds); 50 | } 51 | else 52 | { 53 | return parameters.AddWithValue(Columns.Names.SlidingExpirationInSeconds, SqlDbType.BigInt, DBNull.Value); 54 | } 55 | } 56 | 57 | public static SqlParameterCollection AddAbsoluteExpiration( 58 | this SqlParameterCollection parameters, 59 | DateTimeOffset? utcTime) 60 | { 61 | if (utcTime.HasValue) 62 | { 63 | return parameters.AddWithValue( 64 | Columns.Names.AbsoluteExpiration, SqlDbType.DateTimeOffset, utcTime.Value); 65 | } 66 | else 67 | { 68 | return parameters.AddWithValue( 69 | Columns.Names.AbsoluteExpiration, SqlDbType.DateTimeOffset, DBNull.Value); 70 | } 71 | } 72 | 73 | public static SqlParameterCollection AddWithValue( 74 | this SqlParameterCollection parameters, 75 | string parameterName, 76 | SqlDbType dbType, 77 | object value) 78 | { 79 | var parameter = new SqlParameter(parameterName, dbType); 80 | parameter.Value = value; 81 | parameters.Add(parameter); 82 | parameter.ResetSqlDbType(); 83 | return parameters; 84 | } 85 | 86 | public static SqlParameterCollection AddWithValue( 87 | this SqlParameterCollection parameters, 88 | string parameterName, 89 | SqlDbType dbType, 90 | int size, 91 | object value) 92 | { 93 | var parameter = new SqlParameter(parameterName, dbType, size); 94 | parameter.Value = value; 95 | parameters.Add(parameter); 96 | parameter.ResetSqlDbType(); 97 | return parameters; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/SqlQueries.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Internal; 5 | 6 | namespace Microsoft.Extensions.Caching.SqlServer 7 | { 8 | internal class SqlQueries 9 | { 10 | private const string TableInfoFormat = 11 | "SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE " + 12 | "FROM INFORMATION_SCHEMA.TABLES " + 13 | "WHERE TABLE_SCHEMA = '{0}' " + 14 | "AND TABLE_NAME = '{1}'"; 15 | 16 | private const string UpdateCacheItemFormat = 17 | "UPDATE {0} " + 18 | "SET ExpiresAtTime = " + 19 | "(CASE " + 20 | "WHEN DATEDIFF(SECOND, @UtcNow, AbsoluteExpiration) <= SlidingExpirationInSeconds " + 21 | "THEN AbsoluteExpiration " + 22 | "ELSE " + 23 | "DATEADD(SECOND, SlidingExpirationInSeconds, @UtcNow) " + 24 | "END) " + 25 | "WHERE Id = @Id " + 26 | "AND @UtcNow <= ExpiresAtTime " + 27 | "AND SlidingExpirationInSeconds IS NOT NULL " + 28 | "AND (AbsoluteExpiration IS NULL OR AbsoluteExpiration <> ExpiresAtTime) ;"; 29 | 30 | private const string GetCacheItemFormat = 31 | "SELECT Id, ExpiresAtTime, SlidingExpirationInSeconds, AbsoluteExpiration, Value " + 32 | "FROM {0} WHERE Id = @Id AND @UtcNow <= ExpiresAtTime;"; 33 | 34 | private const string SetCacheItemFormat = 35 | "DECLARE @ExpiresAtTime DATETIMEOFFSET; " + 36 | "SET @ExpiresAtTime = " + 37 | "(CASE " + 38 | "WHEN (@SlidingExpirationInSeconds IS NUll) " + 39 | "THEN @AbsoluteExpiration " + 40 | "ELSE " + 41 | "DATEADD(SECOND, Convert(bigint, @SlidingExpirationInSeconds), @UtcNow) " + 42 | "END);" + 43 | "UPDATE {0} SET Value = @Value, ExpiresAtTime = @ExpiresAtTime," + 44 | "SlidingExpirationInSeconds = @SlidingExpirationInSeconds, AbsoluteExpiration = @AbsoluteExpiration " + 45 | "WHERE Id = @Id " + 46 | "IF (@@ROWCOUNT = 0) " + 47 | "BEGIN " + 48 | "INSERT INTO {0} " + 49 | "(Id, Value, ExpiresAtTime, SlidingExpirationInSeconds, AbsoluteExpiration) " + 50 | "VALUES (@Id, @Value, @ExpiresAtTime, @SlidingExpirationInSeconds, @AbsoluteExpiration); " + 51 | "END "; 52 | 53 | private const string DeleteCacheItemFormat = "DELETE FROM {0} WHERE Id = @Id"; 54 | 55 | public const string DeleteExpiredCacheItemsFormat = "DELETE FROM {0} WHERE @UtcNow > ExpiresAtTime"; 56 | 57 | public SqlQueries(string schemaName, string tableName) 58 | { 59 | var tableNameWithSchema = string.Format( 60 | "{0}.{1}", DelimitIdentifier(schemaName), DelimitIdentifier(tableName)); 61 | 62 | // when retrieving an item, we do an UPDATE first and then a SELECT 63 | GetCacheItem = string.Format(UpdateCacheItemFormat + GetCacheItemFormat, tableNameWithSchema); 64 | GetCacheItemWithoutValue = string.Format(UpdateCacheItemFormat, tableNameWithSchema); 65 | DeleteCacheItem = string.Format(DeleteCacheItemFormat, tableNameWithSchema); 66 | DeleteExpiredCacheItems = string.Format(DeleteExpiredCacheItemsFormat, tableNameWithSchema); 67 | SetCacheItem = string.Format(SetCacheItemFormat, tableNameWithSchema); 68 | TableInfo = string.Format(TableInfoFormat, EscapeLiteral(schemaName), EscapeLiteral(tableName)); 69 | } 70 | 71 | public string TableInfo { get; } 72 | 73 | public string GetCacheItem { get; } 74 | 75 | public string GetCacheItemWithoutValue { get; } 76 | 77 | public string SetCacheItem { get; } 78 | 79 | public string DeleteCacheItem { get; } 80 | 81 | public string DeleteExpiredCacheItems { get; } 82 | 83 | // From EF's SqlServerQuerySqlGenerator 84 | private string DelimitIdentifier(string identifier) 85 | { 86 | return "[" + identifier.Replace("]", "]]") + "]"; 87 | } 88 | 89 | private string EscapeLiteral(string literal) 90 | { 91 | return literal.Replace("'", "''"); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/SqlServerCache.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.Caching.Distributed; 8 | using Microsoft.Extensions.Internal; 9 | using Microsoft.Extensions.Options; 10 | 11 | namespace Microsoft.Extensions.Caching.SqlServer 12 | { 13 | /// 14 | /// Distributed cache implementation using Microsoft SQL Server database. 15 | /// 16 | public class SqlServerCache : IDistributedCache 17 | { 18 | private static readonly TimeSpan MinimumExpiredItemsDeletionInterval = TimeSpan.FromMinutes(5); 19 | private static readonly TimeSpan DefaultExpiredItemsDeletionInterval = TimeSpan.FromMinutes(30); 20 | 21 | private readonly IDatabaseOperations _dbOperations; 22 | private readonly ISystemClock _systemClock; 23 | private readonly TimeSpan _expiredItemsDeletionInterval; 24 | private DateTimeOffset _lastExpirationScan; 25 | private readonly Action _deleteExpiredCachedItemsDelegate; 26 | private readonly TimeSpan _defaultSlidingExpiration; 27 | 28 | public SqlServerCache(IOptions options) 29 | { 30 | var cacheOptions = options.Value; 31 | 32 | if (string.IsNullOrEmpty(cacheOptions.ConnectionString)) 33 | { 34 | throw new ArgumentException( 35 | $"{nameof(SqlServerCacheOptions.ConnectionString)} cannot be empty or null."); 36 | } 37 | if (string.IsNullOrEmpty(cacheOptions.SchemaName)) 38 | { 39 | throw new ArgumentException( 40 | $"{nameof(SqlServerCacheOptions.SchemaName)} cannot be empty or null."); 41 | } 42 | if (string.IsNullOrEmpty(cacheOptions.TableName)) 43 | { 44 | throw new ArgumentException( 45 | $"{nameof(SqlServerCacheOptions.TableName)} cannot be empty or null."); 46 | } 47 | if (cacheOptions.ExpiredItemsDeletionInterval.HasValue && 48 | cacheOptions.ExpiredItemsDeletionInterval.Value < MinimumExpiredItemsDeletionInterval) 49 | { 50 | throw new ArgumentException( 51 | $"{nameof(SqlServerCacheOptions.ExpiredItemsDeletionInterval)} cannot be less the minimum " + 52 | $"value of {MinimumExpiredItemsDeletionInterval.TotalMinutes} minutes."); 53 | } 54 | if (cacheOptions.DefaultSlidingExpiration <= TimeSpan.Zero) 55 | { 56 | throw new ArgumentOutOfRangeException( 57 | nameof(cacheOptions.DefaultSlidingExpiration), 58 | cacheOptions.DefaultSlidingExpiration, 59 | "The sliding expiration value must be positive."); 60 | } 61 | 62 | _systemClock = cacheOptions.SystemClock ?? new SystemClock(); 63 | _expiredItemsDeletionInterval = 64 | cacheOptions.ExpiredItemsDeletionInterval ?? DefaultExpiredItemsDeletionInterval; 65 | _deleteExpiredCachedItemsDelegate = DeleteExpiredCacheItems; 66 | _defaultSlidingExpiration = cacheOptions.DefaultSlidingExpiration; 67 | 68 | // SqlClient library on Mono doesn't have support for DateTimeOffset and also 69 | // it doesn't have support for apis like GetFieldValue, GetFieldValueAsync etc. 70 | // So we detect the platform to perform things differently for Mono vs. non-Mono platforms. 71 | if (PlatformHelper.IsMono) 72 | { 73 | _dbOperations = new MonoDatabaseOperations( 74 | cacheOptions.ConnectionString, 75 | cacheOptions.SchemaName, 76 | cacheOptions.TableName, 77 | _systemClock); 78 | } 79 | else 80 | { 81 | _dbOperations = new DatabaseOperations( 82 | cacheOptions.ConnectionString, 83 | cacheOptions.SchemaName, 84 | cacheOptions.TableName, 85 | _systemClock); 86 | } 87 | } 88 | 89 | public byte[] Get(string key) 90 | { 91 | if (key == null) 92 | { 93 | throw new ArgumentNullException(nameof(key)); 94 | } 95 | 96 | var value = _dbOperations.GetCacheItem(key); 97 | 98 | ScanForExpiredItemsIfRequired(); 99 | 100 | return value; 101 | } 102 | 103 | public async Task GetAsync(string key, CancellationToken token = default(CancellationToken)) 104 | { 105 | if (key == null) 106 | { 107 | throw new ArgumentNullException(nameof(key)); 108 | } 109 | 110 | token.ThrowIfCancellationRequested(); 111 | 112 | var value = await _dbOperations.GetCacheItemAsync(key, token); 113 | 114 | ScanForExpiredItemsIfRequired(); 115 | 116 | return value; 117 | } 118 | 119 | public void Refresh(string key) 120 | { 121 | if (key == null) 122 | { 123 | throw new ArgumentNullException(nameof(key)); 124 | } 125 | 126 | _dbOperations.RefreshCacheItem(key); 127 | 128 | ScanForExpiredItemsIfRequired(); 129 | } 130 | 131 | public async Task RefreshAsync(string key, CancellationToken token = default(CancellationToken)) 132 | { 133 | if (key == null) 134 | { 135 | throw new ArgumentNullException(nameof(key)); 136 | } 137 | 138 | token.ThrowIfCancellationRequested(); 139 | 140 | await _dbOperations.RefreshCacheItemAsync(key, token); 141 | 142 | ScanForExpiredItemsIfRequired(); 143 | } 144 | 145 | public void Remove(string key) 146 | { 147 | if (key == null) 148 | { 149 | throw new ArgumentNullException(nameof(key)); 150 | } 151 | 152 | _dbOperations.DeleteCacheItem(key); 153 | 154 | ScanForExpiredItemsIfRequired(); 155 | } 156 | 157 | public async Task RemoveAsync(string key, CancellationToken token = default(CancellationToken)) 158 | { 159 | if (key == null) 160 | { 161 | throw new ArgumentNullException(nameof(key)); 162 | } 163 | 164 | token.ThrowIfCancellationRequested(); 165 | 166 | await _dbOperations.DeleteCacheItemAsync(key, token); 167 | 168 | ScanForExpiredItemsIfRequired(); 169 | } 170 | 171 | public void Set(string key, byte[] value, DistributedCacheEntryOptions options) 172 | { 173 | if (key == null) 174 | { 175 | throw new ArgumentNullException(nameof(key)); 176 | } 177 | 178 | if (value == null) 179 | { 180 | throw new ArgumentNullException(nameof(value)); 181 | } 182 | 183 | if (options == null) 184 | { 185 | throw new ArgumentNullException(nameof(options)); 186 | } 187 | 188 | GetOptions(ref options); 189 | 190 | _dbOperations.SetCacheItem(key, value, options); 191 | 192 | ScanForExpiredItemsIfRequired(); 193 | } 194 | 195 | public async Task SetAsync( 196 | string key, 197 | byte[] value, 198 | DistributedCacheEntryOptions options, 199 | CancellationToken token = default(CancellationToken)) 200 | { 201 | if (key == null) 202 | { 203 | throw new ArgumentNullException(nameof(key)); 204 | } 205 | 206 | if (value == null) 207 | { 208 | throw new ArgumentNullException(nameof(value)); 209 | } 210 | 211 | if (options == null) 212 | { 213 | throw new ArgumentNullException(nameof(options)); 214 | } 215 | 216 | token.ThrowIfCancellationRequested(); 217 | 218 | GetOptions(ref options); 219 | 220 | await _dbOperations.SetCacheItemAsync(key, value, options, token); 221 | 222 | ScanForExpiredItemsIfRequired(); 223 | } 224 | 225 | // Called by multiple actions to see how long it's been since we last checked for expired items. 226 | // If sufficient time has elapsed then a scan is initiated on a background task. 227 | private void ScanForExpiredItemsIfRequired() 228 | { 229 | var utcNow = _systemClock.UtcNow; 230 | // TODO: Multiple threads could trigger this scan which leads to multiple calls to database. 231 | if ((utcNow - _lastExpirationScan) > _expiredItemsDeletionInterval) 232 | { 233 | _lastExpirationScan = utcNow; 234 | Task.Run(_deleteExpiredCachedItemsDelegate); 235 | } 236 | } 237 | 238 | private void DeleteExpiredCacheItems() 239 | { 240 | _dbOperations.DeleteExpiredCacheItems(); 241 | } 242 | 243 | private void GetOptions(ref DistributedCacheEntryOptions options) 244 | { 245 | if (!options.AbsoluteExpiration.HasValue 246 | && !options.AbsoluteExpirationRelativeToNow.HasValue 247 | && !options.SlidingExpiration.HasValue) 248 | { 249 | options = new DistributedCacheEntryOptions() 250 | { 251 | SlidingExpiration = _defaultSlidingExpiration 252 | }; 253 | } 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/SqlServerCacheOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Internal; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace Microsoft.Extensions.Caching.SqlServer 9 | { 10 | /// 11 | /// Configuration options for . 12 | /// 13 | public class SqlServerCacheOptions : IOptions 14 | { 15 | /// 16 | /// An abstraction to represent the clock of a machine in order to enable unit testing. 17 | /// 18 | public ISystemClock SystemClock { get; set; } 19 | 20 | /// 21 | /// The periodic interval to scan and delete expired items in the cache. Default is 30 minutes. 22 | /// 23 | public TimeSpan? ExpiredItemsDeletionInterval { get; set; } 24 | 25 | /// 26 | /// The connection string to the database. 27 | /// 28 | public string ConnectionString { get; set; } 29 | 30 | /// 31 | /// The schema name of the table. 32 | /// 33 | public string SchemaName { get; set; } 34 | 35 | /// 36 | /// Name of the table where the cache items are stored. 37 | /// 38 | public string TableName { get; set; } 39 | 40 | /// 41 | /// The default sliding expiration set for a cache entry if neither Absolute or SlidingExpiration has been set explicitly. 42 | /// By default, its 20 minutes. 43 | /// 44 | public TimeSpan DefaultSlidingExpiration { get; set; } = TimeSpan.FromMinutes(20); 45 | 46 | SqlServerCacheOptions IOptions.Value 47 | { 48 | get 49 | { 50 | return this; 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.SqlServer/SqlServerCacheServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Caching.Distributed; 6 | using Microsoft.Extensions.Caching.SqlServer; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | 9 | namespace Microsoft.Extensions.DependencyInjection 10 | { 11 | /// 12 | /// Extension methods for setting up Microsoft SQL Server distributed cache services in an . 13 | /// 14 | public static class SqlServerCachingServicesExtensions 15 | { 16 | /// 17 | /// Adds Microsoft SQL Server distributed caching services to the specified . 18 | /// 19 | /// The to add services to. 20 | /// An to configure the provided . 21 | /// The so that additional calls can be chained. 22 | public static IServiceCollection AddDistributedSqlServerCache(this IServiceCollection services, Action setupAction) 23 | { 24 | if (services == null) 25 | { 26 | throw new ArgumentNullException(nameof(services)); 27 | } 28 | 29 | if (setupAction == null) 30 | { 31 | throw new ArgumentNullException(nameof(setupAction)); 32 | } 33 | 34 | services.AddOptions(); 35 | AddSqlServerCacheServices(services); 36 | services.Configure(setupAction); 37 | 38 | return services; 39 | } 40 | 41 | // to enable unit testing 42 | internal static void AddSqlServerCacheServices(IServiceCollection services) 43 | { 44 | services.Add(ServiceDescriptor.Singleton()); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.StackExchangeRedis/Microsoft.Extensions.Caching.StackExchangeRedis.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Distributed cache implementation of Microsoft.Extensions.Caching.Distributed.IDistributedCache using Redis. 5 | netstandard2.0 6 | $(NoWarn);CS1591 7 | true 8 | cache;distributedcache;redis 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.StackExchangeRedis/RedisCacheOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.Options; 5 | using StackExchange.Redis; 6 | 7 | namespace Microsoft.Extensions.Caching.StackExchangeRedis 8 | { 9 | /// 10 | /// Configuration options for . 11 | /// 12 | public class RedisCacheOptions : IOptions 13 | { 14 | /// 15 | /// The configuration used to connect to Redis. 16 | /// 17 | public string Configuration { get; set; } 18 | 19 | /// 20 | /// The configuration used to connect to Redis. 21 | /// This is preferred over Configuration. 22 | /// 23 | public ConfigurationOptions ConfigurationOptions { get; set; } 24 | 25 | /// 26 | /// The Redis instance name. 27 | /// 28 | public string InstanceName { get; set; } 29 | 30 | RedisCacheOptions IOptions.Value 31 | { 32 | get { return this; } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.StackExchangeRedis/RedisCacheServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Caching.Distributed; 6 | using Microsoft.Extensions.Caching.StackExchangeRedis; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | 9 | namespace Microsoft.Extensions.DependencyInjection 10 | { 11 | /// 12 | /// Extension methods for setting up Redis distributed cache related services in an . 13 | /// 14 | public static class StackExchangeRedisCacheServiceCollectionExtensions 15 | { 16 | /// 17 | /// Adds Redis distributed caching services to the specified . 18 | /// 19 | /// The to add services to. 20 | /// An to configure the provided 21 | /// . 22 | /// The so that additional calls can be chained. 23 | public static IServiceCollection AddStackExchangeRedisCache(this IServiceCollection services, Action setupAction) 24 | { 25 | if (services == null) 26 | { 27 | throw new ArgumentNullException(nameof(services)); 28 | } 29 | 30 | if (setupAction == null) 31 | { 32 | throw new ArgumentNullException(nameof(setupAction)); 33 | } 34 | 35 | services.AddOptions(); 36 | services.Configure(setupAction); 37 | services.Add(ServiceDescriptor.Singleton()); 38 | 39 | return services; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Microsoft.Extensions.Caching.StackExchangeRedis/RedisExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Threading.Tasks; 5 | using StackExchange.Redis; 6 | 7 | namespace Microsoft.Extensions.Caching.StackExchangeRedis 8 | { 9 | internal static class RedisExtensions 10 | { 11 | private const string HmGetScript = (@"return redis.call('HMGET', KEYS[1], unpack(ARGV))"); 12 | 13 | internal static RedisValue[] HashMemberGet(this IDatabase cache, string key, params string[] members) 14 | { 15 | var result = cache.ScriptEvaluate( 16 | HmGetScript, 17 | new RedisKey[] { key }, 18 | GetRedisMembers(members)); 19 | 20 | // TODO: Error checking? 21 | return (RedisValue[])result; 22 | } 23 | 24 | internal static async Task HashMemberGetAsync( 25 | this IDatabase cache, 26 | string key, 27 | params string[] members) 28 | { 29 | var result = await cache.ScriptEvaluateAsync( 30 | HmGetScript, 31 | new RedisKey[] { key }, 32 | GetRedisMembers(members)); 33 | 34 | // TODO: Error checking? 35 | return (RedisValue[])result; 36 | } 37 | 38 | private static RedisValue[] GetRedisMembers(params string[] members) 39 | { 40 | var redisMembers = new RedisValue[members.Length]; 41 | for (int i = 0; i < members.Length; i++) 42 | { 43 | redisMembers[i] = (RedisValue)members[i]; 44 | } 45 | 46 | return redisMembers; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /test/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | netcoreapp2.2 6 | $(DeveloperBuildTestTfms) 7 | 8 | $(StandardTestTfms);net461 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.Memory.Tests/CacheServiceExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Microsoft.Extensions.Caching.Memory; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Xunit; 12 | 13 | namespace Microsoft.Extensions.Caching.Distributed 14 | { 15 | public class CacheServiceExtensionsTests 16 | { 17 | [Fact] 18 | public void AddMemoryCache_RegistersMemoryCacheAsSingleton() 19 | { 20 | // Arrange 21 | var services = new ServiceCollection(); 22 | 23 | // Act 24 | services.AddMemoryCache(); 25 | 26 | // Assert 27 | var memoryCache = services.FirstOrDefault(desc => desc.ServiceType == typeof(IMemoryCache)); 28 | 29 | Assert.NotNull(memoryCache); 30 | Assert.Equal(ServiceLifetime.Singleton, memoryCache.Lifetime); 31 | } 32 | 33 | [Fact] 34 | public void AddDistributedMemoryCache_DoesntConflictWithMemoryCache() 35 | { 36 | // Arrange 37 | var services = new ServiceCollection(); 38 | services.AddDistributedMemoryCache(); 39 | services.AddMemoryCache(); 40 | 41 | var key = "key"; 42 | var memoryValue = "123"; 43 | var distributedValue = new byte[] { 1, 2, 3 }; 44 | 45 | // Act 46 | var serviceProvider = services.BuildServiceProvider(); 47 | var distributedCache = serviceProvider.GetService(); 48 | var memoryCache = serviceProvider.GetService(); 49 | memoryCache.Set(key, memoryValue); 50 | distributedCache.Set(key, distributedValue); 51 | 52 | // Assert 53 | 54 | Assert.Equal(memoryValue, memoryCache.Get(key)); 55 | Assert.Equal(distributedValue, distributedCache.Get(key)); 56 | } 57 | 58 | [Fact] 59 | public void AddCaching_DoesNotReplaceUserRegisteredServices() 60 | { 61 | // Arrange 62 | var services = new ServiceCollection(); 63 | services.AddScoped(); 64 | services.AddScoped(); 65 | 66 | // Act 67 | services.AddMemoryCache(); 68 | services.AddDistributedMemoryCache(); 69 | 70 | // Assert 71 | var serviceProvider = services.BuildServiceProvider(); 72 | var memoryCache = services.FirstOrDefault(desc => desc.ServiceType == typeof(IMemoryCache)); 73 | Assert.NotNull(memoryCache); 74 | Assert.Equal(ServiceLifetime.Scoped, memoryCache.Lifetime); 75 | Assert.IsType(serviceProvider.GetRequiredService()); 76 | 77 | var distributedCache = services.FirstOrDefault(desc => desc.ServiceType == typeof(IDistributedCache)); 78 | Assert.NotNull(distributedCache); 79 | Assert.Equal(ServiceLifetime.Scoped, memoryCache.Lifetime); 80 | Assert.IsType(serviceProvider.GetRequiredService()); 81 | } 82 | 83 | [Fact] 84 | public void AddMemoryCache_allows_chaining() 85 | { 86 | var services = new ServiceCollection(); 87 | 88 | Assert.Same(services, services.AddMemoryCache()); 89 | } 90 | 91 | [Fact] 92 | public void AddMemoryCache_with_action_allows_chaining() 93 | { 94 | var services = new ServiceCollection(); 95 | 96 | Assert.Same(services, services.AddMemoryCache(_ => { })); 97 | } 98 | 99 | [Fact] 100 | public void AddDistributedMemoryCache_allows_chaining() 101 | { 102 | var services = new ServiceCollection(); 103 | 104 | Assert.Same(services, services.AddDistributedMemoryCache()); 105 | } 106 | 107 | private class TestMemoryCache : IMemoryCache 108 | { 109 | public void Dispose() 110 | { 111 | throw new NotImplementedException(); 112 | } 113 | 114 | public void Remove(object key) 115 | { 116 | throw new NotImplementedException(); 117 | } 118 | 119 | public bool TryGetValue(object key, out object value) 120 | { 121 | throw new NotImplementedException(); 122 | } 123 | 124 | public ICacheEntry CreateEntry(object key) 125 | { 126 | throw new NotImplementedException(); 127 | } 128 | } 129 | 130 | private class TestDistributedCache : IDistributedCache 131 | { 132 | public void Connect() 133 | { 134 | throw new NotImplementedException(); 135 | } 136 | 137 | public Task ConnectAsync() 138 | { 139 | throw new NotImplementedException(); 140 | } 141 | 142 | public byte[] Get(string key) 143 | { 144 | throw new NotImplementedException(); 145 | } 146 | 147 | public Task GetAsync(string key, CancellationToken token = default(CancellationToken)) 148 | { 149 | throw new NotImplementedException(); 150 | } 151 | 152 | public void Refresh(string key) 153 | { 154 | throw new NotImplementedException(); 155 | } 156 | 157 | public Task RefreshAsync(string key, CancellationToken token = default(CancellationToken)) 158 | { 159 | throw new NotImplementedException(); 160 | } 161 | 162 | public void Remove(string key) 163 | { 164 | throw new NotImplementedException(); 165 | } 166 | 167 | public Task RemoveAsync(string key, CancellationToken token = default(CancellationToken)) 168 | { 169 | throw new NotImplementedException(); 170 | } 171 | 172 | public void Set(string key, byte[] value, DistributedCacheEntryOptions options) 173 | { 174 | throw new NotImplementedException(); 175 | } 176 | 177 | public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default(CancellationToken)) 178 | { 179 | throw new NotImplementedException(); 180 | } 181 | 182 | public bool TryGetValue(string key, out byte[] value) 183 | { 184 | throw new NotImplementedException(); 185 | } 186 | 187 | public Task TryGetValueAsync(string key, out byte[] value) 188 | { 189 | throw new NotImplementedException(); 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.Memory.Tests/CompactTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Internal; 6 | using Xunit; 7 | 8 | namespace Microsoft.Extensions.Caching.Memory 9 | { 10 | public class CompactTests 11 | { 12 | private MemoryCache CreateCache(ISystemClock clock = null) 13 | { 14 | return new MemoryCache(new MemoryCacheOptions() 15 | { 16 | Clock = clock, 17 | }); 18 | } 19 | 20 | [Fact] 21 | public void CompactEmptyNoOps() 22 | { 23 | var cache = CreateCache(); 24 | cache.Compact(0.10); 25 | } 26 | 27 | [Fact] 28 | public void Compact100PercentClearsAll() 29 | { 30 | var cache = CreateCache(); 31 | cache.Set("key1", "value1"); 32 | cache.Set("key2", "value2"); 33 | Assert.Equal(2, cache.Count); 34 | cache.Compact(1.0); 35 | Assert.Equal(0, cache.Count); 36 | } 37 | 38 | [Fact] 39 | public void Compact100PercentClearsAllButNeverRemoveItems() 40 | { 41 | var cache = CreateCache(); 42 | cache.Set("key1", "Value1", new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.NeverRemove)); 43 | cache.Set("key2", "Value2", new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.NeverRemove)); 44 | cache.Set("key3", "value3"); 45 | cache.Set("key4", "value4"); 46 | Assert.Equal(4, cache.Count); 47 | cache.Compact(1.0); 48 | Assert.Equal(2, cache.Count); 49 | Assert.Equal("Value1", cache.Get("key1")); 50 | Assert.Equal("Value2", cache.Get("key2")); 51 | } 52 | 53 | [Fact] 54 | public void CompactPrioritizesLowPriortyItems() 55 | { 56 | var cache = CreateCache(); 57 | cache.Set("key1", "Value1", new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.Low)); 58 | cache.Set("key2", "Value2", new MemoryCacheEntryOptions().SetPriority(CacheItemPriority.Low)); 59 | cache.Set("key3", "value3"); 60 | cache.Set("key4", "value4"); 61 | Assert.Equal(4, cache.Count); 62 | cache.Compact(0.5); 63 | Assert.Equal(2, cache.Count); 64 | Assert.Equal("value3", cache.Get("key3")); 65 | Assert.Equal("value4", cache.Get("key4")); 66 | } 67 | 68 | [Fact] 69 | public void CompactPrioritizesLRU() 70 | { 71 | var testClock = new TestClock(); 72 | var cache = CreateCache(testClock); 73 | cache.Set("key1", "value1"); 74 | testClock.Add(TimeSpan.FromSeconds(1)); 75 | cache.Set("key2", "value2"); 76 | testClock.Add(TimeSpan.FromSeconds(1)); 77 | cache.Set("key3", "value3"); 78 | testClock.Add(TimeSpan.FromSeconds(1)); 79 | cache.Set("key4", "value4"); 80 | Assert.Equal(4, cache.Count); 81 | cache.Compact(0.90); 82 | Assert.Equal(1, cache.Count); 83 | Assert.Equal("value4", cache.Get("key4")); 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.Memory.Tests/Infrastructure/TestClock.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Internal 7 | { 8 | public class TestClock : ISystemClock 9 | { 10 | public TestClock() 11 | { 12 | UtcNow = new DateTime(2013, 6, 15, 12, 34, 56, 789); 13 | } 14 | 15 | public DateTimeOffset UtcNow { get; set; } 16 | 17 | public void Add(TimeSpan timeSpan) 18 | { 19 | UtcNow = UtcNow + timeSpan; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.Memory.Tests/Infrastructure/TestExpirationToken.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.Extensions.Primitives; 6 | 7 | namespace Microsoft.Extensions.Caching.Memory.Infrastructure 8 | { 9 | internal class TestExpirationToken : IChangeToken 10 | { 11 | private bool _hasChanged; 12 | private bool _activeChangeCallbacks; 13 | 14 | public bool HasChanged 15 | { 16 | get 17 | { 18 | HasChangedWasCalled = true; 19 | return _hasChanged; 20 | } 21 | set 22 | { 23 | _hasChanged = value; 24 | } 25 | } 26 | 27 | public bool HasChangedWasCalled { get; set; } 28 | 29 | public bool ActiveChangeCallbacks 30 | { 31 | get 32 | { 33 | ActiveChangeCallbacksWasCalled = true; 34 | return _activeChangeCallbacks; 35 | } 36 | set 37 | { 38 | _activeChangeCallbacks = value; 39 | } 40 | } 41 | 42 | public bool ActiveChangeCallbacksWasCalled { get; set; } 43 | 44 | public TokenCallbackRegistration Registration { get; set; } 45 | 46 | public IDisposable RegisterChangeCallback(Action callback, object state) 47 | { 48 | Registration = new TokenCallbackRegistration() 49 | { 50 | RegisteredCallback = callback, 51 | RegisteredState = state, 52 | }; 53 | return Registration; 54 | } 55 | 56 | public void Fire() 57 | { 58 | HasChanged = true; 59 | if (Registration != null && !Registration.Disposed) 60 | { 61 | Registration.RegisteredCallback(Registration.RegisteredState); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.Memory.Tests/Infrastructure/TokenCallbackRegistration.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.Memory.Infrastructure 7 | { 8 | public class TokenCallbackRegistration : IDisposable 9 | { 10 | public Action RegisteredCallback { get; set; } 11 | 12 | public object RegisteredState { get; set; } 13 | 14 | public bool Disposed { get; set; } 15 | 16 | public void Dispose() 17 | { 18 | Disposed = true; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.Memory.Tests/Microsoft.Extensions.Caching.Memory.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(StandardTestTfms) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.SqlServer.Tests/CacheItemInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Caching.SqlServer 7 | { 8 | public class CacheItemInfo 9 | { 10 | public string Id { get; set; } 11 | 12 | public byte[] Value { get; set; } 13 | 14 | public DateTimeOffset ExpiresAtTime { get; set; } 15 | 16 | public TimeSpan? SlidingExpirationInSeconds { get; set; } 17 | 18 | public DateTimeOffset? AbsoluteExpiration { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.SqlServer.Tests/Microsoft.Extensions.Caching.SqlServer.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(StandardTestTfms) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.SqlServer.Tests/SqlServerCacheServicesExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using Microsoft.Extensions.Caching.Distributed; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Moq; 8 | using Xunit; 9 | 10 | namespace Microsoft.Extensions.Caching.SqlServer 11 | { 12 | public class SqlServerCacheServicesExtensionsTest 13 | { 14 | [Fact] 15 | public void AddDistributedSqlServerCache_AddsAsSingleRegistrationService() 16 | { 17 | // Arrange 18 | var services = new ServiceCollection(); 19 | 20 | // Act 21 | SqlServerCachingServicesExtensions.AddSqlServerCacheServices(services); 22 | 23 | // Assert 24 | var serviceDescriptor = Assert.Single(services); 25 | Assert.Equal(typeof(IDistributedCache), serviceDescriptor.ServiceType); 26 | Assert.Equal(typeof(SqlServerCache), serviceDescriptor.ImplementationType); 27 | Assert.Equal(ServiceLifetime.Singleton, serviceDescriptor.Lifetime); 28 | } 29 | 30 | [Fact] 31 | public void AddDistributedSqlServerCache_ReplacesPreviouslyUserRegisteredServices() 32 | { 33 | // Arrange 34 | var services = new ServiceCollection(); 35 | services.AddScoped(typeof(IDistributedCache), sp => Mock.Of()); 36 | 37 | // Act 38 | services.AddDistributedSqlServerCache(options => { 39 | options.ConnectionString = "Fake"; 40 | options.SchemaName = "Fake"; 41 | options.TableName = "Fake"; 42 | }); 43 | 44 | // Assert 45 | var serviceProvider = services.BuildServiceProvider(); 46 | 47 | var distributedCache = services.FirstOrDefault(desc => desc.ServiceType == typeof(IDistributedCache)); 48 | 49 | Assert.NotNull(distributedCache); 50 | Assert.Equal(ServiceLifetime.Scoped, distributedCache.Lifetime); 51 | Assert.IsType(serviceProvider.GetRequiredService()); 52 | } 53 | 54 | [Fact] 55 | public void AddDistributedSqlServerCache_allows_chaining() 56 | { 57 | var services = new ServiceCollection(); 58 | 59 | Assert.Same(services, services.AddDistributedSqlServerCache(_ => { })); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.SqlServer.Tests/TestClock.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Microsoft.Extensions.Internal 7 | { 8 | public class TestClock : ISystemClock 9 | { 10 | public TestClock() 11 | { 12 | // Examples: 13 | // DateTime.Now: 6/29/2015 1:20:40 PM 14 | // DateTime.UtcNow: 6/29/2015 8:20:40 PM 15 | // DateTimeOffset.Now: 6/29/2015 1:20:40 PM - 07:00 16 | // DateTimeOffset.UtcNow: 6/29/2015 8:20:40 PM + 00:00 17 | // DateTimeOffset.UtcDateTime: 6/29/2015 8:20:40 PM 18 | UtcNow = new DateTimeOffset(2013, 1, 1, 1, 0, 0, offset: TimeSpan.Zero); 19 | } 20 | 21 | public DateTimeOffset UtcNow { get; private set; } 22 | 23 | public TestClock Add(TimeSpan timeSpan) 24 | { 25 | UtcNow = UtcNow.Add(timeSpan); 26 | 27 | return this; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.SqlServer.Tests/TestOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | 3 | namespace Microsoft.Extensions.Caching.SqlServer 4 | { 5 | internal class TestSqlServerCacheOptions : IOptions 6 | { 7 | private readonly SqlServerCacheOptions _innerOptions; 8 | 9 | public TestSqlServerCacheOptions(SqlServerCacheOptions innerOptions) 10 | { 11 | _innerOptions = innerOptions; 12 | } 13 | 14 | public SqlServerCacheOptions Value 15 | { 16 | get 17 | { 18 | return _innerOptions; 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.SqlServer.Tests/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionString": "Server=localhost;Database=CacheTestDb;Trusted_Connection=True;", 3 | "SchemaName": "dbo", 4 | "TableName": "CacheTest" 5 | } -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.StackExchangeRedis.Tests/CacheServiceExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using Microsoft.Extensions.Caching.Distributed; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Moq; 11 | using Xunit; 12 | 13 | namespace Microsoft.Extensions.Caching.StackExchangeRedis 14 | { 15 | public class CacheServiceExtensionsTests 16 | { 17 | [Fact] 18 | public void AddStackExchangeRedisCache_RegistersDistributedCacheAsSingleton() 19 | { 20 | // Arrange 21 | var services = new ServiceCollection(); 22 | 23 | // Act 24 | services.AddStackExchangeRedisCache(options => { }); 25 | 26 | // Assert 27 | var distributedCache = services.FirstOrDefault(desc => desc.ServiceType == typeof(IDistributedCache)); 28 | 29 | Assert.NotNull(distributedCache); 30 | Assert.Equal(ServiceLifetime.Singleton, distributedCache.Lifetime); 31 | } 32 | 33 | [Fact] 34 | public void AddStackExchangeRedisCache_ReplacesPreviouslyUserRegisteredServices() 35 | { 36 | // Arrange 37 | var services = new ServiceCollection(); 38 | services.AddScoped(typeof(IDistributedCache), sp => Mock.Of()); 39 | 40 | // Act 41 | services.AddStackExchangeRedisCache(options => { }); 42 | 43 | // Assert 44 | var serviceProvider = services.BuildServiceProvider(); 45 | 46 | var distributedCache = services.FirstOrDefault(desc => desc.ServiceType == typeof(IDistributedCache)); 47 | 48 | Assert.NotNull(distributedCache); 49 | Assert.Equal(ServiceLifetime.Scoped, distributedCache.Lifetime); 50 | Assert.IsType(serviceProvider.GetRequiredService()); 51 | } 52 | 53 | [Fact] 54 | public void AddStackExchangeRedisCache_allows_chaining() 55 | { 56 | var services = new ServiceCollection(); 57 | 58 | Assert.Same(services, services.AddStackExchangeRedisCache(_ => { })); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.StackExchangeRedis.Tests/Infrastructure/RedisTestConfig.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Threading; 9 | using Microsoft.Extensions.Caching.Distributed; 10 | 11 | namespace Microsoft.Extensions.Caching.StackExchangeRedis 12 | { 13 | public static class RedisTestConfig 14 | { 15 | internal const string RedisServerExeName = "redis-server.exe"; 16 | internal const string FunctionalTestsRedisServerExeName = "RedisFuncTests-redis-server"; 17 | internal const string UserProfileRedisNugetPackageServerPath = @".dotnet\packages\Redis-64\2.8.9"; 18 | internal const string CIMachineRedisNugetPackageServerPath = @"Redis-64\2.8.9"; 19 | 20 | private static volatile Process _redisServerProcess; // null implies if server exists it was not started by this code 21 | private static readonly object _redisServerProcessLock = new object(); 22 | public static int RedisPort = 6379; // override default so that do not interfere with anyone else's server 23 | 24 | public static IDistributedCache CreateCacheInstance(string instanceName) 25 | { 26 | return new RedisCache(new RedisCacheOptions() 27 | { 28 | Configuration = "localhost:" + RedisPort, 29 | InstanceName = instanceName, 30 | }); 31 | } 32 | 33 | public static void GetOrStartServer() 34 | { 35 | if (UserHasStartedOwnRedisServer()) 36 | { 37 | // user claims they have started their own 38 | return; 39 | } 40 | 41 | if (AlreadyOwnRunningRedisServer()) 42 | { 43 | return; 44 | } 45 | 46 | TryConnectToOrStartServer(); 47 | } 48 | 49 | private static bool AlreadyOwnRunningRedisServer() 50 | { 51 | // Does RedisTestConfig already know about a running server? 52 | if (_redisServerProcess != null 53 | && !_redisServerProcess.HasExited) 54 | { 55 | return true; 56 | } 57 | 58 | return false; 59 | } 60 | 61 | private static bool TryConnectToOrStartServer() 62 | { 63 | if (CanFindExistingRedisServer()) 64 | { 65 | return true; 66 | } 67 | 68 | return TryStartRedisServer(); 69 | } 70 | 71 | public static void StopRedisServer() 72 | { 73 | if (UserHasStartedOwnRedisServer()) 74 | { 75 | // user claims they have started their own - they are responsible for stopping it 76 | return; 77 | } 78 | 79 | if (CanFindExistingRedisServer()) 80 | { 81 | lock (_redisServerProcessLock) 82 | { 83 | if (_redisServerProcess != null) 84 | { 85 | _redisServerProcess.Kill(); 86 | _redisServerProcess = null; 87 | } 88 | } 89 | } 90 | } 91 | 92 | private static bool CanFindExistingRedisServer() 93 | { 94 | var process = Process.GetProcessesByName(FunctionalTestsRedisServerExeName).SingleOrDefault(); 95 | if (process == null || process.HasExited) 96 | { 97 | lock (_redisServerProcessLock) 98 | { 99 | _redisServerProcess = null; 100 | } 101 | return false; 102 | } 103 | 104 | lock (_redisServerProcessLock) 105 | { 106 | _redisServerProcess = process; 107 | } 108 | return true; 109 | } 110 | 111 | private static bool TryStartRedisServer() 112 | { 113 | var serverPath = GetUserProfileServerPath(); 114 | if (!File.Exists(serverPath)) 115 | { 116 | serverPath = GetCIMachineServerPath(); 117 | if (!File.Exists(serverPath)) 118 | { 119 | throw new Exception("Could not find " + RedisServerExeName + 120 | " at path " + GetUserProfileServerPath() + " nor at " + GetCIMachineServerPath()); 121 | } 122 | } 123 | 124 | return RunServer(serverPath); 125 | } 126 | 127 | public static bool UserHasStartedOwnRedisServer() 128 | { 129 | // if the user sets this environment variable they are claiming they've started 130 | // their own Redis Server and are responsible for starting/stopping it 131 | return (Environment.GetEnvironmentVariable("STARTED_OWN_REDIS_SERVER") != null); 132 | } 133 | 134 | public static string GetUserProfileServerPath() 135 | { 136 | var configFilePath = Environment.GetEnvironmentVariable("USERPROFILE"); 137 | return Path.Combine(configFilePath, UserProfileRedisNugetPackageServerPath, RedisServerExeName); 138 | } 139 | 140 | public static string GetCIMachineServerPath() 141 | { 142 | var configFilePath = Environment.GetEnvironmentVariable("DOTNET_PACKAGES"); 143 | return Path.Combine(configFilePath, CIMachineRedisNugetPackageServerPath, RedisServerExeName); 144 | } 145 | 146 | private static bool RunServer(string serverExePath) 147 | { 148 | if (_redisServerProcess == null) 149 | { 150 | lock (_redisServerProcessLock) 151 | { 152 | // copy the redis-server.exe to a directory under the user's TMP path under a different 153 | // name - so we know the difference between a redis-server started by us and a redis-server 154 | // which the customer already has running. 155 | var tempPath = Path.GetTempPath(); 156 | var tempRedisServerFullPath = 157 | Path.Combine(tempPath, FunctionalTestsRedisServerExeName + ".exe"); 158 | if (!File.Exists(tempRedisServerFullPath)) 159 | { 160 | File.Copy(serverExePath, tempRedisServerFullPath); 161 | } 162 | 163 | if (_redisServerProcess == null) 164 | { 165 | var serverArgs = "--port " + RedisPort + " --maxheap 512MB"; 166 | var processInfo = new ProcessStartInfo 167 | { 168 | // start the process in users TMP dir (a .dat file will be created but will be removed when the server dies) 169 | Arguments = serverArgs, 170 | WorkingDirectory = tempPath, 171 | CreateNoWindow = true, 172 | FileName = tempRedisServerFullPath, 173 | RedirectStandardError = true, 174 | RedirectStandardOutput = true, 175 | UseShellExecute = false, 176 | }; 177 | try 178 | { 179 | _redisServerProcess = Process.Start(processInfo); 180 | Thread.Sleep(3000); // to give server time to initialize 181 | 182 | if (_redisServerProcess.HasExited) 183 | { 184 | throw new Exception("Could not start Redis Server at path " 185 | + tempRedisServerFullPath + " with Arguments '" + serverArgs + "', working dir = " + tempPath + Environment.NewLine 186 | + _redisServerProcess.StandardError.ReadToEnd() + Environment.NewLine 187 | + _redisServerProcess.StandardOutput.ReadToEnd()); 188 | } 189 | } 190 | catch (Exception e) 191 | { 192 | throw new Exception("Could not start Redis Server at path " 193 | + tempRedisServerFullPath + " with Arguments '" + serverArgs + "', working dir = " + tempPath, e); 194 | } 195 | 196 | if (_redisServerProcess == null) 197 | { 198 | throw new Exception("Got null process trying to start Redis Server at path " 199 | + tempRedisServerFullPath + " with Arguments '" + serverArgs + "', working dir = " + tempPath); 200 | } 201 | } 202 | } 203 | } 204 | 205 | return true; 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.StackExchangeRedis.Tests/Infrastructure/RedisXunitTestExecutor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | //using System; 5 | //using System.Reflection; 6 | //using Xunit.Abstractions; 7 | //using Xunit.Sdk; 8 | 9 | //namespace Microsoft.Extensions.Caching.StackExchangeRedis 10 | //{ 11 | // TODO - should replace this whole approach with a CollectionFixture when 12 | // Xunit CollectionFixtures are working correctly. 13 | //public class RedisXunitTestExecutor : XunitTestFrameworkExecutor, IDisposable 14 | //{ 15 | // private bool _isDisposed; 16 | 17 | // public RedisXunitTestExecutor( 18 | // AssemblyName assemblyName, ISourceInformationProvider sourceInformationProvider) 19 | // : base(assemblyName, sourceInformationProvider) 20 | // { 21 | // try 22 | // { 23 | // RedisTestConfig.GetOrStartServer(); 24 | // } 25 | // catch (Exception) 26 | // { 27 | // // do not let exceptions starting server prevent XunitTestFrameworkExecutor from being created 28 | // } 29 | // } 30 | 31 | // ~RedisXunitTestExecutor() 32 | // { 33 | // Dispose(false); 34 | // } 35 | 36 | // void IDisposable.Dispose() 37 | // { 38 | // Dispose(true); 39 | // GC.SuppressFinalize(this); 40 | // } 41 | 42 | // protected virtual void Dispose(bool disposing) 43 | // { 44 | // if (!_isDisposed) 45 | // { 46 | // try 47 | // { 48 | // RedisTestConfig.StopRedisServer(); 49 | // } 50 | // catch (Exception) 51 | // { 52 | // // do not let exceptions stopping server prevent XunitTestFrameworkExecutor from being disposed 53 | // } 54 | 55 | // _isDisposed = true; 56 | // } 57 | // } 58 | //} 59 | //} 60 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.StackExchangeRedis.Tests/Infrastructure/RedisXunitTestFramework.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | //using System.Reflection; 5 | //using Xunit; 6 | //using Xunit.Abstractions; 7 | //using Xunit.Sdk; 8 | 9 | // TODO: Disabled due to CI failures. [assembly: TestFramework("Microsoft.Extensions.Cache.Redis.RedisXunitTestFramework", "Microsoft.Extensions.Cache.StackExchangeRedis.Tests")] 10 | 11 | //namespace Microsoft.Extensions.Caching.StackExchangeRedis 12 | //{ 13 | // public class RedisXunitTestFramework : XunitTestFramework 14 | // { 15 | // protected override ITestFrameworkExecutor CreateExecutor(AssemblyName assemblyName) 16 | // { 17 | // return new RedisXunitTestExecutor(assemblyName, SourceInformationProvider); 18 | // } 19 | // } 20 | //} 21 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.StackExchangeRedis.Tests/Microsoft.Extensions.Caching.StackExchangeRedis.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(StandardTestTfms) 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/Microsoft.Extensions.Caching.StackExchangeRedis.Tests/RedisCacheSetAndRemoveTests.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Threading; 6 | using Microsoft.Extensions.Caching.Distributed; 7 | using Xunit; 8 | 9 | namespace Microsoft.Extensions.Caching.StackExchangeRedis 10 | { 11 | public class RedisCacheSetAndRemoveTests 12 | { 13 | private const string SkipReason = "TODO: Disabled due to CI failure. " + 14 | "These tests require Redis server to be started on the machine. Make sure to change the value of" + 15 | "\"RedisTestConfig.RedisPort\" accordingly."; 16 | 17 | [Fact(Skip = SkipReason)] 18 | public void GetMissingKeyReturnsNull() 19 | { 20 | var cache = RedisTestConfig.CreateCacheInstance(GetType().Name); 21 | string key = "non-existent-key"; 22 | 23 | var result = cache.Get(key); 24 | Assert.Null(result); 25 | } 26 | 27 | [Fact(Skip = SkipReason)] 28 | public void SetAndGetReturnsObject() 29 | { 30 | var cache = RedisTestConfig.CreateCacheInstance(GetType().Name); 31 | var value = new byte[1]; 32 | string key = "myKey"; 33 | 34 | cache.Set(key, value); 35 | 36 | var result = cache.Get(key); 37 | Assert.Equal(value, result); 38 | } 39 | 40 | [Fact(Skip = SkipReason)] 41 | public void SetAndGetWorksWithCaseSensitiveKeys() 42 | { 43 | var cache = RedisTestConfig.CreateCacheInstance(GetType().Name); 44 | var value = new byte[1]; 45 | string key1 = "myKey"; 46 | string key2 = "Mykey"; 47 | 48 | cache.Set(key1, value); 49 | 50 | var result = cache.Get(key1); 51 | Assert.Equal(value, result); 52 | 53 | result = cache.Get(key2); 54 | Assert.Null(result); 55 | } 56 | 57 | [Fact(Skip = SkipReason)] 58 | public void SetAlwaysOverwrites() 59 | { 60 | var cache = RedisTestConfig.CreateCacheInstance(GetType().Name); 61 | var value1 = new byte[1] { 1 }; 62 | string key = "myKey"; 63 | 64 | cache.Set(key, value1); 65 | var result = cache.Get(key); 66 | Assert.Equal(value1, result); 67 | 68 | var value2 = new byte[1] { 2 }; 69 | cache.Set(key, value2); 70 | result = cache.Get(key); 71 | Assert.Equal(value2, result); 72 | } 73 | 74 | [Fact(Skip = SkipReason)] 75 | public void RemoveRemoves() 76 | { 77 | var cache = RedisTestConfig.CreateCacheInstance(GetType().Name); 78 | var value = new byte[1]; 79 | string key = "myKey"; 80 | 81 | cache.Set(key, value); 82 | var result = cache.Get(key); 83 | Assert.Equal(value, result); 84 | 85 | cache.Remove(key); 86 | result = cache.Get(key); 87 | Assert.Null(result); 88 | } 89 | 90 | [Fact(Skip = SkipReason)] 91 | public void SetNullValueThrows() 92 | { 93 | var cache = RedisTestConfig.CreateCacheInstance(GetType().Name); 94 | byte[] value = null; 95 | string key = "myKey"; 96 | 97 | Assert.Throws(() => cache.Set(key, value)); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /version.props: -------------------------------------------------------------------------------- 1 |  2 | 3 | 3.0.0 4 | alpha1 5 | $(VersionPrefix) 6 | $(VersionPrefix)-$(VersionSuffix)-final 7 | t000 8 | a- 9 | $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) 10 | $(VersionSuffix)-$(BuildNumber) 11 | 12 | 13 | --------------------------------------------------------------------------------