diff --git a/.nuget/NuGet.exe b/.nuget/NuGet.exe
index 8d13fd8..3ffdd33 100644
Binary files a/.nuget/NuGet.exe and b/.nuget/NuGet.exe differ
diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets
index d3befda..f943812 100644
--- a/.nuget/NuGet.targets
+++ b/.nuget/NuGet.targets
@@ -2,7 +2,7 @@
$(MSBuildProjectDirectory)\..\
-
+
false
@@ -11,16 +11,16 @@
true
-
+
false
-
+
-
+
@@ -28,37 +28,48 @@
$([System.IO.Path]::Combine($(SolutionDir), ".nuget"))
- $([System.IO.Path]::Combine($(ProjectDir), "packages.config"))
- $([System.IO.Path]::Combine($(SolutionDir), "packages"))
-
+
$(SolutionDir).nuget
- packages.config
- $(SolutionDir)packages
+
+
+
+ $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config
+ $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config
+
+
+
+ $(MSBuildProjectDirectory)\packages.config
+ $(PackagesProjectConfig)
- $(NuGetToolsPath)\nuget.exe
+ $(NuGetToolsPath)\NuGet.exe
@(PackageSource)
-
+
"$(NuGetExePath)"
mono --runtime=v4.0.30319 $(NuGetExePath)
$(TargetDir.Trim('\\'))
-
+
-RequireConsent
+ -NonInteractive
+
+ "$(SolutionDir) "
+ "$(SolutionDir)"
+
- $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -o "$(PackagesDir)"
- $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols
+ $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir)
+ $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols
-
+
RestorePackages;
- $(ResolveReferencesDependsOn);
-
+ $(BuildDependsOn);
+
@@ -70,37 +81,36 @@
-
-
+
-
+
-
+
-
-
+
-
+
@@ -119,7 +129,7 @@
Log.LogMessage("Downloading latest version of NuGet.exe...");
WebClient webClient = new WebClient();
- webClient.DownloadFile("https://nuget.org/nuget.exe", OutputFilename);
+ webClient.DownloadFile("https://www.nuget.org/nuget.exe", OutputFilename);
return true;
}
@@ -131,23 +141,4 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
diff --git a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs
index ee6fbca..f4df32b 100644
--- a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs
+++ b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs
@@ -17,261 +17,266 @@
namespace WebApi.OutputCache.V2
{
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
- public class CacheOutputAttribute : ActionFilterAttribute
- {
- private const string CurrentRequestMediaType = "CacheOutput:CurrentRequestMediaType";
- protected static MediaTypeHeaderValue DefaultMediaType = new MediaTypeHeaderValue("application/json") {CharSet = Encoding.UTF8.HeaderName};
-
- ///
- /// Cache enabled only for requests when Thread.CurrentPrincipal is not set
- ///
- public bool AnonymousOnly { get; set; }
-
- ///
- /// Corresponds to MustRevalidate HTTP header - indicates whether the origin server requires revalidation of a cache entry on any subsequent use when the cache entry becomes stale
- ///
- public bool MustRevalidate { get; set; }
-
- ///
- /// Do not vary cache by querystring values
- ///
- public bool ExcludeQueryStringFromCacheKey { get; set; }
-
- ///
- /// How long response should be cached on the server side (in seconds)
- ///
- public int ServerTimeSpan { get; set; }
-
- ///
- /// Corresponds to CacheControl MaxAge HTTP header (in seconds)
- ///
- public int ClientTimeSpan { get; set; }
-
- ///
- /// Corresponds to CacheControl NoCache HTTP header
- ///
- public bool NoCache { get; set; }
-
- ///
- /// Corresponds to CacheControl Private HTTP header. Response can be cached by browser but not by intermediary cache
- ///
- public bool Private { get; set; }
-
- ///
- /// Class used to generate caching keys
- ///
- public Type CacheKeyGenerator { get; set; }
-
- // cache repository
- private IApiOutputCache _webApiCache;
-
- protected virtual void EnsureCache(HttpConfiguration config, HttpRequestMessage req)
- {
- _webApiCache = config.CacheOutputConfiguration().GetCacheOutputProvider(req);
- }
-
- internal IModelQuery CacheTimeQuery;
-
- protected virtual bool IsCachingAllowed(HttpActionContext actionContext, bool anonymousOnly)
- {
- if (anonymousOnly)
- {
- if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
- {
- return false;
- }
- }
-
- if (actionContext.ActionDescriptor.GetCustomAttributes().Any())
- {
- return false;
- }
-
- return actionContext.Request.Method == HttpMethod.Get;
- }
-
- protected virtual void EnsureCacheTimeQuery()
- {
- if (CacheTimeQuery == null) ResetCacheTimeQuery();
- }
-
- protected void ResetCacheTimeQuery()
- {
- CacheTimeQuery = new ShortTime( ServerTimeSpan, ClientTimeSpan );
- }
-
- protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration config, HttpActionContext actionContext)
- {
- MediaTypeHeaderValue responseMediaType = null;
-
- var negotiator = config.Services.GetService(typeof(IContentNegotiator)) as IContentNegotiator;
- var returnType = actionContext.ActionDescriptor.ReturnType;
-
- if (negotiator != null && returnType != typeof(HttpResponseMessage) && (returnType != typeof(IHttpActionResult) || typeof(IHttpActionResult).IsAssignableFrom(returnType)))
- {
- var negotiatedResult = negotiator.Negotiate(returnType, actionContext.Request, config.Formatters);
-
- if (negotiatedResult == null)
- {
- return DefaultMediaType;
- }
-
- responseMediaType = negotiatedResult.MediaType;
- if (string.IsNullOrWhiteSpace(responseMediaType.CharSet))
- {
- responseMediaType.CharSet = Encoding.UTF8.HeaderName;
- }
- }
- else
- {
- if (actionContext.Request.Headers.Accept != null)
- {
- responseMediaType = actionContext.Request.Headers.Accept.FirstOrDefault();
- if (responseMediaType == null || !config.Formatters.Any(x => x.SupportedMediaTypes.Contains(responseMediaType)))
- {
- return DefaultMediaType;
- }
- }
- }
-
- return responseMediaType;
- }
-
- public override void OnActionExecuting(HttpActionContext actionContext)
- {
- if (actionContext == null) throw new ArgumentNullException("actionContext");
-
- if (!IsCachingAllowed(actionContext, AnonymousOnly)) return;
-
- var config = actionContext.Request.GetConfiguration();
-
- EnsureCacheTimeQuery();
- EnsureCache(config, actionContext.Request);
-
- var cacheKeyGenerator = config.CacheOutputConfiguration().GetCacheKeyGenerator(actionContext.Request, CacheKeyGenerator);
-
- var responseMediaType = GetExpectedMediaType(config, actionContext);
- actionContext.Request.Properties[CurrentRequestMediaType] = responseMediaType;
- var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, responseMediaType, ExcludeQueryStringFromCacheKey);
-
- if (!_webApiCache.Contains(cachekey)) return;
-
- if (actionContext.Request.Headers.IfNoneMatch != null)
- {
- var etag = _webApiCache.Get(cachekey + Constants.EtagKey);
- if (etag != null)
- {
- if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == etag))
- {
- var time = CacheTimeQuery.Execute(DateTime.Now);
- var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified);
- ApplyCacheHeaders(quickResponse, time);
- actionContext.Response = quickResponse;
- return;
- }
- }
- }
-
- var val = _webApiCache.Get(cachekey);
- if (val == null) return;
-
- var contenttype = _webApiCache.Get(cachekey + Constants.ContentTypeKey) ?? new MediaTypeHeaderValue(cachekey.Split(new[] { ':' }, 2)[1].Split(';')[0]);
-
- actionContext.Response = actionContext.Request.CreateResponse();
- actionContext.Response.Content = new ByteArrayContent(val);
-
- actionContext.Response.Content.Headers.ContentType = contenttype;
- var responseEtag = _webApiCache.Get(cachekey + Constants.EtagKey);
- if (responseEtag != null) SetEtag(actionContext.Response, responseEtag);
-
- var cacheTime = CacheTimeQuery.Execute(DateTime.Now);
- ApplyCacheHeaders(actionContext.Response, cacheTime);
- }
-
- public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
- {
- if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) return;
-
- if (!IsCachingAllowed(actionExecutedContext.ActionContext, AnonymousOnly)) return;
-
- var cacheTime = CacheTimeQuery.Execute(DateTime.Now);
- if (cacheTime.AbsoluteExpiration > DateTime.Now)
- {
- var httpConfig = actionExecutedContext.Request.GetConfiguration();
- var config = httpConfig.CacheOutputConfiguration();
- var cacheKeyGenerator = config.GetCacheKeyGenerator(actionExecutedContext.Request, CacheKeyGenerator);
-
- var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext);
- var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey);
-
- if (!string.IsNullOrWhiteSpace(cachekey) && !(_webApiCache.Contains(cachekey)))
- {
- SetEtag(actionExecutedContext.Response, CreateEtag(actionExecutedContext, cachekey, cacheTime));
-
- var responseContent = actionExecutedContext.Response.Content;
-
- if (responseContent != null)
- {
- var baseKey = config.MakeBaseCachekey(actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName, actionExecutedContext.ActionContext.ActionDescriptor.ActionName);
- var contentType = responseContent.Headers.ContentType;
- string etag = actionExecutedContext.Response.Headers.ETag.Tag;
- //ConfigureAwait false to avoid deadlocks
- var content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false);
-
- responseContent.Headers.Remove("Content-Length");
-
- _webApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration);
- _webApiCache.Add(cachekey, content, cacheTime.AbsoluteExpiration, baseKey);
-
-
- _webApiCache.Add(cachekey + Constants.ContentTypeKey,
- contentType,
- cacheTime.AbsoluteExpiration, baseKey);
-
-
- _webApiCache.Add(cachekey + Constants.EtagKey,
- etag,
- cacheTime.AbsoluteExpiration, baseKey);
- }
- }
- }
-
- ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime);
- }
-
- protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime cacheTime)
- {
- if (cacheTime.ClientTimeSpan > TimeSpan.Zero || MustRevalidate || Private)
- {
- var cachecontrol = new CacheControlHeaderValue
- {
- MaxAge = cacheTime.ClientTimeSpan,
- MustRevalidate = MustRevalidate,
- Private = Private
- };
-
- response.Headers.CacheControl = cachecontrol;
- }
- else if (NoCache)
- {
- response.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
- response.Headers.Add("Pragma", "no-cache");
- }
- }
-
- protected virtual string CreateEtag(HttpActionExecutedContext actionExecutedContext, string cachekey, CacheTime cacheTime)
- {
- return Guid.NewGuid().ToString();
- }
-
- private static void SetEtag(HttpResponseMessage message, string etag)
- {
- if (etag != null)
- {
- var eTag = new EntityTagHeaderValue(@"""" + etag.Replace("\"", string.Empty) + @"""");
- message.Headers.ETag = eTag;
- }
- }
- }
-}
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public class CacheOutputAttribute : ActionFilterAttribute
+ {
+ private const string CurrentRequestMediaType = "CacheOutput:CurrentRequestMediaType";
+ protected static MediaTypeHeaderValue DefaultMediaType = new MediaTypeHeaderValue("application/json") { CharSet = Encoding.UTF8.HeaderName };
+
+ ///
+ /// Cache enabled only for requests when Thread.CurrentPrincipal is not set
+ ///
+ public bool AnonymousOnly { get; set; }
+
+ ///
+ /// Corresponds to MustRevalidate HTTP header - indicates whether the origin server requires revalidation of a cache entry on any subsequent use when the cache entry becomes stale
+ ///
+ public bool MustRevalidate { get; set; }
+
+ ///
+ /// Do not vary cache by querystring values
+ ///
+ public bool ExcludeQueryStringFromCacheKey { get; set; }
+
+ ///
+ /// How long response should be cached on the server side (in seconds)
+ ///
+ public int ServerTimeSpan { get; set; }
+
+ ///
+ /// Corresponds to CacheControl MaxAge HTTP header (in seconds)
+ ///
+ public int ClientTimeSpan { get; set; }
+
+ ///
+ /// Corresponds to CacheControl NoCache HTTP header
+ ///
+ public bool NoCache { get; set; }
+
+ ///
+ /// Corresponds to CacheControl Private HTTP header. Response can be cached by browser but not by intermediary cache
+ ///
+ public bool Private { get; set; }
+
+ ///
+ /// Class used to generate caching keys
+ ///
+ public Type CacheKeyGenerator { get; set; }
+
+ // cache repository
+ private IApiOutputCache _webApiCache;
+
+ protected virtual void EnsureCache(HttpConfiguration config, HttpRequestMessage req)
+ {
+ _webApiCache = config.CacheOutputConfiguration().GetCacheOutputProvider(req);
+ }
+
+ internal IModelQuery CacheTimeQuery;
+
+ protected virtual bool IsCachingAllowed(HttpActionContext actionContext, bool anonymousOnly)
+ {
+ if (anonymousOnly)
+ {
+ if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
+ {
+ return false;
+ }
+ }
+
+ if (actionContext.ActionDescriptor.GetCustomAttributes().Any())
+ {
+ return false;
+ }
+
+
+ var verbsAttrs = actionContext.ActionDescriptor.GetFilterPipeline().Select(a => a.Instance).OfType();
+
+ return verbsAttrs.Any()
+ ? verbsAttrs.SelectMany(a => a.Verbs).Any(a => actionContext.Request.Method == new HttpMethod(a))
+ : actionContext.Request.Method == HttpMethod.Get;
+ }
+
+ protected virtual void EnsureCacheTimeQuery()
+ {
+ if (CacheTimeQuery == null) ResetCacheTimeQuery();
+ }
+
+ protected void ResetCacheTimeQuery()
+ {
+ CacheTimeQuery = new ShortTime(ServerTimeSpan, ClientTimeSpan);
+ }
+
+ protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration config, HttpActionContext actionContext)
+ {
+ MediaTypeHeaderValue responseMediaType = null;
+
+ var negotiator = config.Services.GetService(typeof(IContentNegotiator)) as IContentNegotiator;
+ var returnType = actionContext.ActionDescriptor.ReturnType;
+
+ if (negotiator != null && returnType != typeof(HttpResponseMessage) && (returnType != typeof(IHttpActionResult) || typeof(IHttpActionResult).IsAssignableFrom(returnType)))
+ {
+ var negotiatedResult = negotiator.Negotiate(returnType, actionContext.Request, config.Formatters);
+
+ if (negotiatedResult == null)
+ {
+ return DefaultMediaType;
+ }
+
+ responseMediaType = negotiatedResult.MediaType;
+ if (string.IsNullOrWhiteSpace(responseMediaType.CharSet))
+ {
+ responseMediaType.CharSet = Encoding.UTF8.HeaderName;
+ }
+ }
+ else
+ {
+ if (actionContext.Request.Headers.Accept != null)
+ {
+ responseMediaType = actionContext.Request.Headers.Accept.FirstOrDefault();
+ if (responseMediaType == null || !config.Formatters.Any(x => x.SupportedMediaTypes.Contains(responseMediaType)))
+ {
+ return DefaultMediaType;
+ }
+ }
+ }
+
+ return responseMediaType;
+ }
+
+ public override void OnActionExecuting(HttpActionContext actionContext)
+ {
+ if (actionContext == null) throw new ArgumentNullException("actionContext");
+
+ if (!IsCachingAllowed(actionContext, AnonymousOnly)) return;
+
+ var config = actionContext.Request.GetConfiguration();
+
+ EnsureCacheTimeQuery();
+ EnsureCache(config, actionContext.Request);
+
+ var cacheKeyGenerator = config.CacheOutputConfiguration().GetCacheKeyGenerator(actionContext.Request, CacheKeyGenerator);
+
+ var responseMediaType = GetExpectedMediaType(config, actionContext);
+ actionContext.Request.Properties[CurrentRequestMediaType] = responseMediaType;
+ var cachekey = cacheKeyGenerator.MakeCacheKey(actionContext, responseMediaType, ExcludeQueryStringFromCacheKey);
+
+ if (!_webApiCache.Contains(cachekey)) return;
+
+ if (actionContext.Request.Headers.IfNoneMatch != null)
+ {
+ var etag = _webApiCache.Get(cachekey + Constants.EtagKey);
+ if (etag != null)
+ {
+ if (actionContext.Request.Headers.IfNoneMatch.Any(x => x.Tag == etag))
+ {
+ var time = CacheTimeQuery.Execute(DateTime.Now);
+ var quickResponse = actionContext.Request.CreateResponse(HttpStatusCode.NotModified);
+ ApplyCacheHeaders(quickResponse, time);
+ actionContext.Response = quickResponse;
+ return;
+ }
+ }
+ }
+
+ var val = _webApiCache.Get(cachekey);
+ if (val == null) return;
+
+ var contenttype = _webApiCache.Get(cachekey + Constants.ContentTypeKey) ?? new MediaTypeHeaderValue(cachekey.Split(new[] { ':' }, 2)[1].Split(';')[0]);
+
+ actionContext.Response = actionContext.Request.CreateResponse();
+ actionContext.Response.Content = new ByteArrayContent(val);
+
+ actionContext.Response.Content.Headers.ContentType = contenttype;
+ var responseEtag = _webApiCache.Get(cachekey + Constants.EtagKey);
+ if (responseEtag != null) SetEtag(actionContext.Response, responseEtag);
+
+ var cacheTime = CacheTimeQuery.Execute(DateTime.Now);
+ ApplyCacheHeaders(actionContext.Response, cacheTime);
+ }
+
+ public override async Task OnActionExecutedAsync(HttpActionExecutedContext actionExecutedContext, CancellationToken cancellationToken)
+ {
+ if (actionExecutedContext.ActionContext.Response == null || !actionExecutedContext.ActionContext.Response.IsSuccessStatusCode) return;
+
+ if (!IsCachingAllowed(actionExecutedContext.ActionContext, AnonymousOnly)) return;
+
+ var cacheTime = CacheTimeQuery.Execute(DateTime.Now);
+ if (cacheTime.AbsoluteExpiration > DateTime.Now)
+ {
+ var httpConfig = actionExecutedContext.Request.GetConfiguration();
+ var config = httpConfig.CacheOutputConfiguration();
+ var cacheKeyGenerator = config.GetCacheKeyGenerator(actionExecutedContext.Request, CacheKeyGenerator);
+
+ var responseMediaType = actionExecutedContext.Request.Properties[CurrentRequestMediaType] as MediaTypeHeaderValue ?? GetExpectedMediaType(httpConfig, actionExecutedContext.ActionContext);
+ var cachekey = cacheKeyGenerator.MakeCacheKey(actionExecutedContext.ActionContext, responseMediaType, ExcludeQueryStringFromCacheKey);
+
+ if (!string.IsNullOrWhiteSpace(cachekey) && !(_webApiCache.Contains(cachekey)))
+ {
+ SetEtag(actionExecutedContext.Response, CreateEtag(actionExecutedContext, cachekey, cacheTime));
+
+ var responseContent = actionExecutedContext.Response.Content;
+
+ if (responseContent != null)
+ {
+ var baseKey = config.MakeBaseCachekey(actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerType.FullName, actionExecutedContext.ActionContext.ActionDescriptor.ActionName);
+ var contentType = responseContent.Headers.ContentType;
+ string etag = actionExecutedContext.Response.Headers.ETag.Tag;
+ //ConfigureAwait false to avoid deadlocks
+ var content = await responseContent.ReadAsByteArrayAsync().ConfigureAwait(false);
+
+ responseContent.Headers.Remove("Content-Length");
+
+ _webApiCache.Add(baseKey, string.Empty, cacheTime.AbsoluteExpiration);
+ _webApiCache.Add(cachekey, content, cacheTime.AbsoluteExpiration, baseKey);
+
+
+ _webApiCache.Add(cachekey + Constants.ContentTypeKey,
+ contentType,
+ cacheTime.AbsoluteExpiration, baseKey);
+
+
+ _webApiCache.Add(cachekey + Constants.EtagKey,
+ etag,
+ cacheTime.AbsoluteExpiration, baseKey);
+ }
+ }
+ }
+
+ ApplyCacheHeaders(actionExecutedContext.ActionContext.Response, cacheTime);
+ }
+
+ protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime cacheTime)
+ {
+ if (cacheTime.ClientTimeSpan > TimeSpan.Zero || MustRevalidate || Private)
+ {
+ var cachecontrol = new CacheControlHeaderValue
+ {
+ MaxAge = cacheTime.ClientTimeSpan,
+ MustRevalidate = MustRevalidate,
+ Private = Private
+ };
+
+ response.Headers.CacheControl = cachecontrol;
+ }
+ else if (NoCache)
+ {
+ response.Headers.CacheControl = new CacheControlHeaderValue { NoCache = true };
+ response.Headers.Add("Pragma", "no-cache");
+ }
+ }
+
+ protected virtual string CreateEtag(HttpActionExecutedContext actionExecutedContext, string cachekey, CacheTime cacheTime)
+ {
+ return Guid.NewGuid().ToString();
+ }
+
+ private static void SetEtag(HttpResponseMessage message, string etag)
+ {
+ if (etag != null)
+ {
+ var eTag = new EntityTagHeaderValue(@"""" + etag.Replace("\"", string.Empty) + @"""");
+ message.Headers.ETag = eTag;
+ }
+ }
+ }
+}
diff --git a/src/WebApi.OutputCache.V2/CacheOutputHttpVerbsAttribute.cs b/src/WebApi.OutputCache.V2/CacheOutputHttpVerbsAttribute.cs
new file mode 100644
index 0000000..ca1c249
--- /dev/null
+++ b/src/WebApi.OutputCache.V2/CacheOutputHttpVerbsAttribute.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Web.Http.Filters;
+
+namespace WebApi.OutputCache.V2
+{
+ ///
+ /// When this attribute is used, only the specified vebrs will participate in cache.
+ /// Otherwise GET method only.
+ ///
+ [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
+ public class CacheOutputHttpVerbsAttribute : ActionFilterAttribute
+ {
+ ///
+ /// Gets or sets the list of HTTP verbs that needs to participate in cache.
+ ///
+ public string[] Verbs { get; private set; }
+
+
+ public CacheOutputHttpVerbsAttribute(params string[] verbs)
+ {
+ Verbs = verbs;
+ }
+ }
+
+
+
+ ///
+ /// A shorthand version to CacheOutputHttpVerbsAttribute.
+ ///
+ ///
+ public sealed class CacheOutputHttpGetAttribute : CacheOutputHttpVerbsAttribute
+ {
+ public CacheOutputHttpGetAttribute()
+ : base("GET")
+ {
+ }
+ }
+
+
+
+ ///
+ /// A shorthand version to CacheOutputHttpVerbsAttribute.
+ ///
+ ///
+ public sealed class CacheOutputHttpPostAttribute : CacheOutputHttpVerbsAttribute
+ {
+ public CacheOutputHttpPostAttribute()
+ : base("POST")
+ {
+ }
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/WebApi.OutputCache.V2/CacheOutputKeyAttribute.cs b/src/WebApi.OutputCache.V2/CacheOutputKeyAttribute.cs
new file mode 100644
index 0000000..04db1a3
--- /dev/null
+++ b/src/WebApi.OutputCache.V2/CacheOutputKeyAttribute.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+
+namespace WebApi.OutputCache.V2
+{
+ ///
+ /// Enables custom cache key generation on a type.
+ ///
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
+ public sealed class CacheKeyComponentAttribute : Attribute
+ {
+ public Type GeneratorType { get; private set; }
+
+
+ public CacheKeyComponentAttribute(Type generatorType)
+ {
+ GeneratorType = generatorType;
+ }
+ }
+
+
+
+
+ public interface ICacheKeyComponentGenerator
+ {
+ string Generate(object value);
+ }
+
+
+
+
+
+ /// No recursive generation.
+ public abstract class ComplexValueCacheKeyComponentGenerator : ICacheKeyComponentGenerator
+ {
+ public string Generate(object value)
+ {
+ return string.Format("{0}{{{1}}}", value.GetType().FullName,
+ string.Join(";", GetNamedValues(value).Select(a => a.Key + "=" + a.Value)));
+ }
+
+ ///
+ /// Usually will return a dictionary of property name/value.
+ ///
+ protected abstract IDictionary GetNamedValues(object complexValue);
+ }
+
+
+
+
+ internal class EnumerableCacheKeyComponentGenerator : ICacheKeyComponentGenerator
+ {
+ public string Generate(object value)
+ {
+ return string.Join(";", (IEnumerable)value);
+ }
+ }
+
+
+ internal class AnyCacheKeyComponentGenerator : ICacheKeyComponentGenerator
+ {
+ public string Generate(object value)
+ {
+ return value.ToString();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs b/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs
index cf02a29..701ee6c 100644
--- a/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs
+++ b/src/WebApi.OutputCache.V2/DefaultCacheKeyGenerator.cs
@@ -1,80 +1,97 @@
-using System.Collections;
+using System;
+using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
+using System.Reflection;
using System.Text;
using System.Web.Http.Controllers;
namespace WebApi.OutputCache.V2
{
- public class DefaultCacheKeyGenerator : ICacheKeyGenerator
- {
- public virtual string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
- {
- var controller = context.ControllerContext.ControllerDescriptor.ControllerType.FullName;
- var action = context.ActionDescriptor.ActionName;
- var key = context.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(controller, action);
- var actionParameters = context.ActionArguments.Where(x => x.Value != null).Select(x => x.Key + "=" + GetValue(x.Value));
+ public class DefaultCacheKeyGenerator : ICacheKeyGenerator
+ {
+ public virtual string MakeCacheKey(HttpActionContext context, MediaTypeHeaderValue mediaType, bool excludeQueryString = false)
+ {
+ var controller = context.ControllerContext.ControllerDescriptor.ControllerType.FullName;
+ var action = context.ActionDescriptor.ActionName;
+ var key = context.Request.GetConfiguration().CacheOutputConfiguration().MakeBaseCachekey(controller, action);
+ var actionParameters = context.ActionArguments.Where(x => x.Value != null).Select(x => x.Key + "=" + GetValue(x.Value));
- string parameters;
+ string parameters;
- if (!excludeQueryString)
- {
- var queryStringParameters =
- context.Request.GetQueryNameValuePairs()
- .Where(x => x.Key.ToLower() != "callback")
- .Select(x => x.Key + "=" + x.Value);
- var parametersCollections = actionParameters.Union(queryStringParameters);
- parameters = "-" + string.Join("&", parametersCollections);
+ if (!excludeQueryString)
+ {
+ var queryStringParameters =
+ context.Request.GetQueryNameValuePairs()
+ .Where(x => x.Key.ToLower() != "callback")
+ .Select(x => x.Key + "=" + x.Value);
+ var parametersCollections = actionParameters.Union(queryStringParameters);
+ parameters = "-" + string.Join("&", parametersCollections);
- var callbackValue = GetJsonpCallback(context.Request);
- if (!string.IsNullOrWhiteSpace(callbackValue))
- {
- var callback = "callback=" + callbackValue;
- if (parameters.Contains("&" + callback)) parameters = parameters.Replace("&" + callback, string.Empty);
- if (parameters.Contains(callback + "&")) parameters = parameters.Replace(callback + "&", string.Empty);
- if (parameters.Contains("-" + callback)) parameters = parameters.Replace("-" + callback, string.Empty);
- if (parameters.EndsWith("&")) parameters = parameters.TrimEnd('&');
- }
- }
- else
- {
- parameters = "-" + string.Join("&", actionParameters);
- }
+ var callbackValue = GetJsonpCallback(context.Request);
+ if (!string.IsNullOrWhiteSpace(callbackValue))
+ {
+ var callback = "callback=" + callbackValue;
+ if (parameters.Contains("&" + callback)) parameters = parameters.Replace("&" + callback, string.Empty);
+ if (parameters.Contains(callback + "&")) parameters = parameters.Replace(callback + "&", string.Empty);
+ if (parameters.Contains("-" + callback)) parameters = parameters.Replace("-" + callback, string.Empty);
+ if (parameters.EndsWith("&")) parameters = parameters.TrimEnd('&');
+ }
+ }
+ else
+ {
+ parameters = "-" + string.Join("&", actionParameters);
+ }
- if (parameters == "-") parameters = string.Empty;
+ if (parameters == "-") parameters = string.Empty;
- var cachekey = string.Format("{0}{1}:{2}", key, parameters, mediaType);
- return cachekey;
- }
+ var cachekey = string.Format("{0}{1}:{2}", key, parameters, mediaType);
+ return cachekey;
+ }
- private string GetJsonpCallback(HttpRequestMessage request)
- {
- var callback = string.Empty;
- if (request.Method == HttpMethod.Get)
- {
- var query = request.GetQueryNameValuePairs();
+ private string GetJsonpCallback(HttpRequestMessage request)
+ {
+ var callback = string.Empty;
+ if (request.Method == HttpMethod.Get)
+ {
+ var query = request.GetQueryNameValuePairs();
- if (query != null)
- {
- var queryVal = query.FirstOrDefault(x => x.Key.ToLower() == "callback");
- if (!queryVal.Equals(default(KeyValuePair))) callback = queryVal.Value;
- }
- }
- return callback;
- }
+ if (query != null)
+ {
+ var queryVal = query.FirstOrDefault(x => x.Key.ToLower() == "callback");
+ if (!queryVal.Equals(default(KeyValuePair))) callback = queryVal.Value;
+ }
+ }
+ return callback;
+ }
- private string GetValue(object val)
- {
- if (val is IEnumerable && !(val is string))
- {
- var concatValue = string.Empty;
- var paramArray = val as IEnumerable;
- return paramArray.Cast