From 3590f87e4324914ef4ea0cf208c8448916726211 Mon Sep 17 00:00:00 2001 From: Steven Hildreth Date: Sun, 11 Nov 2018 08:46:09 -0600 Subject: [PATCH] WIP --- RoadieApi/Controllers/ImageController.cs | 27 +--- RoadieApi/Services/ImageService.cs | 18 ++- RoadieApi/Startup.cs | 4 +- RoadieApi/appsettings.json | 5 +- RoadieLibrary/Caching/CacheManagerBase.cs | 6 +- .../Caching/DictionaryCacheManager.cs | 119 ++++++++++++++++++ RoadieLibrary/Caching/MemoryCacheManager.cs | 4 +- RoadieLibrary/Caching/RedisCacheManager.cs | 8 +- RoadieLibrary/Extensions/GenericExt.cs | 36 +++++- RoadieLibrary/FileOperationResult.cs | 4 + RoadieLibrary/OperationResult.cs | 1 + 11 files changed, 185 insertions(+), 47 deletions(-) create mode 100644 RoadieLibrary/Caching/DictionaryCacheManager.cs diff --git a/RoadieApi/Controllers/ImageController.cs b/RoadieApi/Controllers/ImageController.cs index 4c8b1e5..3fc6c12 100644 --- a/RoadieApi/Controllers/ImageController.cs +++ b/RoadieApi/Controllers/ImageController.cs @@ -19,7 +19,7 @@ namespace Roadie.Api.Controllers [Produces("application/json")] [Route("image")] [ApiController] - [Authorize] + // [Authorize] public class ImageController : EntityControllerBase { private IImageService ImageService { get; } @@ -37,29 +37,6 @@ namespace Roadie.Api.Controllers // return Ok(this._RoadieDbContext.Tracks.ProjectToType()); //} - //[HttpGet("{id}")] - //[ProducesResponseType(200)] - //[ProducesResponseType(404)] - //public IActionResult Get(Guid id) - //{ - // var key = id.ToString(); - // var result = this._cacheManager.Get(key, () => - // { - // var d = this._RoadieDbContext.Images.FirstOrDefault(x => x.RoadieId == id); - // if (d != null) - // { - // return d.Adapt(); - // } - // return null; - // }, key); - // if (result == null) - // { - // return NotFound(); - // } - // return Ok(result); - //} - - [HttpGet("{id}")] [ProducesResponseType(200)] [ProducesResponseType(404)] @@ -77,7 +54,7 @@ namespace Roadie.Api.Controllers } return File(fileContents:result.Data.Bytes, contentType: result.ContentType, - fileDownloadName: result.Data.Caption ?? id.ToString(), + fileDownloadName: $"{ result.Data.Caption ?? id.ToString()}.jpg", lastModified: result.LastModified, entityTag: result.ETag); } diff --git a/RoadieApi/Services/ImageService.cs b/RoadieApi/Services/ImageService.cs index 611a2d4..ed57bab 100644 --- a/RoadieApi/Services/ImageService.cs +++ b/RoadieApi/Services/ImageService.cs @@ -6,6 +6,8 @@ using Roadie.Library; using Roadie.Library.Caching; using Roadie.Library.Configuration; using Roadie.Library.Encoding; +using Roadie.Library.Extensions; +using Roadie.Library.Imaging; using Roadie.Library.Models; using Roadie.Library.Utility; using System; @@ -31,21 +33,25 @@ namespace Roadie.Api.Services public async Task> ImageById(Guid id, int? width, int? height, EntityTagHeaderValue etag = null) { var sw = Stopwatch.StartNew(); - sw.Start(); - var cacheKey = string.Format("urn:image_by_id_operation:{0}", id); - var result = await this.CacheManager.GetAsync>(cacheKey, async () => + var result = (await this.CacheManager.GetAsync($"urn:image_by_id_operation:{id}", async () => { return await this.ImageByIdAction(id, etag); - }, data.Image.CacheRegionKey(id)); - + }, data.Image.CacheRegionKey(id))).Adapt>(); if (result.ETag == etag) { return new FileOperationResult(OperationMessages.NotModified); } + if ((width.HasValue || height.HasValue) && result?.Data?.Bytes != null) + { + result.Data.Bytes = ImageHelper.ResizeImage(result?.Data?.Bytes, width.Value, height.Value); + result.ETag = EtagHelper.GenerateETag(this.HttpEncoder, result.Data.Bytes); + result.LastModified = DateTime.UtcNow; + this.Logger.LogInformation($"ImageById: Resized [{ id }], Width [{ width.Value }], Height [{ height.Value }]"); + } sw.Stop(); return new FileOperationResult(result.Messages) { - Data = result?.Data, + Data = result.Data, ETag = result.ETag, LastModified = result.LastModified, ContentType = result.ContentType, diff --git a/RoadieApi/Startup.cs b/RoadieApi/Startup.cs index d3a14df..4ca40b6 100644 --- a/RoadieApi/Startup.cs +++ b/RoadieApi/Startup.cs @@ -61,8 +61,6 @@ namespace Roadie.Api app.UseDeveloperExceptionPage(); } - loggerFactory.AddConsole(LogLevel.Trace); - app.UseCors("Cors"); app.UseAuthentication(); //app.UseSwagger(); @@ -93,7 +91,7 @@ namespace Roadie.Api services.AddSingleton(); - var cacheManager = new MemoryCacheManager(this._loggerFactory.CreateLogger(), new CachePolicy(TimeSpan.FromHours(4))); + var cacheManager = new DictionaryCacheManager(this._loggerFactory.CreateLogger(), new CachePolicy(TimeSpan.FromHours(4))); services.AddSingleton(cacheManager); services.AddDbContextPool( diff --git a/RoadieApi/appsettings.json b/RoadieApi/appsettings.json index ae9d6a5..04e7199 100644 --- a/RoadieApi/appsettings.json +++ b/RoadieApi/appsettings.json @@ -4,9 +4,8 @@ "IncludeScopes": false, "Console": { "LogLevel": { - "Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning", - "Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug", - "Microsoft.AspNetCore.Mvc.Razor": "Error", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning", "Default": "Trace" } }, diff --git a/RoadieLibrary/Caching/CacheManagerBase.cs b/RoadieLibrary/Caching/CacheManagerBase.cs index 5e03a2d..64c0b62 100644 --- a/RoadieLibrary/Caching/CacheManagerBase.cs +++ b/RoadieLibrary/Caching/CacheManagerBase.cs @@ -8,12 +8,12 @@ namespace Roadie.Library.Caching public abstract class CacheManagerBase : ICacheManager { protected readonly CachePolicy _defaultPolicy = null; - protected readonly ILogger _logger = null; + protected ILogger Logger { get; } protected readonly JsonSerializerSettings _serializerSettings = null; public CacheManagerBase(ILogger logger, CachePolicy defaultPolicy) { - this._logger = logger; + this.Logger = logger; this._defaultPolicy = defaultPolicy; this._serializerSettings = new JsonSerializerSettings { @@ -64,7 +64,7 @@ namespace Roadie.Library.Caching } catch (Exception ex) { - this._logger.LogError(ex, null, null); + this.Logger.LogError(ex, null, null); } return default(TOut); } diff --git a/RoadieLibrary/Caching/DictionaryCacheManager.cs b/RoadieLibrary/Caching/DictionaryCacheManager.cs new file mode 100644 index 0000000..5731a67 --- /dev/null +++ b/RoadieLibrary/Caching/DictionaryCacheManager.cs @@ -0,0 +1,119 @@ +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading.Tasks; + +namespace Roadie.Library.Caching +{ + public class DictionaryCacheManager : CacheManagerBase + { + private Dictionary Cache { get; } + + public DictionaryCacheManager(ILogger logger, CachePolicy defaultPolicy) + : base(logger, defaultPolicy) + { + this.Cache = new Dictionary(); + } + + public override bool Add(string key, TCacheValue value) + { + if(this.Cache.ContainsKey(key)) + { + this.Cache.Remove(key); + } + this.Cache.Add(key, value); + return true; + } + + public override bool Add(string key, TCacheValue value, string region) + { + return this.Add(key, value); + } + + public override bool Add(string key, TCacheValue value, CachePolicy policy) + { + return this.Add(key, value); + } + + public override bool Add(string key, TCacheValue value, string region, CachePolicy policy) + { + return this.Add(key, value); + } + + public override void Clear() + { + this.Cache.Clear(); + } + + public override void ClearRegion(string region) + { + this.Clear(); + } + + public override bool Exists(string key) + { + return this.Cache.ContainsKey(key); + } + + public override bool Exists(string key, string region) + { + return this.Exists(key); + } + + public override TOut Get(string key) + { + if(!this.Cache.ContainsKey(key)) + { + return default(TOut); + } + return (TOut)this.Cache[key]; + } + + public override TOut Get(string key, string region) + { + return Get(key); + } + + public override TOut Get(string key, Func getItem, string region) + { + return Get(key); + } + + public override TOut Get(string key, Func getItem, string region, CachePolicy policy) + { + return Get(key); + } + + public override bool Remove(string key) + { + if(this.Cache.ContainsKey(key)) + { + this.Cache.Remove(key); + } + return true; + } + + public override bool Remove(string key, string region) + { + return this.Remove(key); + } + + public async override Task GetAsync(string key, Func> getItem, string region) + { + var r = this.Get(key, region); + if (r == null) + { + r = await getItem(); + this.Add(key, r, region); + this.Logger.LogInformation($"-+> Cache Miss for Key [{ key }], Region [{ region }]"); + } + else + { + this.Logger.LogInformation($"-!> Cache Hit for Key [{ key }], Region [{ region }]"); + } + return r; + } + } +} \ No newline at end of file diff --git a/RoadieLibrary/Caching/MemoryCacheManager.cs b/RoadieLibrary/Caching/MemoryCacheManager.cs index 02b9afb..9f37974 100644 --- a/RoadieLibrary/Caching/MemoryCacheManager.cs +++ b/RoadieLibrary/Caching/MemoryCacheManager.cs @@ -106,11 +106,11 @@ namespace Roadie.Library.Caching { r = await getItem(); this.Add(key, r, region); - Trace.WriteLine($"-+> Cache Miss for Key [{ key }], Region [{ region }]"); + this.Logger.LogInformation($"-+> Cache Miss for Key [{ key }], Region [{ region }]"); } else { - Trace.WriteLine($"-!> Cache Hit for Key [{ key }], Region [{ region }]"); + this.Logger.LogInformation($"-!> Cache Hit for Key [{ key }], Region [{ region }]"); } return r; } diff --git a/RoadieLibrary/Caching/RedisCacheManager.cs b/RoadieLibrary/Caching/RedisCacheManager.cs index e401c87..d9b5ff9 100644 --- a/RoadieLibrary/Caching/RedisCacheManager.cs +++ b/RoadieLibrary/Caching/RedisCacheManager.cs @@ -62,7 +62,7 @@ namespace Roadie.Library.Caching { if (this._doTraceLogging) { - this._logger.LogTrace("Added [{0}], Region [{1}]", key, region); + this.Logger.LogTrace("Added [{0}], Region [{1}]", key, region); } return this.Redis.StringSet(key, this.Serialize(value)); } @@ -74,7 +74,7 @@ namespace Roadie.Library.Caching server.FlushAllDatabases(); if (this._doTraceLogging) { - this._logger.LogTrace("Cleared Cache"); + this.Logger.LogTrace("Cleared Cache"); } } @@ -156,12 +156,12 @@ namespace Roadie.Library.Caching { if (this._doTraceLogging) { - this._logger.LogTrace("Get Cache Miss Key [{0}], Region [{1}]", key, region); + this.Logger.LogTrace("Get Cache Miss Key [{0}], Region [{1}]", key, region); } } else if (this._doTraceLogging) { - this._logger.LogTrace("Get Cache Hit Key [{0}], Region [{1}]", key, region); + this.Logger.LogTrace("Get Cache Hit Key [{0}], Region [{1}]", key, region); } return result; } diff --git a/RoadieLibrary/Extensions/GenericExt.cs b/RoadieLibrary/Extensions/GenericExt.cs index 5772571..41431d2 100644 --- a/RoadieLibrary/Extensions/GenericExt.cs +++ b/RoadieLibrary/Extensions/GenericExt.cs @@ -1,5 +1,9 @@ -using System.Linq; +using System; +using System.IO; +using System.Linq; using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; namespace Roadie.Library.Extensions { @@ -19,5 +23,35 @@ namespace Roadie.Library.Extensions return OriginalEntity; } + + /// + /// Perform a deep Copy of the object using a BinaryFormatter. + /// IMPORTANT: the object class must be marked as [Serializable] and have an parameterless constructor. + /// + /// The type of object being copied. + /// The object instance to copy. + /// The copied object. + public static T Clone(this T source) + { + if (!typeof(T).IsSerializable) + { + throw new ArgumentException("The type must be serializable.", "source"); + } + + // Don't serialize a null object, simply return the default for that object + if (Object.ReferenceEquals(source, null)) + { + return default(T); + } + + IFormatter formatter = new BinaryFormatter(); + using (Stream stream = new MemoryStream()) + { + formatter.Serialize(stream, source); + stream.Seek(0, SeekOrigin.Begin); + return (T)formatter.Deserialize(stream); + } + } + } } \ No newline at end of file diff --git a/RoadieLibrary/FileOperationResult.cs b/RoadieLibrary/FileOperationResult.cs index 5d7db39..213431a 100644 --- a/RoadieLibrary/FileOperationResult.cs +++ b/RoadieLibrary/FileOperationResult.cs @@ -14,6 +14,10 @@ namespace Roadie.Library public DateTimeOffset? LastModified { get; set; } public string ContentType { get; set; } + public FileOperationResult() + { + } + public FileOperationResult(string message) { this.AddMessage(message); diff --git a/RoadieLibrary/OperationResult.cs b/RoadieLibrary/OperationResult.cs index 396d8e7..f94e154 100644 --- a/RoadieLibrary/OperationResult.cs +++ b/RoadieLibrary/OperationResult.cs @@ -4,6 +4,7 @@ using System.Linq; namespace Roadie.Library { + [Serializable] public class OperationResult { private List _errors;