diff --git a/ResponseCaching.sln b/ResponseCaching.sln index c052708fa0..43b8d0f0b8 100644 --- a/ResponseCaching.sln +++ b/ResponseCaching.sln @@ -20,6 +20,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.Respon EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.ResponseCaching", "src\Microsoft.AspNetCore.ResponseCaching\Microsoft.AspNetCore.ResponseCaching.xproj", "{D1031270-DBD3-4F02-A3DC-3E7DADE8EBE6}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNetCore.ResponseCaching.Abstractions", "src\Microsoft.AspNetCore.ResponseCaching.Abstractions\Microsoft.AspNetCore.ResponseCaching.Abstractions.xproj", "{2D1022E8-CBB6-478D-A420-CB888D0EF7B7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -38,6 +40,10 @@ Global {D1031270-DBD3-4F02-A3DC-3E7DADE8EBE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1031270-DBD3-4F02-A3DC-3E7DADE8EBE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1031270-DBD3-4F02-A3DC-3E7DADE8EBE6}.Release|Any CPU.Build.0 = Release|Any CPU + {2D1022E8-CBB6-478D-A420-CB888D0EF7B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D1022E8-CBB6-478D-A420-CB888D0EF7B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D1022E8-CBB6-478D-A420-CB888D0EF7B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D1022E8-CBB6-478D-A420-CB888D0EF7B7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -46,5 +52,6 @@ Global {1139BDEE-FA15-474D-8855-0AB91F23CF26} = {C51DF5BD-B53D-4795-BC01-A9AB066BF286} {151B2027-3936-44B9-A4A0-E1E5902125AB} = {89A50974-E9D4-4F87-ACF2-6A6005E64931} {D1031270-DBD3-4F02-A3DC-3E7DADE8EBE6} = {367AABAF-E03C-4491-A9A7-BDDE8903D1B4} + {2D1022E8-CBB6-478D-A420-CB888D0EF7B7} = {367AABAF-E03C-4491-A9A7-BDDE8903D1B4} EndGlobalSection EndGlobal diff --git a/samples/ResponseCachingSample/project.json b/samples/ResponseCachingSample/project.json index 3ddb9bb33c..8fa43edb22 100644 --- a/samples/ResponseCachingSample/project.json +++ b/samples/ResponseCachingSample/project.json @@ -1,7 +1,7 @@ { "version": "1.1.0-*", "dependencies": { - "Microsoft.AspNetCore.ResponseCaching": "0.1.0-*", + "Microsoft.AspNetCore.ResponseCaching": "1.0.0-*", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0-*", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0-*", "Microsoft.Extensions.Caching.Memory": "1.1.0-*" diff --git a/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/IResponseCacheFeature.cs b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/IResponseCacheFeature.cs new file mode 100644 index 0000000000..2306c410f8 --- /dev/null +++ b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/IResponseCacheFeature.cs @@ -0,0 +1,18 @@ +// 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 +{ + /// + /// A feature for configuring additional response cache options on the HTTP response. + /// + public interface IResponseCacheFeature + { + /// + /// Gets or sets the query keys used by the response cache middleware for calculating secondary vary keys. + /// + StringValues VaryByQueryKeys { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/Microsoft.AspNetCore.ResponseCaching.Abstractions.xproj b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/Microsoft.AspNetCore.ResponseCaching.Abstractions.xproj new file mode 100644 index 0000000000..d06ce2e8f9 --- /dev/null +++ b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/Microsoft.AspNetCore.ResponseCaching.Abstractions.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + 2d1022e8-cbb6-478d-a420-cb888d0ef7b7 + Microsoft.AspNetCore.ResponseCaching.Abstractions + .\obj + .\bin\ + v4.5.2 + + + + 2.0 + + + diff --git a/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..32dcddfc57 --- /dev/null +++ b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// 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.Reflection; +using System.Resources; + +[assembly: AssemblyMetadata("Serviceable", "True")] +[assembly: NeutralResourcesLanguage("en-us")] +[assembly: AssemblyCompany("Microsoft Corporation.")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyProduct("Microsoft ASP.NET Core")] \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/project.json b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/project.json new file mode 100644 index 0000000000..ee4622c44c --- /dev/null +++ b/src/Microsoft.AspNetCore.ResponseCaching.Abstractions/project.json @@ -0,0 +1,31 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "warningsAsErrors": true, + "keyFile": "../../tools/Key.snk", + "xmlDoc": true + }, + "description": "ASP.NET Core response caching middleware abstractions and feature interface definitions.", + "packOptions": { + "repository": { + "type": "git", + "url": "git://github.com/aspnet/ResponseCaching" + }, + "tags": [ + "aspnetcore", + "cache", + "caching" + ] + }, + "dependencies": { + "Microsoft.Extensions.Primitives": "1.1.0-*" + }, + "frameworks": { + "net451": {}, + "netstandard1.3": { + "dependencies": { + "NETStandard.Library": "1.6.1-*" + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Extensions/ResponseCacheHttpContextExtensions.cs b/src/Microsoft.AspNetCore.ResponseCaching/Extensions/ResponseCacheHttpContextExtensions.cs deleted file mode 100644 index d8349bf594..0000000000 --- a/src/Microsoft.AspNetCore.ResponseCaching/Extensions/ResponseCacheHttpContextExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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.Http; - -namespace Microsoft.AspNetCore.ResponseCaching -{ - public static class ResponseCacheHttpContextExtensions - { - public static ResponseCacheFeature GetResponseCacheFeature(this HttpContext httpContext) - { - return httpContext.Features.Get(); - } - } -} diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Internal/InternalHttpContextExtensions.cs b/src/Microsoft.AspNetCore.ResponseCaching/Internal/InternalHttpContextExtensions.cs deleted file mode 100644 index e76aa83a68..0000000000 --- a/src/Microsoft.AspNetCore.ResponseCaching/Internal/InternalHttpContextExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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; - -namespace Microsoft.AspNetCore.ResponseCaching.Internal -{ - internal static class InternalHttpContextExtensions - { - internal static void AddResponseCacheFeature(this HttpContext httpContext) - { - if (httpContext.GetResponseCacheFeature() != null) - { - throw new InvalidOperationException($"Another instance of {nameof(ResponseCacheFeature)} already exists. Only one instance of {nameof(ResponseCacheMiddleware)} can be configured for an application."); - } - httpContext.Features.Set(new ResponseCacheFeature()); - } - - internal static void RemoveResponseCacheFeature(this HttpContext httpContext) - { - httpContext.Features.Set(null); - } - } -} diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Extensions/ResponseCacheExtensions.cs b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.ResponseCaching/Extensions/ResponseCacheExtensions.cs rename to src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheExtensions.cs diff --git a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheFeature.cs b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheFeature.cs index eec1a64887..374338a688 100644 --- a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheFeature.cs +++ b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheFeature.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.ResponseCaching { - public class ResponseCacheFeature + public class ResponseCacheFeature : IResponseCacheFeature { public StringValues VaryByQueryKeys { get; set; } } diff --git a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheMiddleware.cs b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheMiddleware.cs index c7939b82c8..6be81a9578 100644 --- a/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheMiddleware.cs +++ b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheMiddleware.cs @@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.ResponseCaching // Create the cache entry now var response = context.HttpContext.Response; var varyHeaders = new StringValues(response.Headers.GetCommaSeparatedValues(HeaderNames.Vary)); - var varyQueryKeys = context.HttpContext.GetResponseCacheFeature()?.VaryByQueryKeys ?? StringValues.Empty; + var varyQueryKeys = context.HttpContext.Features.Get()?.VaryByQueryKeys ?? StringValues.Empty; context.CachedResponseValidFor = context.ResponseCacheControlHeaderValue.SharedMaxAge ?? context.ResponseCacheControlHeaderValue.MaxAge ?? (context.ResponseExpires - context.ResponseTime.Value) ?? @@ -325,7 +325,12 @@ namespace Microsoft.AspNetCore.ResponseCaching context.HttpContext.Features.Set(new SendFileFeatureWrapper(context.OriginalSendFileFeature, context.ResponseCacheStream)); } - context.HttpContext.AddResponseCacheFeature(); + // Add IResponseCacheFeature + if (context.HttpContext.Features.Get() != null) + { + throw new InvalidOperationException($"Another instance of {nameof(ResponseCacheFeature)} already exists. Only one instance of {nameof(ResponseCacheMiddleware)} can be configured for an application."); + } + context.HttpContext.Features.Set(new ResponseCacheFeature()); } internal static void UnshimResponseStream(ResponseCacheContext context) @@ -336,7 +341,8 @@ namespace Microsoft.AspNetCore.ResponseCaching // Unshim IHttpSendFileFeature context.HttpContext.Features.Set(context.OriginalSendFileFeature); - context.HttpContext.RemoveResponseCacheFeature(); + // Remove IResponseCacheFeature + context.HttpContext.Features.Set(null); } internal static bool ContentIsNotModified(ResponseCacheContext context) diff --git a/src/Microsoft.AspNetCore.ResponseCaching/Extensions/ResponseCacheServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheServiceCollectionExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.ResponseCaching/Extensions/ResponseCacheServiceCollectionExtensions.cs rename to src/Microsoft.AspNetCore.ResponseCaching/ResponseCacheServiceCollectionExtensions.cs diff --git a/src/Microsoft.AspNetCore.ResponseCaching/project.json b/src/Microsoft.AspNetCore.ResponseCaching/project.json index 5adf0b7360..04f2eae5c3 100644 --- a/src/Microsoft.AspNetCore.ResponseCaching/project.json +++ b/src/Microsoft.AspNetCore.ResponseCaching/project.json @@ -1,5 +1,5 @@ { - "version": "0.1.0-*", + "version": "1.0.0-*", "buildOptions": { "warningsAsErrors": true, "allowUnsafe": true, @@ -24,6 +24,7 @@ "dependencies": { "Microsoft.AspNetCore.Http": "1.1.0-*", "Microsoft.AspNetCore.Http.Extensions": "1.1.0-*", + "Microsoft.AspNetCore.ResponseCaching.Abstractions": "1.0.0-*", "Microsoft.Extensions.Caching.Memory": "1.1.0-*", "Microsoft.Extensions.Logging.Abstractions": "1.1.0-*", "Microsoft.Extensions.TaskCache.Sources": { diff --git a/test/Microsoft.AspNetCore.ResponseCaching.Tests/HttpContextInternalExtensionTests.cs b/test/Microsoft.AspNetCore.ResponseCaching.Tests/HttpContextInternalExtensionTests.cs deleted file mode 100644 index 8efd416d16..0000000000 --- a/test/Microsoft.AspNetCore.ResponseCaching.Tests/HttpContextInternalExtensionTests.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.AspNetCore.ResponseCaching.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.ResponseCaching.Tests -{ - public class HttpContextInternalExtensionTests - { - [Fact] - public void AddingSecondResponseCacheFeature_Throws() - { - var httpContext = new DefaultHttpContext(); - - // Should not throw - httpContext.AddResponseCacheFeature(); - - // Should throw - Assert.ThrowsAny(() => httpContext.AddResponseCacheFeature()); - } - } -} diff --git a/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheMiddlewareTests.cs b/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheMiddlewareTests.cs index 9e251dc9b1..f2005ff54f 100644 --- a/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheMiddlewareTests.cs @@ -433,8 +433,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests var context = TestUtils.CreateTestContext(); context.HttpContext.Response.Headers[HeaderNames.Vary] = new StringValues(new[] { "headerA", "HEADERB", "HEADERc" }); - context.HttpContext.AddResponseCacheFeature(); - context.HttpContext.GetResponseCacheFeature().VaryByQueryKeys = new StringValues(new[] { "queryB", "QUERYA" }); + context.HttpContext.Features.Set(new ResponseCacheFeature() + { + VaryByQueryKeys = new StringValues(new[] { "queryB", "QUERYA" }) + }); var cachedVaryByRules = new CachedVaryByRules() { Headers = new StringValues(new[] { "HeaderA", "HeaderB" }), @@ -462,8 +464,10 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests var context = TestUtils.CreateTestContext(); context.HttpContext.Response.Headers[HeaderNames.Vary] = new StringValues(new[] { "headerA", "HEADERB" }); - context.HttpContext.AddResponseCacheFeature(); - context.HttpContext.GetResponseCacheFeature().VaryByQueryKeys = new StringValues(new[] { "queryB", "QUERYA" }); + context.HttpContext.Features.Set(new ResponseCacheFeature() + { + VaryByQueryKeys = new StringValues(new[] { "queryB", "QUERYA" }) + }); var cachedVaryByRules = new CachedVaryByRules() { VaryByKeyPrefix = FastGuid.NewGuid().IdString, @@ -669,6 +673,19 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests LoggedMessage.ResponseNotCached); } + [Fact] + public void ShimResponseStream_SecondInvocation_Throws() + { + var middleware = TestUtils.CreateTestMiddleware(); + var context = TestUtils.CreateTestContext(); + + // Should not throw + middleware.ShimResponseStream(context); + + // Should throw + Assert.ThrowsAny(() => middleware.ShimResponseStream(context)); + } + [Fact] public void GetOrderCasingNormalizedStringValues_NormalizesCasingToUpper() { diff --git a/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheTests.cs b/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheTests.cs index bc473d12a5..68c7f2ab86 100644 --- a/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheTests.cs +++ b/test/Microsoft.AspNetCore.ResponseCaching.Tests/ResponseCacheTests.cs @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = "query"; + context.Features.Get().VaryByQueryKeys = "query"; await TestUtils.TestRequestDelegate(context); }); @@ -126,7 +126,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = new[] { "QueryA", "queryb" }; + context.Features.Get().VaryByQueryKeys = new[] { "QueryA", "queryb" }; await TestUtils.TestRequestDelegate(context); }); @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = new[] { "*" }; + context.Features.Get().VaryByQueryKeys = new[] { "*" }; await TestUtils.TestRequestDelegate(context); }); @@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = new[] { "QueryB", "QueryA" }; + context.Features.Get().VaryByQueryKeys = new[] { "QueryB", "QueryA" }; await TestUtils.TestRequestDelegate(context); }); @@ -192,7 +192,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = new[] { "*" }; + context.Features.Get().VaryByQueryKeys = new[] { "*" }; await TestUtils.TestRequestDelegate(context); }); @@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = "query"; + context.Features.Get().VaryByQueryKeys = "query"; await TestUtils.TestRequestDelegate(context); }); @@ -236,7 +236,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = new[] { "QueryA", "QueryB" }; + context.Features.Get().VaryByQueryKeys = new[] { "QueryA", "QueryB" }; await TestUtils.TestRequestDelegate(context); }); @@ -258,7 +258,7 @@ namespace Microsoft.AspNetCore.ResponseCaching.Tests { var builders = TestUtils.CreateBuildersWithResponseCache(requestDelegate: async (context) => { - context.GetResponseCacheFeature().VaryByQueryKeys = new[] { "*" }; + context.Features.Get().VaryByQueryKeys = new[] { "*" }; await TestUtils.TestRequestDelegate(context); }); diff --git a/test/Microsoft.AspNetCore.ResponseCaching.Tests/project.json b/test/Microsoft.AspNetCore.ResponseCaching.Tests/project.json index 7eefd0a959..7932f53e10 100644 --- a/test/Microsoft.AspNetCore.ResponseCaching.Tests/project.json +++ b/test/Microsoft.AspNetCore.ResponseCaching.Tests/project.json @@ -5,7 +5,7 @@ }, "dependencies": { "dotnet-test-xunit": "2.2.0-*", - "Microsoft.AspNetCore.ResponseCaching": "0.1.0-*", + "Microsoft.AspNetCore.ResponseCaching": "1.0.0-*", "Microsoft.AspNetCore.TestHost": "1.1.0-*", "Microsoft.Extensions.Logging.Testing": "1.1.0-*", "xunit": "2.2.0-*"