/// <summary> /// Sets the cache-control header. /// </summary> /// <param name="bucketInfo">The bucket info dictionary.</param> /// <param name="value">The cache control values.</param> public static void SetCacheControl(this BucketInfo bucketInfo, CacheControlHeaderValue value) { if (value != null) { bucketInfo.Add("Cache-Control", value.ToString()); } }
public void ToString_UseRequestDirectiveValues_AllSerializedCorrectly() { CacheControlHeaderValue cacheControl = new CacheControlHeaderValue(); Assert.Equal("", cacheControl.ToString()); // Note that we allow all combinations of all properties even though the RFC specifies rules what value // can be used together. // Also for property pairs (bool property + collection property) like 'NoCache' and 'NoCacheHeaders' the // caller needs to set the bool property in order for the collection to be populated as string. // Cache Request Directive sample cacheControl.NoStore = true; Assert.Equal("no-store", cacheControl.ToString()); cacheControl.NoCache = true; Assert.Equal("no-store, no-cache", cacheControl.ToString()); cacheControl.MaxAge = new TimeSpan(0, 1, 10); Assert.Equal("no-store, no-cache, max-age=70", cacheControl.ToString()); cacheControl.MaxStale = true; Assert.Equal("no-store, no-cache, max-age=70, max-stale", cacheControl.ToString()); cacheControl.MaxStaleLimit = new TimeSpan(0, 2, 5); Assert.Equal("no-store, no-cache, max-age=70, max-stale=125", cacheControl.ToString()); cacheControl.MinFresh = new TimeSpan(0, 3, 0); Assert.Equal("no-store, no-cache, max-age=70, max-stale=125, min-fresh=180", cacheControl.ToString()); cacheControl = new CacheControlHeaderValue(); cacheControl.NoTransform = true; Assert.Equal("no-transform", cacheControl.ToString()); cacheControl.OnlyIfCached = true; Assert.Equal("no-transform, only-if-cached", cacheControl.ToString()); cacheControl.Extensions.Add(new NameValueHeaderValue("custom")); cacheControl.Extensions.Add(new NameValueHeaderValue("customName", "customValue")); Assert.Equal("no-transform, only-if-cached, custom, customName=customValue", cacheControl.ToString()); cacheControl = new CacheControlHeaderValue(); cacheControl.Extensions.Add(new NameValueHeaderValue("custom")); Assert.Equal("custom", cacheControl.ToString()); }
public void ToString_UseResponseDirectiveValues_AllSerializedCorrectly() { CacheControlHeaderValue cacheControl = new CacheControlHeaderValue(); Assert.Equal("", cacheControl.ToString()); cacheControl.NoCache = true; Assert.Equal("no-cache", cacheControl.ToString()); cacheControl.NoCacheHeaders.Add("token1"); Assert.Equal("no-cache=\"token1\"", cacheControl.ToString()); cacheControl.Public = true; Assert.Equal("public, no-cache=\"token1\"", cacheControl.ToString()); cacheControl = new CacheControlHeaderValue(); cacheControl.Private = true; Assert.Equal("private", cacheControl.ToString()); cacheControl.PrivateHeaders.Add("token2"); cacheControl.PrivateHeaders.Add("token3"); Assert.Equal("private=\"token2, token3\"", cacheControl.ToString()); cacheControl.MustRevalidate = true; Assert.Equal("must-revalidate, private=\"token2, token3\"", cacheControl.ToString()); cacheControl.ProxyRevalidate = true; Assert.Equal("must-revalidate, proxy-revalidate, private=\"token2, token3\"", cacheControl.ToString()); }
public static IResponse EnableCaching(this IResponse response, TimeSpan?timeSpan = null) { return(new ResponseDecorator(response, ctx => { #if !DEBUG var cacheControl = new CacheControlHeaderValue { Public = true, MaxAge = timeSpan ?? TimeSpan.FromDays(150) }; #else var cacheControl = new CacheControlHeaderValue { NoCache = true, }; #endif ctx.Response.Headers[HeaderNames.CacheControl] = cacheControl.ToString(); })); }
public void ToString_NegativeValues_UsesMinusSignRegardlessOfCurrentCulture() { RemoteExecutor.Invoke(() => { var cacheControl = new CacheControlHeaderValue() { MaxAge = new TimeSpan(0, 0, -1), MaxStale = true, MaxStaleLimit = new TimeSpan(0, 0, -2), MinFresh = new TimeSpan(0, 0, -3), SharedMaxAge = new TimeSpan(0, 0, -4) }; var ci = (CultureInfo)CultureInfo.CurrentCulture.Clone(); ci.NumberFormat.NegativeSign = "n"; CultureInfo.CurrentCulture = ci; Assert.Equal("max-age=-1, s-maxage=-4, max-stale=-2, min-fresh=-3", cacheControl.ToString()); }).Dispose(); }
private static void NoCacheHeaders(ActionExecutedContext context) { var headerCache = new CacheControlHeaderValue() { NoCache = true }; context.HttpContext.Response.Headers.Add("Cache-Control", headerCache.ToString()); StringValues dateValue; context.HttpContext.Response.Headers.TryGetValue("Date", out dateValue); if (string.IsNullOrEmpty(dateValue)) { context.HttpContext.Response.Headers.Add("Date", DateTimeOffset.UtcNow.ToString()); } context.HttpContext.Response.Headers.Add("Expires", DateTimeOffset.UtcNow.ToString()); }
private static void InitializeAzureStorage(IServiceProvider services, AzureBlobStorageImageProviderOptions options) { // Upload an image to the Azure Test Storage; AzureBlobContainerClientOptions containerOptions = options.BlobContainers.First(); var container = new BlobContainerClient(containerOptions.ConnectionString, containerOptions.ContainerName); container.CreateIfNotExists(PublicAccessType.Blob); #if NETCOREAPP2_1 IHostingEnvironment environment = services.GetRequiredService <IHostingEnvironment>(); #else IWebHostEnvironment environment = services.GetRequiredService <IWebHostEnvironment>(); #endif BlobClient blob = container.GetBlobClient(TestConstants.ImagePath); if (!blob.Exists()) { IFileInfo file = environment.WebRootFileProvider.GetFileInfo(TestConstants.ImagePath); using Stream stream = file.CreateReadStream(); // Set the max-age property so we get coverage for testing is in our Azure provider. var cacheControl = new CacheControlHeaderValue { Public = true, MaxAge = TimeSpan.FromDays(7), MustRevalidate = true }; var headers = new BlobHttpHeaders { CacheControl = cacheControl.ToString(), }; blob.Upload(stream, httpHeaders: headers); } }
private void AddCacheHeaders(ActionExecutedContext context) { if (context.HttpContext.Response.StatusCode == 200) { var cacheControlHeaderValue = new CacheControlHeaderValue() { NoCache = false, Public = (CacheType == ClientCacheControl.Public), Private = (CacheType == ClientCacheControl.Private), MaxAge = TimeSpan.FromSeconds(CacheSeconds) }; context.HttpContext.Response.Headers.Add("Cache-Control", cacheControlHeaderValue.ToString()); StringValues dateValue; context.HttpContext.Response.Headers.TryGetValue("Date", out dateValue); if (string.IsNullOrEmpty(dateValue)) { context.HttpContext.Response.Headers.Add("Date", DateTimeOffset.UtcNow.ToString()); context.HttpContext.Response.Headers.TryGetValue("Date", out dateValue); } context.HttpContext.Response.Headers.Add("Expires", DateTimeOffset.UtcNow.AddSeconds(CacheSeconds).ToString()); } }
/// <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()); } }); }
public void SetCacheControlHeaders(HttpCacheability?httpCacheability = null, DateTime?lastModified = null, TimeSpan?maxAge = null) { // Cache control headers are stored temporarily in an aggregated object // and written to the single response header. The reason behind this is // that this method may be called multiple times with different kinds // of parameters. var writeCacheControlHeader = false; try { if (httpCacheability.HasValue) { switch (httpCacheability) { case HttpCacheability.NoCache: _cacheHeaders.NoCache = true; _cacheHeaders.NoStore = true; _cacheHeaders.ProxyRevalidate = true; _cacheHeaders.MustRevalidate = true; break; case HttpCacheability.Private: _cacheHeaders.Private = true; break; case HttpCacheability.Public: _cacheHeaders.Public = true; break; default: throw new ArgumentOutOfRangeException(nameof(httpCacheability), httpCacheability, null); } writeCacheControlHeader = true; } if (lastModified.HasValue) { // make sure that the date is in the past var t = lastModified.Value; if (t > DateTime.UtcNow) { t = DateTime.UtcNow; } _context.Response.Headers[HeaderNames.LastModified] = t.ToUniversalTime().ToString("r"); } if (maxAge.HasValue) { _cacheHeaders.MaxAge = maxAge.Value; writeCacheControlHeader = true; } if (writeCacheControlHeader) { _context.Response.Headers[HeaderNames.CacheControl] = _cacheHeaders.ToString(); } } catch (Exception ex) { SnLog.WriteException(ex, "Exception in SetCacheControlHeaders. " + $"httpCacheability:'{httpCacheability}' lastModified:'{lastModified}' maxAge:'{maxAge}'", EventId.Portal); } }
/// <summary> /// Gets a FilePathResult based on the file's path. It sets the mime type based on the file's extension. /// </summary> /// <param name="downloadFileName">If specified, the browser will not try to process the file directly (such as PDF files) and instead always opens the file download dialogue.</param> protected async Task <ActionResult> File(Blob file, string downloadFileName = null, CacheControlHeaderValue cacheControl = null, bool showInline = false) { if (cacheControl != null) { Response.Headers.Remove(Microsoft.Net.Http.Headers.HeaderNames.Pragma); Response.Headers.Remove(Microsoft.Net.Http.Headers.HeaderNames.CacheControl); Response.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, cacheControl.ToString()); } var cd = new System.Net.Mime.ContentDisposition { FileName = downloadFileName.Or(file.FileName), Inline = showInline, }; if (showInline) { Response.Headers.Add("X-Content-Type-Options", "nosniff"); } Response.Headers.Remove("Content-Disposition"); Response.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.ContentDisposition, cd.ToString()); return(File(await file.GetFileDataAsync(), file.GetMimeType())); }
public static async Task <ActionResult> GenerateActionResult(ControllerBase controller, ICacheableDataProvider provider, TimeSpan?maxAge = null) { const string CacheControlHeaderKey = "Cache-Control"; const string IfNonMatchHeaderKey = "If-None-Match"; const string IfModifiedSinceHeaderKey = "If-Modified-Since"; const string ETagHeaderKey = "ETag"; const string LastModifiedHeaderKey = "Last-Modified"; string GenerateCacheControlHeaderValue() { var cacheControlHeader = new CacheControlHeaderValue() { NoCache = true, NoStore = false, MaxAge = maxAge ?? TimeSpan.FromDays(14), Private = true, MustRevalidate = true }; return(cacheControlHeader.ToString()); } var digest = await provider.GetDigest(); var eTagValue = $"\"{digest.ETag}\""; var eTag = new EntityTagHeaderValue(eTagValue); ActionResult Generate304Result() { controller.Response.Headers.Add(ETagHeaderKey, eTagValue); controller.Response.Headers.Add(LastModifiedHeaderKey, digest.LastModified.ToString("R")); controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue()); return(controller.StatusCode(StatusCodes.Status304NotModified, null)); } if (controller.Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var ifNonMatchHeaderValue)) { if (!EntityTagHeaderValue.TryParseList(ifNonMatchHeaderValue, out var eTagList)) { return(controller.BadRequest(new CommonResponse(ErrorCodes.Common.Header.IfNonMatch_BadFormat, "Header If-None-Match is of bad format."))); } if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null) { return(Generate304Result()); } } else if (controller.Request.Headers.TryGetValue(IfModifiedSinceHeaderKey, out var ifModifiedSinceHeaderValue)) { if (!DateTime.TryParse(ifModifiedSinceHeaderValue, out var headerValue)) { return(controller.BadRequest(new CommonResponse(ErrorCodes.Common.Header.IfModifiedSince_BadFormat, "Header If-Modified-Since is of bad format."))); } if (headerValue > digest.LastModified) { return(Generate304Result()); } } var data = await provider.GetData(); controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue()); return(controller.File(data.Data, data.ContentType, digest.LastModified, eTag)); }
public static string ToSafeString(this CacheControlHeaderValue cacheControl) { return(cacheControl == null ? "(null)" : cacheControl.ToString()); }
/// <summary> /// Gets a FilePathResult based on the file's path. It sets the mime type based on the file's extension. /// </summary> /// <param name="downloadFileName">If specified, the browser will not try to process the file directly (such as PDF files) and instead always opens the file download dialogue.</param> protected async Task <ActionResult> File(Blob file, string downloadFileName = null, CacheControlHeaderValue cacheControl = null) { if (cacheControl != null) { Response.Headers.Remove(Microsoft.Net.Http.Headers.HeaderNames.Pragma); Response.Headers.Remove(Microsoft.Net.Http.Headers.HeaderNames.CacheControl); Response.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, cacheControl.ToString()); } return(File(await file.GetFileDataAsync(), file.GetMimeType(), downloadFileName.Or(file.FileName))); }
private static async Task InitializeAWSStorageAsync(IServiceProvider services, AWSS3StorageImageProviderOptions options) { // Upload an image to the AWS Test Storage; AWSS3BucketClientOptions bucketOptions = options.S3Buckets.First(); AmazonS3Client amazonS3Client = AmazonS3ClientFactory.CreateClient(bucketOptions); ListBucketsResponse listBucketsResponse = await amazonS3Client.ListBucketsAsync(); bool foundBucket = false; foreach (S3Bucket b in listBucketsResponse.Buckets) { if (b.BucketName == bucketOptions.BucketName) { foundBucket = true; break; } } if (!foundBucket) { try { var putBucketRequest = new PutBucketRequest { BucketName = bucketOptions.BucketName, BucketRegion = bucketOptions.Region, CannedACL = S3CannedACL.PublicRead }; await amazonS3Client.PutBucketAsync(putBucketRequest); } catch (AmazonS3Exception e) { // CI tests are run in parallel and can sometimes return a // false negative for the existance of a bucket. if (string.Equals(e.ErrorCode, "BucketAlreadyExists")) { return; } throw; } } #if NETCOREAPP2_1 IHostingEnvironment environment = services.GetRequiredService <IHostingEnvironment>(); #else IWebHostEnvironment environment = services.GetRequiredService <IWebHostEnvironment>(); #endif try { GetObjectRequest request = new() { BucketName = bucketOptions.BucketName, Key = TestConstants.ImagePath }; await amazonS3Client.GetObjectAsync(request); } catch { IFileInfo file = environment.WebRootFileProvider.GetFileInfo(TestConstants.ImagePath); using Stream stream = file.CreateReadStream(); // Set the max-age property so we get coverage for testing in our AWS provider. var cacheControl = new CacheControlHeaderValue { Public = true, MaxAge = TimeSpan.FromDays(7), MustRevalidate = true }; var putRequest = new PutObjectRequest() { BucketName = bucketOptions.BucketName, Key = TestConstants.ImagePath, Headers = { CacheControl = cacheControl.ToString() }, ContentType = " image/png", InputStream = stream }; await amazonS3Client.PutObjectAsync(putRequest); } }