IResponseCache adapter and support for vary by header
This commit is contained in:
parent
8e7eb5d99e
commit
a40cc88169
|
|
@ -15,7 +15,7 @@ namespace ResponseCachingSample
|
|||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMemoryCache();
|
||||
services.AddDistributedResponseCache();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
|
|
@ -23,11 +23,14 @@ namespace ResponseCachingSample
|
|||
app.UseResponseCaching();
|
||||
app.Run(async (context) =>
|
||||
{
|
||||
// These settings should be configured by context.Response.Cache.*
|
||||
context.Response.GetTypedHeaders().CacheControl = new CacheControlHeaderValue()
|
||||
{
|
||||
Public = true,
|
||||
MaxAge = TimeSpan.FromSeconds(10)
|
||||
MaxAge = TimeSpan.FromSeconds(10)
|
||||
};
|
||||
context.Response.Headers["Vary"] = new string[] { "Accept-Encoding", "Non-Existent" };
|
||||
|
||||
await context.Response.WriteAsync("Hello World! " + DateTime.UtcNow);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching
|
||||
{
|
||||
public interface IResponseCache
|
||||
{
|
||||
object Get(string key);
|
||||
// TODO: Set expiry policy in the underlying cache?
|
||||
void Set(string key, object entry);
|
||||
void Remove(string key);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,14 @@
|
|||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||
{
|
||||
internal class ResponseCachingEntry
|
||||
internal class CachedResponse
|
||||
{
|
||||
public int StatusCode { get; set; }
|
||||
internal int StatusCode { get; set; }
|
||||
|
||||
internal IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
|
||||
|
||||
internal byte[] Body { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||
{
|
||||
internal class CachedVaryBy
|
||||
{
|
||||
internal StringValues Headers { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||
{
|
||||
internal static class DefaultResponseCacheSerializer
|
||||
{
|
||||
private const int FormatVersion = 1;
|
||||
|
||||
public static object Deserialize(byte[] serializedEntry)
|
||||
{
|
||||
using (var memory = new MemoryStream(serializedEntry))
|
||||
{
|
||||
using (var reader = new BinaryReader(memory))
|
||||
{
|
||||
return Read(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Serialize(object entry)
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
using (var writer = new BinaryWriter(memory))
|
||||
{
|
||||
Write(writer, entry);
|
||||
writer.Flush();
|
||||
return memory.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serialization Format
|
||||
// Format version (int)
|
||||
// Type (string)
|
||||
// Type-dependent data (see CachedResponse and CachedVaryBy)
|
||||
public static object Read(BinaryReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(reader));
|
||||
}
|
||||
|
||||
if (reader.ReadInt32() != FormatVersion)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var type = reader.ReadString();
|
||||
|
||||
if (string.Equals(nameof(CachedResponse), type))
|
||||
{
|
||||
var cachedResponse = ReadCachedResponse(reader);
|
||||
return cachedResponse;
|
||||
}
|
||||
else if (string.Equals(nameof(CachedVaryBy), type))
|
||||
{
|
||||
var cachedResponse = ReadCachedVaryBy(reader);
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
// Unable to read as CachedResponse or CachedVaryBy
|
||||
return null;
|
||||
}
|
||||
|
||||
// Serialization Format
|
||||
// Status code (int)
|
||||
// Header count (int)
|
||||
// Header(s)
|
||||
// Key (string)
|
||||
// Value (string)
|
||||
// Body length (int)
|
||||
// Body (byte[])
|
||||
private static CachedResponse ReadCachedResponse(BinaryReader reader)
|
||||
{
|
||||
var statusCode = reader.ReadInt32();
|
||||
var headerCount = reader.ReadInt32();
|
||||
var headers = new HeaderDictionary();
|
||||
for (var index = 0; index < headerCount; index++)
|
||||
{
|
||||
var key = reader.ReadString();
|
||||
var value = reader.ReadString();
|
||||
headers[key] = value;
|
||||
}
|
||||
var bodyLength = reader.ReadInt32();
|
||||
var body = reader.ReadBytes(bodyLength);
|
||||
|
||||
return new CachedResponse { StatusCode = statusCode, Headers = headers, Body = body };
|
||||
}
|
||||
|
||||
// Serialization Format
|
||||
// Headers (comma separated string)
|
||||
private static CachedVaryBy ReadCachedVaryBy(BinaryReader reader)
|
||||
{
|
||||
var headers = reader.ReadString().Split(',');
|
||||
|
||||
return new CachedVaryBy { Headers = headers };
|
||||
}
|
||||
|
||||
// See serialization format above
|
||||
public static void Write(BinaryWriter writer, object entry)
|
||||
{
|
||||
if (writer == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(writer));
|
||||
}
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entry));
|
||||
}
|
||||
|
||||
writer.Write(FormatVersion);
|
||||
|
||||
if (entry is CachedResponse)
|
||||
{
|
||||
WriteCachedResponse(writer, entry as CachedResponse);
|
||||
}
|
||||
else if (entry is CachedVaryBy)
|
||||
{
|
||||
WriteCachedVaryBy(writer, entry as CachedVaryBy);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException($"Unrecognized entry format for {nameof(entry)}.");
|
||||
}
|
||||
}
|
||||
|
||||
// See serialization format above
|
||||
private static void WriteCachedResponse(BinaryWriter writer, CachedResponse entry)
|
||||
{
|
||||
writer.Write(nameof(CachedResponse));
|
||||
writer.Write(entry.StatusCode);
|
||||
writer.Write(entry.Headers.Count);
|
||||
foreach (var header in entry.Headers)
|
||||
{
|
||||
writer.Write(header.Key);
|
||||
writer.Write(header.Value);
|
||||
}
|
||||
|
||||
writer.Write(entry.Body.Length);
|
||||
writer.Write(entry.Body);
|
||||
}
|
||||
|
||||
// See serialization format above
|
||||
private static void WriteCachedVaryBy(BinaryWriter writer, CachedVaryBy entry)
|
||||
{
|
||||
writer.Write(nameof(CachedVaryBy));
|
||||
writer.Write(entry.Headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||
{
|
||||
internal class DistributedResponseCache : IResponseCache
|
||||
{
|
||||
private readonly IDistributedCache _cache;
|
||||
|
||||
public DistributedResponseCache(IDistributedCache cache)
|
||||
{
|
||||
if (cache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cache));
|
||||
}
|
||||
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public object Get(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DefaultResponseCacheSerializer.Deserialize(_cache.Get(key));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: Log error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cache.Remove(key);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: Log error
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(string key, object entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cache.Set(key, DefaultResponseCacheSerializer.Serialize(entry));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: Log error
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Internal
|
||||
{
|
||||
internal class MemoryResponseCache : IResponseCache
|
||||
{
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public MemoryResponseCache(IMemoryCache cache)
|
||||
{
|
||||
if (cache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cache));
|
||||
}
|
||||
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public object Get(string key)
|
||||
{
|
||||
return _cache.Get(key);
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
_cache.Remove(key);
|
||||
}
|
||||
|
||||
public void Set(string key, object entry)
|
||||
{
|
||||
_cache.Set(key, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,11 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.AspNetCore.ResponseCaching.Internal;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching
|
||||
{
|
||||
|
|
@ -13,15 +15,25 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
{
|
||||
private string _cacheKey;
|
||||
|
||||
public ResponseCachingContext(HttpContext httpContext, IMemoryCache cache)
|
||||
public ResponseCachingContext(HttpContext httpContext, IResponseCache cache)
|
||||
{
|
||||
if (cache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cache));
|
||||
}
|
||||
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(httpContext));
|
||||
}
|
||||
|
||||
HttpContext = httpContext;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
private HttpContext HttpContext { get; }
|
||||
|
||||
private IMemoryCache Cache { get; }
|
||||
private IResponseCache Cache { get; }
|
||||
|
||||
private Stream OriginalResponseStream { get; set; }
|
||||
|
||||
|
|
@ -46,38 +58,81 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
}
|
||||
|
||||
// Only QueryString is treated as case sensitive
|
||||
// GET;HTTP://MYDOMAIN.COM:80/PATHBASE/PATH?QueryString
|
||||
// GET;/PATH;VaryBy
|
||||
private string CreateCacheKey()
|
||||
{
|
||||
return CreateCacheKey(varyBy: null);
|
||||
}
|
||||
|
||||
private string CreateCacheKey(CachedVaryBy varyBy)
|
||||
{
|
||||
var request = HttpContext.Request;
|
||||
return request.Method.ToUpperInvariant()
|
||||
+ ";"
|
||||
+ request.Scheme.ToUpperInvariant()
|
||||
+ "://"
|
||||
+ request.Host.Value.ToUpperInvariant()
|
||||
+ request.PathBase.Value.ToUpperInvariant()
|
||||
+ request.Path.Value.ToUpperInvariant()
|
||||
+ request.QueryString;
|
||||
var builder = new StringBuilder()
|
||||
.Append(request.Method.ToUpperInvariant())
|
||||
.Append(";")
|
||||
.Append(request.Path.Value.ToUpperInvariant())
|
||||
.Append(CreateVaryByCacheKey(varyBy));
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private string CreateVaryByCacheKey(CachedVaryBy varyBy)
|
||||
{
|
||||
// TODO: resolve key format and delimiters
|
||||
if (varyBy == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var request = HttpContext.Request;
|
||||
var builder = new StringBuilder(";");
|
||||
|
||||
foreach (var header in varyBy.Headers)
|
||||
{
|
||||
var value = request.Headers[header].ToString();
|
||||
// null vs Empty?
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
value = "null";
|
||||
}
|
||||
|
||||
builder.Append(header)
|
||||
.Append("=")
|
||||
.Append(value)
|
||||
.Append(";");
|
||||
}
|
||||
|
||||
// Parse querystring params
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal async Task<bool> TryServeFromCacheAsync()
|
||||
{
|
||||
_cacheKey = CreateCacheKey();
|
||||
ResponseCachingEntry cacheEntry;
|
||||
if (Cache.TryGetValue(_cacheKey, out cacheEntry))
|
||||
var cacheEntry = Cache.Get(_cacheKey);
|
||||
|
||||
if (cacheEntry is CachedVaryBy)
|
||||
{
|
||||
// Request contains VaryBy rules, recompute key and try again
|
||||
_cacheKey = CreateCacheKey(cacheEntry as CachedVaryBy);
|
||||
cacheEntry = Cache.Get(_cacheKey);
|
||||
}
|
||||
|
||||
if (cacheEntry is CachedResponse)
|
||||
{
|
||||
// TODO: Compare cached request headers
|
||||
|
||||
// TODO: Evaluate Vary-By and select the most appropriate response
|
||||
|
||||
// TODO: Content negotiation if there are multiple cached response formats?
|
||||
|
||||
// TODO: Verify content freshness, or else re-validate the data?
|
||||
|
||||
var cachedResponse = cacheEntry as CachedResponse;
|
||||
|
||||
var response = HttpContext.Response;
|
||||
// Copy the cached status code and response headers
|
||||
response.StatusCode = cacheEntry.StatusCode;
|
||||
foreach (var pair in cacheEntry.Headers)
|
||||
response.StatusCode = cachedResponse.StatusCode;
|
||||
foreach (var pair in cachedResponse.Headers)
|
||||
{
|
||||
response.Headers[pair.Key] = pair.Value;
|
||||
}
|
||||
|
|
@ -86,7 +141,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
response.Headers["Served_From_Cache"] = DateTime.Now.ToString();
|
||||
|
||||
// Copy the cached response body
|
||||
var body = cacheEntry.Body;
|
||||
var body = cachedResponse.Body;
|
||||
if (body.Length > 0)
|
||||
{
|
||||
await response.Body.WriteAsync(body, 0, body.Length);
|
||||
|
|
@ -120,15 +175,34 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
{
|
||||
if (CacheResponse)
|
||||
{
|
||||
// Store the buffer to cache
|
||||
var cacheEntry = new ResponseCachingEntry();
|
||||
cacheEntry.StatusCode = HttpContext.Response.StatusCode;
|
||||
var response = HttpContext.Response;
|
||||
var varyHeaderValue = response.Headers["Vary"];
|
||||
|
||||
// Check if any VaryBy rules exist
|
||||
if (!StringValues.IsNullOrEmpty(varyHeaderValue))
|
||||
{
|
||||
var cachedVaryBy = new CachedVaryBy
|
||||
{
|
||||
// Only vary by headers for now
|
||||
Headers = varyHeaderValue
|
||||
};
|
||||
|
||||
Cache.Set(_cacheKey, cachedVaryBy);
|
||||
_cacheKey = CreateCacheKey(cachedVaryBy);
|
||||
}
|
||||
|
||||
// Store the response to cache
|
||||
var cachedResponse = new CachedResponse
|
||||
{
|
||||
StatusCode = HttpContext.Response.StatusCode,
|
||||
Body = Buffer.ToArray()
|
||||
};
|
||||
foreach (var pair in HttpContext.Response.Headers)
|
||||
{
|
||||
cacheEntry.Headers[pair.Key] = pair.Value;
|
||||
cachedResponse.Headers[pair.Key] = pair.Value;
|
||||
}
|
||||
cacheEntry.Body = Buffer.ToArray();
|
||||
Cache.Set(_cacheKey, cacheEntry); // TODO: Timeouts
|
||||
|
||||
Cache.Set(_cacheKey, cachedResponse); // TODO: Timeouts
|
||||
}
|
||||
|
||||
// TODO: TEMP, flush the buffer to the client
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching
|
||||
{
|
||||
|
|
@ -11,10 +11,20 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
public class ResponseCachingMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly IResponseCache _cache;
|
||||
|
||||
public ResponseCachingMiddleware(RequestDelegate next, IMemoryCache cache)
|
||||
public ResponseCachingMiddleware(RequestDelegate next, IResponseCache cache)
|
||||
{
|
||||
if (cache == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(cache));
|
||||
}
|
||||
|
||||
if (next == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
_next = next;
|
||||
_cache = cache;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.ResponseCaching;
|
||||
using Microsoft.AspNetCore.ResponseCaching.Internal;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
public static class ResponseCachingServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddMemoryResponseCache(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.AddMemoryCache();
|
||||
services.TryAdd(ServiceDescriptor.Singleton<IResponseCache, MemoryResponseCache>());
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddDistributedResponseCache(this IServiceCollection services)
|
||||
{
|
||||
if (services == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
services.AddDistributedMemoryCache();
|
||||
services.TryAdd(ServiceDescriptor.Singleton<IResponseCache, DistributedResponseCache>());
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNetCore.Http": "1.1.0-*",
|
||||
"Microsoft.Extensions.Caching.Abstractions": "1.1.0-*"
|
||||
"Microsoft.Extensions.Caching.Memory": "1.1.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
"net451": {},
|
||||
|
|
|
|||
|
|
@ -14,5 +14,8 @@
|
|||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||
{
|
||||
public class ResponseCachingContextTests
|
||||
{
|
||||
|
|
@ -14,7 +15,7 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Method = "GET";
|
||||
var context = new ResponseCachingContext(httpContext, new MemoryCache(new MemoryCacheOptions()));
|
||||
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
|
||||
|
||||
Assert.True(context.CheckRequestAllowsCaching());
|
||||
}
|
||||
|
|
@ -25,9 +26,25 @@ namespace Microsoft.AspNetCore.ResponseCaching
|
|||
{
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.Request.Method = method;
|
||||
var context = new ResponseCachingContext(httpContext, new MemoryCache(new MemoryCacheOptions()));
|
||||
var context = new ResponseCachingContext(httpContext, new TestResponseCache());
|
||||
|
||||
Assert.False(context.CheckRequestAllowsCaching());
|
||||
}
|
||||
|
||||
private class TestResponseCache : IResponseCache
|
||||
{
|
||||
public object Get(string key)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Remove(string key)
|
||||
{
|
||||
}
|
||||
|
||||
public void Set(string key, object entry)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.ResponseCaching.Tests
|
||||
{
|
||||
public class ResponseCachingTests
|
||||
{
|
||||
[Fact]
|
||||
public async void ServesCachedContentIfAvailable()
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.ConfigureServices(services =>
|
||||
{
|
||||
services.AddDistributedResponseCache();
|
||||
})
|
||||
.Configure(app =>
|
||||
{
|
||||
app.UseResponseCaching();
|
||||
app.Run(async (context) =>
|
||||
{
|
||||
context.Response.Headers["Cache-Control"] = "public";
|
||||
await context.Response.WriteAsync(DateTime.UtcNow.ToString());
|
||||
});
|
||||
});
|
||||
|
||||
using (var server = new TestServer(builder))
|
||||
{
|
||||
var client = server.CreateClient();
|
||||
var initialResponse = await client.GetAsync("");
|
||||
var subsequentResponse = await client.GetAsync("");
|
||||
|
||||
initialResponse.EnsureSuccessStatusCode();
|
||||
subsequentResponse.EnsureSuccessStatusCode();
|
||||
|
||||
// TODO: Check for the appropriate headers once we actually set them
|
||||
Assert.False(initialResponse.Headers.Contains("Served_From_Cache"));
|
||||
Assert.True(subsequentResponse.Headers.Contains("Served_From_Cache"));
|
||||
Assert.Equal(await initialResponse.Content.ReadAsStringAsync(), await subsequentResponse.Content.ReadAsStringAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"dotnet-test-xunit": "2.2.0-*",
|
||||
"Microsoft.AspNetCore.Http": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.ResponseCaching": "0.1.0-*",
|
||||
"Microsoft.Extensions.Caching.Memory": "1.1.0-*",
|
||||
"Microsoft.AspNetCore.TestHost": "1.1.0-*",
|
||||
"xunit": "2.2.0-*"
|
||||
},
|
||||
"frameworks": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue