From 39fed867c10ec610b9a5e399f68f8ee28f186c55 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 12 May 2020 10:21:40 +1000 Subject: [PATCH] Cleans up some routing, mvc base classes, gets controller specific model binding working, applies our own application model to our controllers --- .gitignore | 2 + ...racoApiBehaviorApplicationModelProvider.cs | 93 +++++++++++ .../UmbracoApiControllerAttribute.cs | 13 ++ .../Controllers/UmbracoApiControllerBase.cs | 28 ++-- ...ons.cs => ApplicationBuilderExtensions.cs} | 14 +- .../UmbracoCoreServiceCollectionExtensions.cs | 38 +++-- .../UmbracoWebServiceCollectionExtensions.cs | 151 ++++++++++++++++++ .../AngularJsonOnlyConfigurationAttribute.cs | 3 +- .../FeatureAuthorizeAttribute.cs | 0 .../Filters/HttpResponseExceptionFilter.cs | 5 + .../Install/InstallApiController.cs | 18 ++- .../Install/InstallController.cs | 6 +- .../UmbracoRequestLoggingMiddleware.cs | 14 +- .../Middleware/UmbracoRequestMiddleware.cs | 49 +++++- .../UmbracoJsonModelBinderProvider.cs | 117 ++++++++++++++ .../{Runtime => }/Profiler/WebProfiler.cs | 5 +- .../Profiler/WebProfilerComponent.cs | 2 +- .../Profiler/WebProfilerComposer.cs | 2 +- .../{Runtime => }/Profiler/WebProfilerHtml.cs | 3 +- .../Runtime/AspNetCoreComposer.cs | 2 +- .../Security/WebSecurity.cs | 56 +++++++ .../Umbraco.Web.Common.csproj | 1 + .../UmbracoContext/UmbracoContextFactory.cs | 5 +- .../src/installer/installer.service.js | 5 +- src/Umbraco.Web.UI.NetCore/Startup.cs | 29 ++-- ...bracoWebsiteServiceCollectionExtensions.cs | 79 --------- 26 files changed, 580 insertions(+), 160 deletions(-) create mode 100644 src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs create mode 100644 src/Umbraco.Web.Common/Attributes/UmbracoApiControllerAttribute.cs rename src/Umbraco.Web.Common/Extensions/{UmbracoCommonApplicationBuilderExtensions.cs => ApplicationBuilderExtensions.cs} (62%) create mode 100644 src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs rename src/Umbraco.Web.Common/{Attributes => Filters}/AngularJsonOnlyConfigurationAttribute.cs (97%) rename src/Umbraco.Web.Common/{Attributes => Filters}/FeatureAuthorizeAttribute.cs (100%) create mode 100644 src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs rename src/Umbraco.Web.Common/{Runtime => }/Profiler/WebProfiler.cs (94%) rename src/Umbraco.Web.Common/{Runtime => }/Profiler/WebProfilerComponent.cs (98%) rename src/Umbraco.Web.Common/{Runtime => }/Profiler/WebProfilerComposer.cs (88%) rename src/Umbraco.Web.Common/{Runtime => }/Profiler/WebProfilerHtml.cs (93%) create mode 100644 src/Umbraco.Web.Common/Security/WebSecurity.cs delete mode 100644 src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs diff --git a/.gitignore b/.gitignore index d8c3f27d5a..99c21bd287 100644 --- a/.gitignore +++ b/.gitignore @@ -177,3 +177,5 @@ build/temp/ /src/Umbraco.Web.UI.NetCore/wwwroot/Umbraco/views/* /src/Umbraco.Web.UI.NetCore/wwwroot/App_Data/TEMP/* /src/Umbraco.Web.UI.NetCore/App_Data/Logs/* +/src/Umbraco.Web.UI.NetCore/App_Data/TEMP/TypesCache/* +/src/Umbraco.Web.UI.NetCore/App_Data/TEMP/* diff --git a/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs new file mode 100644 index 0000000000..54cb650ce0 --- /dev/null +++ b/src/Umbraco.Web.Common/ApplicationModels/UmbracoApiBehaviorApplicationModelProvider.cs @@ -0,0 +1,93 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core; +using Umbraco.Web.Common.Attributes; +using Umbraco.Web.Common.ModelBinding; + +namespace Umbraco.Web.Common.ApplicationModels +{ + public class NewtonsoftJsonModelBinderConvention : IActionModelConvention + { + public void Apply(ActionModel action) + { + foreach(var p in action.Parameters) + { + if (p.BindingInfo?.BindingSource == BindingSource.Body) + { + p.BindingInfo.BinderType = typeof(UmbracoJsonModelBinder); + } + } + } + } + + /// + /// A custom application model provider for Umbraco controllers + /// + /// + /// + /// Conventions will be applied to controllers attributed with + /// + /// + /// This is nearly a copy of aspnetcore's ApiBehaviorApplicationModelProvider which supplies a convention for the + /// [ApiController] attribute, however that convention is too strict for our purposes so we will have our own. + /// + /// + public class UmbracoApiBehaviorApplicationModelProvider : IApplicationModelProvider + { + public UmbracoApiBehaviorApplicationModelProvider(IModelMetadataProvider modelMetadataProvider) + { + // see see https://docs.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-3.1#apicontroller-attribute + // for what these things actually do + // NOTE: we don't have attribute routing requirements and we cannot use ApiVisibilityConvention without attribute routing + + ActionModelConventions = new List() + { + new ClientErrorResultFilterConvention(), // TODO: Need to determine exactly how this affects errors + new InvalidModelStateFilterConvention(), // automatically 400 responses if ModelState is invalid before hitting the controller + new ConsumesConstraintForFormFileParameterConvention(), // If an controller accepts files, it must accept multipart/form-data. + new InferParameterBindingInfoConvention(modelMetadataProvider), // no need for [FromBody] everywhere, A complex type parameter is assigned to FromBody + + new NewtonsoftJsonModelBinderConvention() + }; + + // TODO: Need to determine exactly how this affects errors + var defaultErrorType = typeof(ProblemDetails); + var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType); + ActionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute)); + } + + /// + /// Will execute after + /// + public int Order => 0; + + public List ActionModelConventions { get; } + + public void OnProvidersExecuted(ApplicationModelProviderContext context) + { + } + + public void OnProvidersExecuting(ApplicationModelProviderContext context) + { + foreach (var controller in context.Result.Controllers) + { + if (!IsUmbracoApiController(controller)) + continue; + + foreach (var action in controller.Actions) + { + foreach (var convention in ActionModelConventions) + { + convention.Apply(action); + } + } + + } + } + + private bool IsUmbracoApiController(ControllerModel controller) => controller.Attributes.OfType().Any(); + } +} diff --git a/src/Umbraco.Web.Common/Attributes/UmbracoApiControllerAttribute.cs b/src/Umbraco.Web.Common/Attributes/UmbracoApiControllerAttribute.cs new file mode 100644 index 0000000000..a3ffc3d9e9 --- /dev/null +++ b/src/Umbraco.Web.Common/Attributes/UmbracoApiControllerAttribute.cs @@ -0,0 +1,13 @@ +using System; +using Umbraco.Web.Common.ApplicationModels; + +namespace Umbraco.Web.Common.Attributes +{ + /// + /// When present on a controller then conventions will apply + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class UmbracoApiControllerAttribute : Attribute + { + } +} diff --git a/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs b/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs index 4a0bff7ffb..01300d1fa5 100644 --- a/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs +++ b/src/Umbraco.Web.Common/Controllers/UmbracoApiControllerBase.cs @@ -1,26 +1,26 @@ -using System; -using Microsoft.AspNetCore.Mvc; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Configuration; -using Umbraco.Core.Logging; -using Umbraco.Core.Mapping; -using Umbraco.Core.Persistence; -using Umbraco.Core.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Umbraco.Web.Common.Filters; using Umbraco.Web.Features; -using Umbraco.Web.Routing; -using Umbraco.Web.Security; using Umbraco.Web.WebApi.Filters; +using Umbraco.Web.Common.Attributes; namespace Umbraco.Web.Common.Controllers { /// /// Provides a base class for Umbraco API controllers. /// - /// These controllers are NOT auto-routed. + /// + /// These controllers are NOT auto-routed. + /// The base class is which are netcore API controllers without any view support + /// [FeatureAuthorize] - public abstract class UmbracoApiControllerBase : Controller, IUmbracoFeature + [TypeFilter(typeof(HttpResponseExceptionFilter))] + [UmbracoApiController] + public abstract class UmbracoApiControllerBase : ControllerBase, IUmbracoFeature { - + public UmbracoApiControllerBase() + { + } } } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCommonApplicationBuilderExtensions.cs b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs similarity index 62% rename from src/Umbraco.Web.Common/Extensions/UmbracoCommonApplicationBuilderExtensions.cs rename to src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs index 8386a1d8fe..47cee99264 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCommonApplicationBuilderExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/ApplicationBuilderExtensions.cs @@ -7,8 +7,13 @@ using Umbraco.Web.Common.Middleware; namespace Umbraco.Extensions { - public static class UmbracoCommonApplicationBuilderExtensions + public static class ApplicationBuilderExtensions { + /// + /// Returns true if Umbraco is greater than + /// + /// + /// public static bool UmbracoCanBoot(this IApplicationBuilder app) { var runtime = app.ApplicationServices.GetRequiredService(); @@ -16,8 +21,13 @@ namespace Umbraco.Extensions return runtime.State.Level > RuntimeLevel.BootFailed; } + /// + /// Enables middlewares required to run Umbraco + /// + /// + /// // TODO: Could be internal or part of another call - this is a required system so should't be 'opt-in' - public static IApplicationBuilder UseUmbracoRequestLifetime(this IApplicationBuilder app) + public static IApplicationBuilder UseUmbracoRouting(this IApplicationBuilder app) { if (app == null) throw new ArgumentNullException(nameof(app)); diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs index 256bc1a78c..5078fa5f22 100644 --- a/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs +++ b/src/Umbraco.Web.Common/Extensions/UmbracoCoreServiceCollectionExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections; -using System.Collections.Generic; using System.Data.Common; using System.Data.SqlClient; using System.IO; @@ -13,8 +12,6 @@ using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Hosting; using Serilog.Extensions.Logging; -using Smidge; -using Smidge.Nuglify; using Umbraco.Composing; using Umbraco.Configuration; using Umbraco.Core; @@ -28,12 +25,19 @@ using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Runtime; using Umbraco.Web.Common.AspNetCore; -using Umbraco.Web.Common.Runtime.Profiler; +using Umbraco.Web.Common.Profiler; namespace Umbraco.Extensions { + + public static class UmbracoCoreServiceCollectionExtensions { + /// + /// Adds SqlCe support for Umbraco + /// + /// + /// public static IServiceCollection AddUmbracoSqlCeSupport(this IServiceCollection services) { try @@ -60,7 +64,7 @@ namespace Umbraco.Extensions var sqlCe = sqlCeAssembly.GetType("System.Data.SqlServerCe.SqlCeProviderFactory"); if (!(sqlCe is null)) { - DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlCe, sqlCe ); + DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlCe, sqlCe); } } } @@ -72,6 +76,11 @@ namespace Umbraco.Extensions return services; } + /// + /// Adds Sql Server support for Umbraco + /// + /// + /// public static IServiceCollection AddUmbracoSqlServerSupport(this IServiceCollection services) { DbProviderFactories.RegisterFactory(Core.Constants.DbProviderNames.SqlServer, SqlClientFactory.Instance); @@ -102,7 +111,6 @@ namespace Umbraco.Extensions return services; } - /// /// Adds the Umbraco Back Core requirements /// @@ -111,7 +119,7 @@ namespace Umbraco.Extensions /// public static IServiceCollection AddUmbracoCore(this IServiceCollection services, IWebHostEnvironment webHostEnvironment) { - return services.AddUmbracoCore(webHostEnvironment,out _); + return services.AddUmbracoCore(webHostEnvironment, out _); } /// @@ -198,7 +206,7 @@ namespace Umbraco.Extensions configs, webHostEnvironment, loggingConfiguration, - out var logger, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler); + out var logger, out var ioHelper, out var hostingEnvironment, out var backOfficeInfo, out var profiler); var globalSettings = configs.Global(); var umbracoVersion = new UmbracoVersion(globalSettings); @@ -219,8 +227,7 @@ namespace Umbraco.Extensions factory = coreRuntime.Configure(container); return services; - } - + } private static ITypeFinder CreateTypeFinder(Core.Logging.ILogger logger, IProfiler profiler, IWebHostEnvironment webHostEnvironment, Assembly entryAssembly, ITypeFinderSettings typeFinderSettings) { @@ -322,15 +329,6 @@ namespace Umbraco.Extensions return logger; } - public static IServiceCollection AddUmbracoRuntimeMinifier(this IServiceCollection services, - IConfiguration configuration) - { - services.AddSmidge(configuration.GetSection(Core.Constants.Configuration.ConfigRuntimeMinification)); - services.AddSmidgeNuglify(); - - return services; - } - private static IProfiler GetWebProfiler(Umbraco.Core.Hosting.IHostingEnvironment hostingEnvironment) { // create and start asap to profile boot @@ -346,6 +344,7 @@ namespace Umbraco.Extensions return webProfiler; } + private class AspNetCoreBootPermissionsChecker : IUmbracoBootPermissionChecker { public void ThrowIfNotPermissions() @@ -354,7 +353,6 @@ namespace Umbraco.Extensions } } - } } diff --git a/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs b/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs new file mode 100644 index 0000000000..0278630be8 --- /dev/null +++ b/src/Umbraco.Web.Common/Extensions/UmbracoWebServiceCollectionExtensions.cs @@ -0,0 +1,151 @@ +using System.Buffers; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Web.Caching; +using SixLabors.ImageSharp.Web.Commands; +using SixLabors.ImageSharp.Web.DependencyInjection; +using SixLabors.ImageSharp.Web.Processors; +using SixLabors.ImageSharp.Web.Providers; +using Smidge; +using Smidge.Nuglify; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Web.Common.ApplicationModels; +using Umbraco.Web.Common.ModelBinding; + +namespace Umbraco.Extensions +{ + public static class UmbracoWebServiceCollectionExtensions + { + /// + /// Registers the web components needed for Umbraco + /// + /// + /// + public static IServiceCollection AddUmbracoWebComponents(this IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + //services.ConfigureOptions(); + services.TryAddEnumerable(ServiceDescriptor.Transient()); + + // TODO: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured + var serviceProvider = services.BuildServiceProvider(); + var configs = serviceProvider.GetService(); + var imagingSettings = configs.Imaging(); + services.AddUmbracoImageSharp(imagingSettings); + + return services; + } + + /// + /// Adds Image Sharp with Umbraco settings + /// + /// + /// + /// + public static IServiceCollection AddUmbracoImageSharp(this IServiceCollection services, IImagingSettings imagingSettings) + { + services.AddImageSharpCore( + options => + { + options.Configuration = SixLabors.ImageSharp.Configuration.Default; + options.MaxBrowserCacheDays = imagingSettings.MaxBrowserCacheDays; + options.MaxCacheDays = imagingSettings.MaxCacheDays; + options.CachedNameLength = imagingSettings.CachedNameLength; + options.OnParseCommands = context => + { + RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Width, imagingSettings.MaxResizeWidth); + RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Height, imagingSettings.MaxResizeHeight); + }; + options.OnBeforeSave = _ => { }; + options.OnProcessed = _ => { }; + options.OnPrepareResponse = _ => { }; + }) + .SetRequestParser() + .SetMemoryAllocator(provider => ArrayPoolMemoryAllocator.CreateWithMinimalPooling()) + .Configure(options => + { + options.CacheFolder = imagingSettings.CacheFolder; + }) + .SetCache() + .SetCacheHash() + .AddProvider() + .AddProcessor() + .AddProcessor() + .AddProcessor(); + + return services; + } + + /// + /// Adds the Umbraco runtime minifier + /// + /// + /// + /// + public static IServiceCollection AddUmbracoRuntimeMinifier(this IServiceCollection services, + IConfiguration configuration) + { + services.AddSmidge(configuration.GetSection(Core.Constants.Configuration.ConfigRuntimeMinification)); + services.AddSmidgeNuglify(); + + return services; + } + + private static void RemoveIntParamenterIfValueGreatherThen(IDictionary commands, string parameter, int maxValue) + { + if (commands.TryGetValue(parameter, out var command)) + { + if (int.TryParse(command, out var i)) + { + if (i > maxValue) + { + commands.Remove(parameter); + } + } + } + } + + /// + /// Options for configuring MVC + /// + /// + /// We generally don't want to change the global MVC settings since we want to be unobtrusive as possible but some + /// global mods are needed - so long as they don't interfere with normal user usages of MVC. + /// + private class UmbracoMvcConfigureOptions : IConfigureOptions + { + private readonly IHttpRequestStreamReaderFactory _readerFactory; + private readonly ILoggerFactory _logger; + private readonly ArrayPool _arrayPool; + private readonly ObjectPoolProvider _objectPoolProvider; + + public UmbracoMvcConfigureOptions(IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory logger, ArrayPool arrayPool, ObjectPoolProvider objectPoolProvider) + { + _readerFactory = readerFactory; + _logger = logger; + _arrayPool = arrayPool; + _objectPoolProvider = objectPoolProvider; + } + + public void Configure(MvcOptions options) + { + options.ModelBinderProviders.Insert(0, new UmbracoJsonModelBinderProvider(_readerFactory, _logger, _arrayPool, _objectPoolProvider)); + } + } + + + } + +} diff --git a/src/Umbraco.Web.Common/Attributes/AngularJsonOnlyConfigurationAttribute.cs b/src/Umbraco.Web.Common/Filters/AngularJsonOnlyConfigurationAttribute.cs similarity index 97% rename from src/Umbraco.Web.Common/Attributes/AngularJsonOnlyConfigurationAttribute.cs rename to src/Umbraco.Web.Common/Filters/AngularJsonOnlyConfigurationAttribute.cs index f0281abcba..192b5e7df0 100644 --- a/src/Umbraco.Web.Common/Attributes/AngularJsonOnlyConfigurationAttribute.cs +++ b/src/Umbraco.Web.Common/Filters/AngularJsonOnlyConfigurationAttribute.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Options; using Newtonsoft.Json; using Umbraco.Web.Common.Formatters; -namespace Umbraco.Web.Common.Attributes +namespace Umbraco.Web.Common.Filters { /// /// Applying this attribute to any controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. @@ -38,4 +38,5 @@ namespace Umbraco.Web.Common.Attributes base.OnResultExecuting(context); } } + } diff --git a/src/Umbraco.Web.Common/Attributes/FeatureAuthorizeAttribute.cs b/src/Umbraco.Web.Common/Filters/FeatureAuthorizeAttribute.cs similarity index 100% rename from src/Umbraco.Web.Common/Attributes/FeatureAuthorizeAttribute.cs rename to src/Umbraco.Web.Common/Filters/FeatureAuthorizeAttribute.cs diff --git a/src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs b/src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs index 46bfd6cdfa..cd2d2cb3a0 100644 --- a/src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs +++ b/src/Umbraco.Web.Common/Filters/HttpResponseExceptionFilter.cs @@ -7,6 +7,11 @@ namespace Umbraco.Web.Common.Filters { public class HttpResponseExceptionFilter : IActionFilter, IOrderedFilter { + public HttpResponseExceptionFilter() + { + + } + public int Order { get; set; } = int.MaxValue - 10; public void OnActionExecuting(ActionExecutingContext context) { } diff --git a/src/Umbraco.Web.Common/Install/InstallApiController.cs b/src/Umbraco.Web.Common/Install/InstallApiController.cs index d9771fd7f8..c0c714a838 100644 --- a/src/Umbraco.Web.Common/Install/InstallApiController.cs +++ b/src/Umbraco.Web.Common/Install/InstallApiController.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Newtonsoft.Json.Linq; using Umbraco.Core; using Umbraco.Core.Logging; @@ -11,15 +12,20 @@ using Umbraco.Core.Migrations.Install; using Umbraco.Net; using Umbraco.Web.Common.Attributes; using Umbraco.Web.Common.Exceptions; +using Umbraco.Web.Common.Filters; +using Umbraco.Web.Common.ModelBinding; using Umbraco.Web.Install; using Umbraco.Web.Install.Models; namespace Umbraco.Web.Common.Install { + + [UmbracoApiController] + [TypeFilter(typeof(HttpResponseExceptionFilter))] [TypeFilter(typeof(AngularJsonOnlyConfigurationAttribute))] [HttpInstallAuthorize] [Area("Install")] - public class InstallApiController : Controller + public class InstallApiController : ControllerBase { private readonly DatabaseBuilder _databaseBuilder; private readonly InstallStatusTracker _installStatusTracker; @@ -28,10 +34,12 @@ namespace Umbraco.Web.Common.Install private readonly ILogger _logger; private readonly IProfilingLogger _proflog; - public InstallApiController(DatabaseBuilder databaseBuilder, IProfilingLogger proflog, + public InstallApiController(UmbracoJsonModelBinderFactory modelBinderFactory, DatabaseBuilder databaseBuilder, IProfilingLogger proflog, InstallHelper installHelper, InstallStepCollection installSteps, InstallStatusTracker installStatusTracker, IUmbracoApplicationLifetime umbracoApplicationLifetime) { + ModelBinderFactory = modelBinderFactory; + _databaseBuilder = databaseBuilder ?? throw new ArgumentNullException(nameof(databaseBuilder)); _proflog = proflog ?? throw new ArgumentNullException(nameof(proflog)); _installSteps = installSteps; @@ -41,6 +49,7 @@ namespace Umbraco.Web.Common.Install _logger = _proflog; } + internal InstallHelper InstallHelper { get; } public bool PostValidateDatabaseConnection(DatabaseModel model) @@ -88,9 +97,8 @@ namespace Umbraco.Web.Common.Install /// /// Installs. - /// - [HttpPost] - public async Task PostPerformInstall([FromBody] InstallInstructions installModel) + /// + public async Task PostPerformInstall(InstallInstructions installModel) { if (installModel == null) throw new ArgumentNullException(nameof(installModel)); diff --git a/src/Umbraco.Web.Common/Install/InstallController.cs b/src/Umbraco.Web.Common/Install/InstallController.cs index 88171820ec..07f0376697 100644 --- a/src/Umbraco.Web.Common/Install/InstallController.cs +++ b/src/Umbraco.Web.Common/Install/InstallController.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using System.Threading.Tasks; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; @@ -11,6 +12,7 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Common.Install { + /// /// The MVC Installation controller /// @@ -49,7 +51,7 @@ namespace Umbraco.Web.Common.Install [HttpGet] [StatusCodeResult(System.Net.HttpStatusCode.ServiceUnavailable)] [TypeFilter(typeof(StatusCodeResultAttribute), Arguments = new object []{System.Net.HttpStatusCode.ServiceUnavailable})] - public ActionResult Index() + public async Task Index() { if (_runtime.Level == RuntimeLevel.Run) return Redirect(_globalSettings.UmbracoPath.EnsureEndsWith('/')); @@ -77,7 +79,7 @@ namespace Umbraco.Web.Common.Install ViewData.SetUmbracoVersion(_umbracoVersion.SemanticVersion); - _installHelper.InstallStatus(false, ""); + await _installHelper.InstallStatus(false, ""); // always ensure full path (see NOTE in the class remarks) return View(); diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs index 0e2158c939..b1d2d01f9d 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestLoggingMiddleware.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Serilog.Context; -using Umbraco.Core.Cache; +using Umbraco.Core; using Umbraco.Core.Logging.Serilog.Enrichers; -using Umbraco.Net; namespace Umbraco.Web.Common.Middleware { @@ -27,6 +28,13 @@ namespace Umbraco.Web.Common.Middleware public async Task Invoke(HttpContext httpContext) { + // do not process if client-side request + if (new Uri(httpContext.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) + { + await _next(httpContext); + return; + } + // TODO: Need to decide if we want this stuff still, there's new request logging in serilog: // https://github.com/serilog/serilog-aspnetcore#request-logging which i think would suffice and replace all of this? diff --git a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs index e8695f3c9c..85cf6607cc 100644 --- a/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs +++ b/src/Umbraco.Web.Common/Middleware/UmbracoRequestMiddleware.cs @@ -1,25 +1,66 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; using Umbraco.Web.Common.Lifetime; +using Umbraco.Core; +using Umbraco.Core.Logging; namespace Umbraco.Web.Common.Middleware { + /// + /// Manages Umbraco request objects and their lifetime + /// public class UmbracoRequestMiddleware { private readonly RequestDelegate _next; + private readonly ILogger _logger; private readonly IUmbracoRequestLifetimeManager _umbracoRequestLifetimeManager; - public UmbracoRequestMiddleware(RequestDelegate next, IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager) + private readonly IUmbracoContextFactory _umbracoContextFactory; + + public UmbracoRequestMiddleware(RequestDelegate next, + ILogger logger, + IUmbracoRequestLifetimeManager umbracoRequestLifetimeManager, + IUmbracoContextFactory umbracoContextFactory) { _next = next; + _logger = logger; _umbracoRequestLifetimeManager = umbracoRequestLifetimeManager; + _umbracoContextFactory = umbracoContextFactory; } public async Task InvokeAsync(HttpContext context) { - _umbracoRequestLifetimeManager.InitRequest(context); - await _next(context); - _umbracoRequestLifetimeManager.EndRequest(context); + // do not process if client-side request + + if (new Uri(context.Request.GetEncodedUrl(), UriKind.RelativeOrAbsolute).IsClientSideRequest()) + { + await _next(context); + return; + } + + var umbracoContextReference = _umbracoContextFactory.EnsureUmbracoContext(); + + try + { + try + { + _umbracoRequestLifetimeManager.InitRequest(context); + } + catch (Exception ex) + { + // try catch so we don't kill everything in all requests + _logger.Error(ex); + } + + await _next(context); + + _umbracoRequestLifetimeManager.EndRequest(context); + } + finally + { + umbracoContextReference.Dispose(); + } } } } diff --git a/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs b/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs new file mode 100644 index 0000000000..120a6cf4f5 --- /dev/null +++ b/src/Umbraco.Web.Common/ModelBinding/UmbracoJsonModelBinderProvider.cs @@ -0,0 +1,117 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Umbraco.Core; +using Umbraco.Web.Common.Controllers; + +namespace Umbraco.Web.Common.ModelBinding +{ + + public class UmbracoJsonModelBinderFactory : ModelBinderFactory + { + public UmbracoJsonModelBinderFactory( + UmbracoJsonModelBinderProvider umbracoJsonModelBinderProvider, + IModelMetadataProvider metadataProvider, + IOptions options, + IServiceProvider serviceProvider) + : base(metadataProvider, GetOptions(options, umbracoJsonModelBinderProvider), serviceProvider) + { + } + + private static IOptions GetOptions(IOptions options, UmbracoJsonModelBinderProvider umbracoJsonModelBinderProvider) + { + // copy to new collection + var providers = options.Value.ModelBinderProviders.ToList(); + // remove the default + providers.RemoveType(); + // prepend our own + providers.Insert(0, umbracoJsonModelBinderProvider); + var newOptions = new MvcOptions(); + foreach (var p in providers) + newOptions.ModelBinderProviders.Add(p); + return new CustomOptions(newOptions); + } + + private class CustomOptions : IOptions + { + public CustomOptions(MvcOptions options) + { + Value = options; + } + public MvcOptions Value { get; } + } + } + + public class UmbracoJsonModelBinder : BodyModelBinder, IModelBinder + { + public UmbracoJsonModelBinder(ArrayPool arrayPool, ObjectPoolProvider objectPoolProvider, IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory) + : base(GetNewtonsoftJsonFormatter(loggerFactory, arrayPool, objectPoolProvider), readerFactory, loggerFactory) + { + } + + internal static IInputFormatter[] GetNewtonsoftJsonFormatter(ILoggerFactory logger, ArrayPool arrayPool, ObjectPoolProvider objectPoolProvider) + { + var jsonOptions = new MvcNewtonsoftJsonOptions + { + AllowInputFormatterExceptionMessages = true + }; + return new IInputFormatter[] + { + new NewtonsoftJsonInputFormatter( + logger.CreateLogger(), + jsonOptions.SerializerSettings, // Just use the defaults + arrayPool, + objectPoolProvider, + new MvcOptions(), // The only option that NewtonsoftJsonInputFormatter uses is SuppressInputFormatterBuffering + jsonOptions) + }; + } + + Task IModelBinder.BindModelAsync(ModelBindingContext bindingContext) + { + return BindModelAsync(bindingContext); + } + } + + /// + /// A model binder for Umbraco models that require Newtonsoft Json serializers + /// + public class UmbracoJsonModelBinderProvider : BodyModelBinderProvider, IModelBinderProvider + { + public UmbracoJsonModelBinderProvider(IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory logger, ArrayPool arrayPool, ObjectPoolProvider objectPoolProvider) + : base(UmbracoJsonModelBinder.GetNewtonsoftJsonFormatter(logger, arrayPool, objectPoolProvider), readerFactory) + { + } + + /// + /// Returns the model binder if it's for Umbraco models + /// + /// + /// + IModelBinder IModelBinderProvider.GetBinder(ModelBinderProviderContext context) + { + // Must be 'Body' (json) binding + if (context.BindingInfo.BindingSource != BindingSource.Body) + return null; + + if (context.Metadata?.UnderlyingOrModelType.Assembly == typeof(UmbracoJsonModelBinderProvider).Assembly // Umbraco.Web.Common + || context.Metadata?.UnderlyingOrModelType.Assembly == typeof(IRuntimeState).Assembly // Umbraco.Core + || context.Metadata?.UnderlyingOrModelType.Assembly == typeof(RuntimeState).Assembly) // Umbraco.Infrastructure + { + return GetBinder(context); + } + + return null; + } + } +} diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs b/src/Umbraco.Web.Common/Profiler/WebProfiler.cs similarity index 94% rename from src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs rename to src/Umbraco.Web.Common/Profiler/WebProfiler.cs index 958e134bab..30777d07a5 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfiler.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfiler.cs @@ -7,8 +7,7 @@ using StackExchange.Profiling; using Umbraco.Core; using Umbraco.Core.Logging; -// TODO: This namespace is strange, not sure why i has "Runtime" in the name? -namespace Umbraco.Web.Common.Runtime.Profiler +namespace Umbraco.Web.Common.Profiler { public class WebProfiler : IProfiler @@ -45,9 +44,7 @@ namespace Umbraco.Web.Common.Runtime.Profiler public void UmbracoApplicationBeginRequest(HttpContext context) { if (ShouldProfile(context.Request)) - { Start(); - } } public void UmbracoApplicationEndRequest(HttpContext context) diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs similarity index 98% rename from src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs rename to src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs index a36753e634..bc5cce9df1 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComponent.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfilerComponent.cs @@ -5,7 +5,7 @@ using Umbraco.Net; using Umbraco.Web.Common.Lifetime; using Umbraco.Web.Common.Middleware; -namespace Umbraco.Web.Common.Runtime.Profiler +namespace Umbraco.Web.Common.Profiler { internal sealed class WebProfilerComponent : IComponent { diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs similarity index 88% rename from src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs rename to src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs index 523faf2da5..edb3db6f85 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerComposer.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfilerComposer.cs @@ -1,7 +1,7 @@ using Umbraco.Core; using Umbraco.Core.Composing; -namespace Umbraco.Web.Common.Runtime.Profiler +namespace Umbraco.Web.Common.Profiler { internal class WebProfilerComposer : ComponentComposer, ICoreComposer { diff --git a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerHtml.cs b/src/Umbraco.Web.Common/Profiler/WebProfilerHtml.cs similarity index 93% rename from src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerHtml.cs rename to src/Umbraco.Web.Common/Profiler/WebProfilerHtml.cs index 9e989d6b5c..40c245dd5a 100644 --- a/src/Umbraco.Web.Common/Runtime/Profiler/WebProfilerHtml.cs +++ b/src/Umbraco.Web.Common/Profiler/WebProfilerHtml.cs @@ -5,8 +5,7 @@ using StackExchange.Profiling; using StackExchange.Profiling.Internal; using Umbraco.Core.Logging; -// TODO: This namespace is strange, not sure why i has "Runtime" in the name? -namespace Umbraco.Web.Common.Runtime.Profiler +namespace Umbraco.Web.Common.Profiler { public class WebProfilerHtml : IProfilerHtml { diff --git a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs index 30d7cdaced..78068b551c 100644 --- a/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs +++ b/src/Umbraco.Web.Common/Runtime/AspNetCoreComposer.cs @@ -12,8 +12,8 @@ using Umbraco.Web.Common.Macros; using Umbraco.Web.Composing.CompositionExtensions; using Umbraco.Web.Macros; using Umbraco.Core.Diagnostics; -using Umbraco.Web.Common.Runtime.Profiler; using Umbraco.Core.Logging; +using Umbraco.Web.Common.Profiler; namespace Umbraco.Web.Common.Runtime { diff --git a/src/Umbraco.Web.Common/Security/WebSecurity.cs b/src/Umbraco.Web.Common/Security/WebSecurity.cs new file mode 100644 index 0000000000..5f54d2e9ee --- /dev/null +++ b/src/Umbraco.Web.Common/Security/WebSecurity.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Umbraco.Core; +using Umbraco.Core.Models.Membership; +using Umbraco.Web.Security; + +namespace Umbraco.Web.Common.Security +{ + // TODO: need to implement this + + public class WebSecurity : IWebSecurity + { + public IUser CurrentUser => throw new NotImplementedException(); + + public ValidateRequestAttempt AuthorizeRequest(bool throwExceptions = false) + { + throw new NotImplementedException(); + } + + public void ClearCurrentLogin() + { + throw new NotImplementedException(); + } + + public Attempt GetUserId() + { + throw new NotImplementedException(); + } + + public bool IsAuthenticated() + { + throw new NotImplementedException(); + } + + public double PerformLogin(int userId) + { + throw new NotImplementedException(); + } + + public bool UserHasSectionAccess(string section, IUser user) + { + throw new NotImplementedException(); + } + + public bool ValidateCurrentUser() + { + throw new NotImplementedException(); + } + + public ValidateRequestAttempt ValidateCurrentUser(bool throwExceptions, bool requiresApproval = true) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj index eed88bc491..9f6509cd82 100644 --- a/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj +++ b/src/Umbraco.Web.Common/Umbraco.Web.Common.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs index 97f009968c..89751bfa08 100644 --- a/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs +++ b/src/Umbraco.Web.Common/UmbracoContext/UmbracoContextFactory.cs @@ -6,6 +6,7 @@ using Umbraco.Core.Configuration; using Umbraco.Core.Hosting; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; +using Umbraco.Web.Common.Security; using Umbraco.Web.PublishedCache; using Umbraco.Web.Security; @@ -16,8 +17,6 @@ namespace Umbraco.Web /// public class UmbracoContextFactory : IUmbracoContextFactory { - private static readonly NullWriter NullWriterInstance = new NullWriter(); - private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IVariationContextAccessor _variationContextAccessor; @@ -73,7 +72,7 @@ namespace Umbraco.Web _variationContextAccessor.VariationContext = new VariationContext(_defaultCultureAccessor.DefaultCulture); } - IWebSecurity webSecurity = null; // TODO, we need to when users are migrated + IWebSecurity webSecurity = new WebSecurity(); return new UmbracoContext( _publishedSnapshotService, diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js index 7cebaaada3..1003b19b20 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.service.js +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.service.js @@ -336,7 +336,10 @@ angular.module("umbraco.install").factory('installerService', function ($rootSco if (status >= 500 && status < 600) { service.status.current = { view: "ysod", model: null }; var ysod = data; - //we need to manually write the html to the iframe - the html contains full html markup + //we need to manually write the html to the iframe + // TODO: In dotnetcore the resulting YSOD isn't HTML, the error is just a string so it looks ugly + // So we shouldn't be using an iframe and will need to change this so that we have an unhandled exception filter for the installer (and eventually + // the rest of the back office) to handle errors and chuck the data into a json format for us to use. $timeout(function () { document.getElementById('ysod').contentDocument.write(ysod); }, 500); diff --git a/src/Umbraco.Web.UI.NetCore/Startup.cs b/src/Umbraco.Web.UI.NetCore/Startup.cs index f74208bac6..bd23ef4229 100644 --- a/src/Umbraco.Web.UI.NetCore/Startup.cs +++ b/src/Umbraco.Web.UI.NetCore/Startup.cs @@ -53,20 +53,10 @@ namespace Umbraco.Web.UI.BackOffice { services.AddUmbracoConfiguration(_config); services.AddUmbracoCore(_env, out var factory); - services.AddUmbracoWebsite(); + services.AddUmbracoWebComponents(); services.AddUmbracoRuntimeMinifier(_config); - services.AddMvc(options => - { - options.Filters.Add(); - - }).SetCompatibilityVersion(CompatibilityVersion.Version_3_0) - .AddNewtonsoftJson(options => - { - options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); - - }) - ; + services.AddMvc(); services.AddMiniProfiler(options => { @@ -90,19 +80,24 @@ namespace Umbraco.Web.UI.BackOffice public void Configure(IApplicationBuilder app) { - //app.UseMiniProfiler(); - app.UseUmbracoRequestLifetime(); + //app.UseMiniProfiler(); if (_env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } + app.UseStatusCodePages(); + app.UseRouting(); + + + app.UseUmbracoRouting(); app.UseUmbracoCore(); app.UseUmbracoRequestLogging(); app.UseUmbracoWebsite(); - app.UseUmbracoBackOffice(); - app.UseRouting(); + app.UseUmbracoBackOffice(); app.UseUmbracoRuntimeMinification(); + + app.UseEndpoints(endpoints => { endpoints.MapControllerRoute("Backoffice", "/umbraco/{Action}", new @@ -111,7 +106,7 @@ namespace Umbraco.Web.UI.BackOffice Action = "Default" }); - + // TODO: Fix this routing with an area endpoints.MapControllerRoute("Install", "/install/{controller}/{Action}", defaults:new { Area = "Install"}); //TODO register routing correct: Name must be like this diff --git a/src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs b/src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs deleted file mode 100644 index 3a327ef867..0000000000 --- a/src/Umbraco.Web.Website/AspNetCore/UmbracoWebsiteServiceCollectionExtensions.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.Web.Caching; -using SixLabors.ImageSharp.Web.Commands; -using SixLabors.ImageSharp.Web.DependencyInjection; -using SixLabors.ImageSharp.Web.Processors; -using SixLabors.ImageSharp.Web.Providers; - -using Umbraco.Core; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.Website.AspNetCore -{ - public static class UmbracoBackOfficeServiceCollectionExtensions - { - public static IServiceCollection AddUmbracoWebsite(this IServiceCollection services) - { - // TODO: We need to avoid this, surely there's a way? See ContainerTests.BuildServiceProvider_Before_Host_Is_Configured - var serviceProvider = services.BuildServiceProvider(); - var configs = serviceProvider.GetService(); - var imagingSettings = configs.Imaging(); - services.AddUmbracoImageSharp(imagingSettings); - - return services; - } - - public static IServiceCollection AddUmbracoImageSharp(this IServiceCollection services, IImagingSettings imagingSettings) - { - - - services.AddImageSharpCore( - options => - { - options.Configuration = SixLabors.ImageSharp.Configuration.Default; - options.MaxBrowserCacheDays = imagingSettings.MaxBrowserCacheDays; - options.MaxCacheDays = imagingSettings.MaxCacheDays; - options.CachedNameLength = imagingSettings.CachedNameLength; - options.OnParseCommands = context => - { - RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Width, imagingSettings.MaxResizeWidth); - RemoveIntParamenterIfValueGreatherThen(context.Commands, ResizeWebProcessor.Height, imagingSettings.MaxResizeHeight); - }; - options.OnBeforeSave = _ => { }; - options.OnProcessed = _ => { }; - options.OnPrepareResponse = _ => { }; - }) - .SetRequestParser() - .SetMemoryAllocator(provider => ArrayPoolMemoryAllocator.CreateWithMinimalPooling()) - .Configure(options => - { - options.CacheFolder = imagingSettings.CacheFolder; - }) - .SetCache() - .SetCacheHash() - .AddProvider() - .AddProcessor() - .AddProcessor() - .AddProcessor(); - - return services; - } - - private static void RemoveIntParamenterIfValueGreatherThen(IDictionary commands, string parameter, int maxValue) - { - if (commands.TryGetValue(parameter, out var command)) - { - if (int.TryParse(command, out var i)) - { - if (i > maxValue) - { - commands.Remove(parameter); - } - } - } - } - } - -}