From 8a1ecbf49673cb2bed538ac8bc4e82691b90d973 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 13 Mar 2020 17:05:03 +0800 Subject: Abstract out data cache helper. --- Timeline/Controllers/UserAvatarController.cs | 31 +---- Timeline/Helpers/DataCacheHelper.cs | 108 +++++++++++++++++ .../Controllers/UserAvatarController.Designer.cs | 27 ----- .../Controllers/UserAvatarController.resx | 9 -- .../Resources/Helper/DataCacheHelper.Designer.cs | 90 ++++++++++++++ Timeline/Resources/Helper/DataCacheHelper.resx | 129 +++++++++++++++++++++ Timeline/Services/UserAvatarService.cs | 5 + Timeline/Timeline.csproj | 9 ++ 8 files changed, 345 insertions(+), 63 deletions(-) create mode 100644 Timeline/Helpers/DataCacheHelper.cs create mode 100644 Timeline/Resources/Helper/DataCacheHelper.Designer.cs create mode 100644 Timeline/Resources/Helper/DataCacheHelper.resx diff --git a/Timeline/Controllers/UserAvatarController.cs b/Timeline/Controllers/UserAvatarController.cs index f4f3db3e..f78dcb08 100644 --- a/Timeline/Controllers/UserAvatarController.cs +++ b/Timeline/Controllers/UserAvatarController.cs @@ -46,34 +46,11 @@ namespace Timeline.Controllers return NotFound(ErrorResponse.UserCommon.NotExist()); } - const string IfNonMatchHeaderKey = "If-None-Match"; - - var eTagValue = $"\"{await _service.GetAvatarETag(id)}\""; - var eTag = new EntityTagHeaderValue(eTagValue); - - if (Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var value)) + return await DataCacheHelper.GenerateActionResult(this, () => _service.GetAvatarETag(id), async () => { - if (!EntityTagHeaderValue.TryParseStrictList(value, out var eTagList)) - { - _logger.LogInformation(Log.Format(LogGetBadIfNoneMatch, - ("Username", username), ("If-None-Match", value))); - return BadRequest(ErrorResponse.Common.Header.IfNonMatch_BadFormat()); - } - - if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null) - { - Response.Headers.Add("ETag", eTagValue); - _logger.LogInformation(Log.Format(LogGetReturnNotModify, ("Username", username))); - return StatusCode(StatusCodes.Status304NotModified); - } - } - - var avatarInfo = await _service.GetAvatar(id); - var avatar = avatarInfo.Avatar; - - _logger.LogInformation(Log.Format(LogGetReturnData, ("Username", username))); - return File(avatar.Data, avatar.Type, new DateTimeOffset(avatarInfo.LastModified), eTag); - + var avatar = await _service.GetAvatar(id); + return avatar.ToCacheableData(); + }); } [HttpPut("users/{username}/avatar")] diff --git a/Timeline/Helpers/DataCacheHelper.cs b/Timeline/Helpers/DataCacheHelper.cs new file mode 100644 index 00000000..c13aaddb --- /dev/null +++ b/Timeline/Helpers/DataCacheHelper.cs @@ -0,0 +1,108 @@ +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)); + } + } +} diff --git a/Timeline/Resources/Controllers/UserAvatarController.Designer.cs b/Timeline/Resources/Controllers/UserAvatarController.Designer.cs index e6eeb1e8..b0c35ff9 100644 --- a/Timeline/Resources/Controllers/UserAvatarController.Designer.cs +++ b/Timeline/Resources/Controllers/UserAvatarController.Designer.cs @@ -96,33 +96,6 @@ namespace Timeline.Resources.Controllers { } } - /// - /// Looks up a localized string similar to Attempt to get a avatar with If-None-Match in bad format.. - /// - internal static string LogGetBadIfNoneMatch { - get { - return ResourceManager.GetString("LogGetBadIfNoneMatch", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Returned full data for a get avatar attempt.. - /// - internal static string LogGetReturnData { - get { - return ResourceManager.GetString("LogGetReturnData", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Returned NotModify for a get avatar attempt.. - /// - internal static string LogGetReturnNotModify { - get { - return ResourceManager.GetString("LogGetReturnNotModify", resourceCulture); - } - } - /// /// Looks up a localized string similar to Attempt to get a avatar of a non-existent user failed.. /// diff --git a/Timeline/Resources/Controllers/UserAvatarController.resx b/Timeline/Resources/Controllers/UserAvatarController.resx index 58860c83..864d96c0 100644 --- a/Timeline/Resources/Controllers/UserAvatarController.resx +++ b/Timeline/Resources/Controllers/UserAvatarController.resx @@ -129,15 +129,6 @@ Succeed to delete a avatar of a user. - - Attempt to get a avatar with If-None-Match in bad format. - - - Returned full data for a get avatar attempt. - - - Returned NotModify for a get avatar attempt. - Attempt to get a avatar of a non-existent user failed. diff --git a/Timeline/Resources/Helper/DataCacheHelper.Designer.cs b/Timeline/Resources/Helper/DataCacheHelper.Designer.cs new file mode 100644 index 00000000..acf56d13 --- /dev/null +++ b/Timeline/Resources/Helper/DataCacheHelper.Designer.cs @@ -0,0 +1,90 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Timeline.Resources.Helper { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class DataCacheHelper { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal DataCacheHelper() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Timeline.Resources.Helper.DataCacheHelper", typeof(DataCacheHelper).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Header If-None-Match is of bad format.. + /// + internal static string LogBadIfNoneMatch { + get { + return ResourceManager.GetString("LogBadIfNoneMatch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cache is invalid and data is returned.. + /// + internal static string LogResultData { + get { + return ResourceManager.GetString("LogResultData", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cache is valid and 304 Not Modified is returned.. + /// + internal static string LogResultNotModified { + get { + return ResourceManager.GetString("LogResultNotModified", resourceCulture); + } + } + } +} diff --git a/Timeline/Resources/Helper/DataCacheHelper.resx b/Timeline/Resources/Helper/DataCacheHelper.resx new file mode 100644 index 00000000..515cfa9b --- /dev/null +++ b/Timeline/Resources/Helper/DataCacheHelper.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Header If-None-Match is of bad format. + + + Cache is invalid and data is returned. + + + Cache is valid and 304 Not Modified is returned. + + \ No newline at end of file diff --git a/Timeline/Services/UserAvatarService.cs b/Timeline/Services/UserAvatarService.cs index 1b1be698..3ab8f14d 100644 --- a/Timeline/Services/UserAvatarService.cs +++ b/Timeline/Services/UserAvatarService.cs @@ -22,6 +22,11 @@ namespace Timeline.Services { public Avatar Avatar { get; set; } = default!; public DateTime LastModified { get; set; } + + public CacheableData ToCacheableData() + { + return new CacheableData(Avatar.Type, Avatar.Data, LastModified); + } } /// diff --git a/Timeline/Timeline.csproj b/Timeline/Timeline.csproj index 245ff3e7..739f79dd 100644 --- a/Timeline/Timeline.csproj +++ b/Timeline/Timeline.csproj @@ -72,6 +72,11 @@ True Filters.resx + + True + True + DataCacheHelper.resx + True True @@ -159,6 +164,10 @@ ResXFileCodeGenerator Filters.Designer.cs + + ResXFileCodeGenerator + DataCacheHelper.Designer.cs + ResXFileCodeGenerator Messages.Designer.cs -- cgit v1.2.3