From 398883c28d3c8f1ccef00c449aafe13fad963e9e Mon Sep 17 00:00:00 2001 From: Daniil Grudzinsky Date: Mon, 11 Feb 2019 16:36:32 +0200 Subject: [PATCH 1/2] allow custom headers caching. Fixed test, failing due to start of 2019 --- src/WebApi.OutputCache.Core/Constants.cs | 2 + .../CacheOutputAttribute.cs | 52 +++++- .../WebApi.OutputCache.Core.Tests.csproj | 3 + .../ClientSideTests.cs | 4 +- .../CustomHeadersContent.cs | 62 ++++++ .../CustomHeadersTests.cs | 176 ++++++++++++++++++ .../MemoryCacheForTests.cs | 58 ++++++ .../CustomHeadersController.cs | 90 +++++++++ .../TestControllers/SampleController.cs | 7 +- .../WebApi.OutputCache.V2.Tests.csproj | 7 + 10 files changed, 454 insertions(+), 7 deletions(-) create mode 100644 test/WebApi.OutputCache.V2.Tests/CustomHeadersContent.cs create mode 100644 test/WebApi.OutputCache.V2.Tests/CustomHeadersTests.cs create mode 100644 test/WebApi.OutputCache.V2.Tests/MemoryCacheForTests.cs create mode 100644 test/WebApi.OutputCache.V2.Tests/TestControllers/CustomHeadersController.cs diff --git a/src/WebApi.OutputCache.Core/Constants.cs b/src/WebApi.OutputCache.Core/Constants.cs index 139b0b7..8553bcd 100644 --- a/src/WebApi.OutputCache.Core/Constants.cs +++ b/src/WebApi.OutputCache.Core/Constants.cs @@ -5,5 +5,7 @@ public sealed class Constants public const string ContentTypeKey = ":response-ct"; public const string EtagKey = ":response-etag"; public const string GenerationTimestampKey = ":response-generationtimestamp"; + public const string CustomHeaders = ":custom-headers"; + public const string CustomContentHeaders = ":custom-content-headers"; } } diff --git a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs index f995dc9..fc79e71 100644 --- a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs +++ b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; @@ -81,6 +82,11 @@ public int SharedTimeSpan /// public Type CacheKeyGenerator { get; set; } + /// + /// Comma seperated list of HTTP headers to cache + /// + public string IncludeCustomHeaders { get; set; } + /// /// If set to something else than an empty string, this value will always be used for the Content-Type header, regardless of content negotiation. /// @@ -185,6 +191,9 @@ public override void OnActionExecuting(HttpActionContext actionContext) if (!_webApiCache.Contains(cachekey)) return; + var responseHeaders = _webApiCache.Get>>(cachekey + Constants.CustomHeaders); + var responseContentHeaders = _webApiCache.Get>>(cachekey + Constants.CustomContentHeaders); + if (actionContext.Request.Headers.IfNoneMatch != null) { var etag = _webApiCache.Get(cachekey + Constants.EtagKey); @@ -194,7 +203,8 @@ public override void OnActionExecuting(HttpActionContext actionContext) { var time = CacheTimeQuery.Execute(DateTime.Now); var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified); - + if (responseHeaders != null) AddCustomCachedHeaders(quickResponse, responseHeaders, responseContentHeaders); + SetEtag(quickResponse, etag); ApplyCacheHeaders(quickResponse, time); actionContext.Response = quickResponse; @@ -225,6 +235,8 @@ public override void OnActionExecuting(HttpActionContext actionContext) var responseEtag = _webApiCache.Get(cachekey + Constants.EtagKey); if (responseEtag != null) SetEtag(actionContext.Response, responseEtag); + if (responseHeaders != null) AddCustomCachedHeaders(actionContext.Response, responseHeaders, responseContentHeaders); + var cacheTime = CacheTimeQuery.Execute(DateTime.Now); ApplyCacheHeaders(actionContext.Response, cacheTime, contentGenerationTimestamp); } @@ -275,10 +287,27 @@ public override async Task OnActionExecutedAsync(HttpActionExecutedContext actio etag, cacheTime.AbsoluteExpiration, baseKey); - _webApiCache.Add(cachekey + Constants.GenerationTimestampKey, actionExecutionTimestamp.ToString(), cacheTime.AbsoluteExpiration, baseKey); + + if (!String.IsNullOrEmpty(IncludeCustomHeaders)) + { + // convert to dictionary of lists to ensure thread safety if implementation of IEnumerable is changed + var headers = actionExecutedContext.Response.Headers.Where(h => IncludeCustomHeaders.Contains(h.Key)) + .ToDictionary(x => x.Key, x => x.Value.ToList()); + + var contentHeaders = actionExecutedContext.Response.Content.Headers.Where(h => IncludeCustomHeaders.Contains(h.Key)) + .ToDictionary(x => x.Key, x => x.Value.ToList()); + + _webApiCache.Add(cachekey + Constants.CustomHeaders, + headers, + cacheTime.AbsoluteExpiration, baseKey); + + _webApiCache.Add(cachekey + Constants.CustomContentHeaders, + contentHeaders, + cacheTime.AbsoluteExpiration, baseKey); + } } } } @@ -311,6 +340,25 @@ protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime } } + protected virtual void AddCustomCachedHeaders(HttpResponseMessage response, Dictionary> headers, Dictionary> contentHeaders) + { + foreach (var headerKey in headers.Keys) + { + foreach (var headerValue in headers[headerKey]) + { + response.Headers.Add(headerKey, headerValue); + } + } + + foreach (var headerKey in contentHeaders.Keys) + { + foreach (var headerValue in contentHeaders[headerKey]) + { + response.Content.Headers.Add(headerKey, headerValue); + } + } + } + protected virtual string CreateEtag(HttpActionExecutedContext actionExecutedContext, string cachekey, CacheTime cacheTime) { return Guid.NewGuid().ToString(); diff --git a/test/WebApi.OutputCache.Core.Tests/WebApi.OutputCache.Core.Tests.csproj b/test/WebApi.OutputCache.Core.Tests/WebApi.OutputCache.Core.Tests.csproj index 7739329..21df54c 100644 --- a/test/WebApi.OutputCache.Core.Tests/WebApi.OutputCache.Core.Tests.csproj +++ b/test/WebApi.OutputCache.Core.Tests/WebApi.OutputCache.Core.Tests.csproj @@ -56,6 +56,9 @@ WebApi.OutputCache.Core + + +