internal Func <HttpRequestMessage, Task <HttpResponseMessage> > PutIfMatch()
        {
            return(request =>
            {
                if (request.Method != HttpMethod.Put)
                {
                    return null;
                }

                ICollection <EntityTagHeaderValue> matchTags = request.Headers.IfMatch;
                if (matchTags == null || matchTags.Count == 0)
                {
                    return null;
                }

                IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                string resource = UriTrimmer(request.RequestUri);
                EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers);
                TimedEntityTagHeaderValue actualEtag;

                bool matchFound = false;
                if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                {
                    if (matchTags.Any(etag => etag.Tag == actualEtag.Tag))
                    {
                        matchFound = true;
                    }
                }

                return matchFound
                    ? null
                    : new HttpResponseMessage(HttpStatusCode.PreconditionFailed).ToTask();
            });
        }
        /// <summary>
        /// This invalidates the resource based on routePattern
        /// for methods POST, PUT and DELETE.
        /// It also removes for all linked URLs
        /// </summary>
        /// <param name="entityTagKey"></param>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <returns></returns>
        internal Action InvalidateCache(
            EntityTagKey entityTagKey,
            HttpRequestMessage request,
            HttpResponseMessage response)
        {
            return
                (() =>
            {
                if (!request.Method.Method.IsIn("PUT", "DELETE", "POST"))
                {
                    return;
                }

                string uri = UriTrimmer(request.RequestUri);

                // remove pattern
                _entityTagStore.RemoveAllByRoutePattern(entityTagKey.RoutePattern);

                // remove all related URIs
                IEnumerable <string> linkedUrls = LinkedRoutePatternProvider(uri, request.Method);
                foreach (string linkedUrl in linkedUrls)
                {
                    _entityTagStore.RemoveAllByRoutePattern(linkedUrl);
                }
            });
        }
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > PutIfUnmodifiedSince()
        {
            return(request =>
            {
                if (request.Method != HttpMethod.Put)
                {
                    return null;
                }

                DateTimeOffset?ifUnmodifiedSince = request.Headers.IfUnmodifiedSince;
                if (ifUnmodifiedSince == null)
                {
                    return null;
                }

                DateTimeOffset modifiedInQuestion = ifUnmodifiedSince.Value;

                IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                string resource = UriTrimmer(request.RequestUri);
                EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers);
                TimedEntityTagHeaderValue actualEtag;

                bool isModified = false;
                if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                {
                    isModified = actualEtag.LastModified > modifiedInQuestion;
                }

                return isModified
                    ? new HttpResponseMessage(HttpStatusCode.PreconditionFailed).ToTask()
                    : null;
            });
        }
 protected void ExecuteCacheAdditionRules(EntityTagKey entityTagKey,
                                          HttpRequestMessage request,
                                          HttpResponseMessage response,
                                          IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders)
 {
     new[]
     {
         AddCaching(entityTagKey, request, response, varyHeaders), // general caching
     }
     .Chain()();
 }
 protected void ExecuteCacheInvalidationRules(EntityTagKey entityTagKey,
                                              HttpRequestMessage request,
                                              HttpResponseMessage response)
 {
     new[]
     {
         InvalidateCache(entityTagKey, request, response), // general invalidation
         PostInvalidationRule(entityTagKey, request, response)
     }
     .Chain()();
 }
 public void AddOrUpdate(EntityTagKey key, TimedEntityTagHeaderValue eTag)
 {
     _eTagCache.AddOrUpdate(key, eTag, (theKey, oldValue) => eTag);
     _routePatternCache.AddOrUpdate(key.RoutePattern, new HashSet <EntityTagKey> {
         key
     },
                                    (routePattern, hashSet) =>
     {
         hashSet.Add(key);
         return(hashSet);
     });
 }
        /// <summary>
        /// Adds caching for GET and PUT if
        /// cache control provided is not null
        /// With PUT, since cache has been alreay invalidated,
        /// we provide the new ETag (old one has been cleared in invalidation phase)
        /// </summary>
        /// <param name="entityTagKey"></param>
        /// <param name="request"></param>
        /// <param name="response"></param>
        /// <param name="varyHeaders"></param>
        /// <returns></returns>
        internal Action AddCaching(
            EntityTagKey entityTagKey,
            HttpRequestMessage request,
            HttpResponseMessage response,
            IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders)
        {
            return
                (() =>
            {
                CacheControlHeaderValue cacheControlHeaderValue = CacheControlHeaderProvider(request);
                if (cacheControlHeaderValue == null)
                {
                    return;
                }

                TimedEntityTagHeaderValue eTagValue;

                string uri = UriTrimmer(request.RequestUri);

                // in case of GET and no ETag
                // in case of PUT, we should return the new ETag of the resource
                // NOTE: No need to check if it is in the cache. If it were, it would not get
                // here
                if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Put)
                {
                    // create new ETag only if it does not already exist
                    if (!_entityTagStore.TryGetValue(entityTagKey, out eTagValue))
                    {
                        eTagValue = new TimedEntityTagHeaderValue(ETagValueGenerator(uri, varyHeaders));
                        _entityTagStore.AddOrUpdate(entityTagKey, eTagValue);
                    }

                    // set ETag
                    response.Headers.ETag = eTagValue.ToEntityTagHeaderValue();

                    // set last-modified
                    if (AddLastModifiedHeader && response.Content != null &&
                        !response.Content.Headers.Any(x => x.Key.Equals(HttpHeaderNames.LastModified,
                                                                        StringComparison.CurrentCultureIgnoreCase)))
                    {
                        response.Content.Headers.Add(HttpHeaderNames.LastModified, eTagValue.LastModified.ToString("r"));
                    }

                    // set Vary
                    if (AddVaryHeader && _varyByHeaders != null && _varyByHeaders.Length > 0)
                    {
                        response.Headers.Add(HttpHeaderNames.Vary, _varyByHeaders);
                    }

                    response.Headers.TryAddWithoutValidation(HttpHeaderNames.CacheControl, cacheControlHeaderValue.ToString());
                }
            });
        }
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfMatchNoneMatch()
        {
            return((request) =>
            {
                if (request.Method != HttpMethod.Get)
                {
                    return null;
                }

                ICollection <EntityTagHeaderValue> noneMatchTags = request.Headers.IfNoneMatch;
                ICollection <EntityTagHeaderValue> matchTags = request.Headers.IfMatch;

                if (matchTags.Count == 0 && noneMatchTags.Count == 0)
                {
                    return null; // no etag
                }

                if (matchTags.Count > 0 && noneMatchTags.Count > 0) // both if-match and if-none-match exist
                {
                    return new HttpResponseMessage(HttpStatusCode.BadRequest).ToTask();
                }

                bool isNoneMatch = noneMatchTags.Count > 0;
                ICollection <EntityTagHeaderValue> etags = isNoneMatch ? noneMatchTags : matchTags;

                string resource = UriTrimmer(request.RequestUri);
                IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers);
                // compare the Etag with the one in the cache
                // do conditional get.
                TimedEntityTagHeaderValue actualEtag = null;

                bool matchFound = false;
                if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                {
                    if (etags.Any(etag => etag.Tag == actualEtag.Tag))
                    {
                        matchFound = true;
                    }
                }
                return matchFound ^ isNoneMatch ? null : new NotModifiedResponse(actualEtag.ToEntityTagHeaderValue()).ToTask();
            });
        }
        internal Func <HttpRequestMessage, Task <HttpResponseMessage> > GetIfModifiedUnmodifiedSince()
        {
            return((request) =>
            {
                if (request.Method != HttpMethod.Get && request.Headers != null)
                {
                    return null;
                }

                DateTimeOffset?ifModifiedSince = request.Headers.IfModifiedSince;
                DateTimeOffset?ifUnmodifiedSince = request.Headers.IfUnmodifiedSince;

                if (ifModifiedSince == null && ifUnmodifiedSince == null)
                {
                    return null; // no etag
                }

                if (ifModifiedSince != null && ifUnmodifiedSince != null) // both exist
                {
                    return new HttpResponseMessage(HttpStatusCode.BadRequest).ToTask();
                }
                bool ifModified = (ifUnmodifiedSince == null);
                DateTimeOffset modifiedInQuestion = ifModified ? ifModifiedSince.Value : ifUnmodifiedSince.Value;

                IEnumerable <KeyValuePair <string, IEnumerable <string> > > headers =
                    request.Headers.Where(h => _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));
                string resource = UriTrimmer(request.RequestUri);
                EntityTagKey entityTagKey = EntityTagKeyGenerator(resource, headers);

                TimedEntityTagHeaderValue actualEtag;

                bool isModified = false;
                if (_entityTagStore.TryGetValue(entityTagKey, out actualEtag))
                {
                    isModified = actualEtag.LastModified > modifiedInQuestion;
                }

                return isModified ^ ifModified
                    ? new NotModifiedResponse(actualEtag.ToEntityTagHeaderValue()).ToTask()
                    : null;
            });
        }
        /// <summary>
        /// This is a scenario where we have a POST to a resource
        /// and it needs to invalidate the cache to that resource
        /// and all its linked URLs
        ///
        /// For example:
        /// POST /api/cars => invalidate /api/cars
        /// also it might invalidate /api/cars/fastest in which case
        /// /api/cars/fastest must be one of the linked URLs
        /// </summary>
        /// <param name="entityTagKey">entityTagKey</param>
        /// <param name="request">request</param>
        /// <param name="response">response</param>
        /// <returns>returns the function to execute</returns>
        internal Action PostInvalidationRule(
            EntityTagKey entityTagKey,
            HttpRequestMessage request,
            HttpResponseMessage response)
        {
            return(() =>
            {
                if (request.Method != HttpMethod.Post)
                {
                    return;
                }

                // if location header is set (for newly created resource), invalidate cache for it
                // this normally should not be necessary as the item is new and should not be in the cache
                // but releasing a non-existent item from cache should not have a big overhead
                if (response.Headers.Location != null)
                {
                    _entityTagStore.RemoveAllByRoutePattern(response.Headers.Location.ToString());
                }
            });
        }
        internal Func <Task <HttpResponseMessage>, HttpResponseMessage> GetCachingContinuation(HttpRequestMessage request)
        {
            return(task =>
            {
                HttpResponseMessage response = task.Result;
                int statusCode = (int)response.StatusCode;
                if (statusCode >= 300 || statusCode < 200) // only if successful carry on processing
                {
                    return response;
                }

                string uri = UriTrimmer(request.RequestUri);
                IEnumerable <KeyValuePair <string, IEnumerable <string> > > varyHeaders = request.Headers.Where(h =>
                                                                                                                _varyByHeaders.Any(v => v.Equals(h.Key, StringComparison.CurrentCultureIgnoreCase)));

                EntityTagKey eTagKey = EntityTagKeyGenerator(uri, varyHeaders);

                ExecuteCacheInvalidationRules(eTagKey, request, response);
                ExecuteCacheAdditionRules(eTagKey, request, response, varyHeaders);

                return response;
            });
        }
        public bool TryRemove(EntityTagKey key)
        {
            TimedEntityTagHeaderValue entityTagHeaderValue;

            return(_eTagCache.TryRemove(key, out entityTagHeaderValue));
        }
 public bool TryGetValue(EntityTagKey key, out TimedEntityTagHeaderValue eTag)
 {
     return(_eTagCache.TryGetValue(key, out eTag));
 }