using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; using System; using System.Linq; using System.Threading.Tasks; using Timeline.Models.Http; using static Timeline.Resources.Helper.DataCacheHelper; namespace Timeline.Helpers { public interface ICacheableData { string Type { get; } #pragma warning disable CA1819 // Properties should not return arrays byte[] Data { get; } #pragma warning restore CA1819 // Properties should not return arrays DateTime? LastModified { get; } } public class CacheableData : ICacheableData { public CacheableData(string type, byte[] data, DateTime? lastModified) { Type = type; Data = data; LastModified = lastModified; } public string Type { get; set; } #pragma warning disable CA1819 // Properties should not return arrays public byte[] Data { get; set; } #pragma warning restore CA1819 // Properties should not return arrays public DateTime? LastModified { get; set; } } public interface ICacheableDataProvider { Task GetDataETag(); Task GetData(); } public class DelegateCacheableDataProvider : ICacheableDataProvider { private readonly Func> _getDataETagDelegate; private readonly Func> _getDataDelegate; public DelegateCacheableDataProvider(Func> getDataETagDelegate, Func> getDataDelegate) { _getDataETagDelegate = getDataETagDelegate; _getDataDelegate = getDataDelegate; } public Task GetData() { return _getDataDelegate(); } public Task GetDataETag() { return _getDataETagDelegate(); } } public static class DataCacheHelper { public static async Task GenerateActionResult(Controller controller, ICacheableDataProvider provider) { const string IfNonMatchHeaderKey = "If-None-Match"; const string ETagHeaderKey = "ETag"; var loggerFactory = controller.HttpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger(typeof(DataCacheHelper)); var eTagValue = await provider.GetDataETag(); eTagValue = '"' + eTagValue + '"'; var eTag = new EntityTagHeaderValue(eTagValue); if (controller.Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var value)) { if (!EntityTagHeaderValue.TryParseStrictList(value, out var eTagList)) { logger.LogInformation(Log.Format(LogBadIfNoneMatch, ("Header Value", value))); return controller.BadRequest(ErrorResponse.Common.Header.IfNonMatch_BadFormat()); } if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null) { controller.Response.Headers.Add(ETagHeaderKey, eTagValue); logger.LogInformation(LogResultNotModified); return controller.StatusCode(StatusCodes.Status304NotModified); } } var data = await provider.GetData(); logger.LogInformation(LogResultData); return controller.File(data.Data, data.Type, data.LastModified, eTag); } public static Task GenerateActionResult(Controller controller, Func> getDataETagDelegate, Func> getDataDelegate) { return GenerateActionResult(controller, new DelegateCacheableDataProvider(getDataETagDelegate, getDataDelegate)); } } }