Merge pull request #7992 from umbraco/netcore/feature/AB6241-reimplement-action-filters

Netcore: Reimplementing action filters
This commit is contained in:
Bjarke Berg
2020-04-22 08:54:25 +02:00
committed by GitHub
18 changed files with 257 additions and 7 deletions

View File

@@ -2,9 +2,7 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Core.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Runtime;
using Umbraco.Core.WebAssets;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.Common.ActionResults;
namespace Umbraco.Web.BackOffice.Filters
@@ -14,10 +12,11 @@ namespace Umbraco.Web.BackOffice.Filters
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
// logic before action goes here
var hostingEnvironment = context.HttpContext.RequestServices.GetService<IHostingEnvironment>();
var serviceProvider = context.HttpContext.RequestServices;
var hostingEnvironment = serviceProvider.GetService<IHostingEnvironment>();
if (!hostingEnvironment.IsDebugMode)
{
var runtimeMinifier = context.HttpContext.RequestServices.GetService<IRuntimeMinifier>();
var runtimeMinifier = serviceProvider.GetService<IRuntimeMinifier>();
if (context.Result is JavaScriptResult jsResult)
{

View File

@@ -0,0 +1,12 @@
namespace Umbraco.Web.Common.Constants
{
/// <summary>
/// constants
/// </summary>
internal static class ViewConstants
{
internal const string ViewLocation = "~/Views";
internal const string DataTokenCurrentViewContext = "umbraco-current-view-context";
}
}

View File

@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Web.Common.Controllers
{
public abstract class RenderController : Controller
{
}
}

View File

@@ -0,0 +1,17 @@
using System;
using Microsoft.AspNetCore.Mvc;
namespace Umbraco.Web.Common.Events
{
public class ActionExecutedEventArgs : EventArgs
{
public Controller Controller { get; set; }
public object Model { get; set; }
public ActionExecutedEventArgs(Controller controller, object model)
{
Controller = controller;
Model = model;
}
}
}

View File

@@ -130,7 +130,7 @@ namespace Umbraco.Web.Common.Extensions
IProfiler profiler, Core.Hosting.IHostingEnvironment hostingEnvironment, IBackOfficeInfo backOfficeInfo,
ITypeFinder typeFinder)
{
var connectionStringConfig = configs.ConnectionStrings()[Constants.System.UmbracoConnectionName];
var connectionStringConfig = configs.ConnectionStrings()[Core.Constants.System.UmbracoConnectionName];
var dbProviderFactoryCreator = new SqlServerDbProviderFactoryCreator(
connectionStringConfig?.ProviderName,
DbProviderFactories.GetFactory);
@@ -185,7 +185,7 @@ namespace Umbraco.Web.Common.Extensions
public static IServiceCollection AddUmbracoRuntimeMinifier(this IServiceCollection services,
IConfiguration configuration)
{
services.AddSmidge(configuration.GetSection(Constants.Configuration.ConfigRuntimeMinification));
services.AddSmidge(configuration.GetSection(Core.Constants.Configuration.ConfigRuntimeMinification));
services.AddSmidgeNuglify();
return services;

View File

@@ -0,0 +1,35 @@
using System;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Net.Http.Headers;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// Ensures that the request is not cached by the browser
/// </summary>
public class DisableBrowserCacheAttribute : ActionFilterAttribute
{
public override void OnResultExecuting(ResultExecutingContext context)
{
base.OnResultExecuting(context);
var httpResponse = context.HttpContext.Response;
if (httpResponse.StatusCode != 200) return;
httpResponse.GetTypedHeaders().CacheControl =
new CacheControlHeaderValue()
{
NoCache = true,
MaxAge = TimeSpan.Zero,
MustRevalidate = true,
NoStore = true
};
httpResponse.Headers[HeaderNames.LastModified] = DateTime.Now.ToString("R"); // Format RFC1123
httpResponse.Headers[HeaderNames.Pragma] = "no-cache";
httpResponse.Headers[HeaderNames.Expires] = new DateTime(1990, 1, 1, 0, 0, 0).ToString("R");
}
}
}

View File

@@ -0,0 +1,88 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Umbraco.Web.Common.Constants;
using Umbraco.Web.Common.Controllers;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// This is a special filter which is required for the RTE to be able to render Partial View Macros that
/// contain forms when the RTE value is resolved outside of an MVC view being rendered
/// </summary>
/// <remarks>
/// The entire way that we support partial view macros that contain forms isn't really great, these forms
/// need to be executed as ChildActions so that the ModelState,ViewData,TempData get merged into that action
/// so the form can show errors, viewdata, etc...
/// Under normal circumstances, macros will be rendered after a ViewContext is created but in some cases
/// developers will resolve the RTE value in the controller, in this case the Form won't be rendered correctly
/// with merged ModelState from the controller because the special DataToken hasn't been set yet (which is
/// normally done in the UmbracoViewPageOfModel when a real ViewContext is available.
/// So we need to detect if the currently rendering controller is IRenderController and if so we'll ensure that
/// this DataToken exists before the action executes in case the developer resolves an RTE value that contains
/// a partial view macro form.
/// </remarks>
public class EnsurePartialViewMacroViewContextFilterAttribute : ActionFilterAttribute
{
/// <summary>
/// Ensures the custom ViewContext datatoken is set before the RenderController action is invoked,
/// this ensures that any calls to GetPropertyValue with regards to RTE or Grid editors can still
/// render any PartialViewMacro with a form and maintain ModelState
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!(context.Controller is Controller controller)) return;
//ignore anything that is not IRenderController
if (!(controller is RenderController)) return;
SetViewContext(context, controller);
}
/// <summary>
/// Ensures that the custom ViewContext datatoken is set after the RenderController action is invoked,
/// this ensures that any custom ModelState that may have been added in the RenderController itself is
/// passed onwards in case it is required when rendering a PartialViewMacro with a form
/// </summary>
/// <param name="context">The filter context.</param>
public override void OnResultExecuting(ResultExecutingContext context)
{
if (!(context.Controller is Controller controller)) return;
//ignore anything that is not IRenderController
if (!(controller is RenderController)) return;
SetViewContext(context, controller);
}
private void SetViewContext(ActionContext context, Controller controller)
{
var viewCtx = new ViewContext(
context,
new DummyView(),
controller.ViewData,
controller.TempData,
new StringWriter(),
new HtmlHelperOptions());
//set the special data token
context.RouteData.DataTokens[ViewConstants.DataTokenCurrentViewContext] = viewCtx;
}
private class DummyView : IView
{
public Task RenderAsync(ViewContext context)
{
return Task.CompletedTask;
}
public string Path { get; }
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Umbraco.Web.Common.Events;
namespace Umbraco.Web.Common.Filters
{
public class PreRenderViewActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (!(context.Controller is Controller umbController) || !(context.Result is ViewResult result))
{
return;
}
var model = result.Model;
if (model == null)
{
return;
}
var args = new ActionExecutedEventArgs(umbController, model);
OnActionExecuted(args);
if (args.Model != model)
{
result.ViewData.Model = args.Model;
}
base.OnActionExecuted(context);
}
public static event EventHandler<ActionExecutedEventArgs> ActionExecuted;
private static void OnActionExecuted(ActionExecutedEventArgs e)
{
var handler = ActionExecuted;
handler?.Invoke(null, e);
}
}
}

View File

@@ -0,0 +1,39 @@
using System.Net;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Core.Configuration.UmbracoSettings;
namespace Umbraco.Web.Common.Filters
{
/// <summary>
/// Forces the response to have a specific http status code
/// </summary>
public class StatusCodeResultAttribute : ActionFilterAttribute
{
private readonly HttpStatusCode _statusCode;
public StatusCodeResultAttribute(HttpStatusCode statusCode)
{
_statusCode = statusCode;
}
public override void OnActionExecuted(ActionExecutedContext context)
{
base.OnActionExecuted(context);
var httpContext = context.HttpContext;
httpContext.Response.StatusCode = (int)_statusCode;
var disableIisCustomErrors = httpContext.RequestServices.GetService<IWebRoutingSettings>().TrySkipIisCustomErrors;
var statusCodePagesFeature = httpContext.Features.Get<IStatusCodePagesFeature>();
if (statusCodePagesFeature != null)
{
// if IIS Custom Errors are disabled, we won't enable the Status Code Pages
statusCodePagesFeature.Enabled = !disableIisCustomErrors;
}
}
}
}

View File

@@ -112,7 +112,7 @@ namespace Umbraco.Web.Common.RuntimeMinification
public void Reset()
{
var version = DateTime.UtcNow.Ticks.ToString();
_configManipulator.SaveConfigValue(Constants.Configuration.ConfigRuntimeMinificationVersion, version.ToString());
_configManipulator.SaveConfigValue(Core.Constants.Configuration.ConfigRuntimeMinificationVersion, version.ToString());
}

View File

@@ -75,6 +75,7 @@ namespace Umbraco.Web.UI.BackOffice
{
app.UseDeveloperExceptionPage();
}
app.UseStatusCodePages();
app.UseUmbracoCore();
app.UseUmbracoWebsite();
app.UseUmbracoBackOffice();

View File

@@ -3,6 +3,7 @@ using System.Web.Mvc;
namespace Umbraco.Web.Mvc
{
/// Migrated already to .Net Core
public class ActionExecutedEventArgs : EventArgs
{
public Controller Controller { get; set; }

View File

@@ -3,6 +3,7 @@
/// <summary>
/// constants
/// </summary>
/// Migrated already to .Net Core
internal static class Constants
{
internal const string ViewLocation = "~/Views";

View File

@@ -19,6 +19,7 @@ namespace Umbraco.Web.Mvc
/// this DataToken exists before the action executes in case the developer resolves an RTE value that contains
/// a partial view macro form.
/// </remarks>
/// Migrated already to .Net Core
internal class EnsurePartialViewMacroViewContextFilterAttribute : ActionFilterAttribute
{
/// <summary>

View File

@@ -5,6 +5,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// A marker interface to designate that a controller will be used for Umbraco front-end requests and/or route hijacking
/// </summary>
/// Migrated already to .Net Core
public interface IRenderController : IController
{

View File

@@ -12,6 +12,7 @@ namespace Umbraco.Web.Mvc
/// <remarks>
/// Only minifies in release mode
/// </remarks>
/// Migrated already to .Net Core
public class MinifyJavaScriptResultAttribute : ActionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;

View File

@@ -3,6 +3,7 @@ using System.Web.Mvc;
namespace Umbraco.Web.Mvc
{
/// Migrated already to .Net Core
public class PreRenderViewActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)

View File

@@ -8,6 +8,7 @@ namespace Umbraco.Web.Mvc
/// <summary>
/// Forces the response to have a specific http status code
/// </summary>
/// Migrated already to .Net Core
internal class StatusCodeResultAttribute : ActionFilterAttribute
{
private readonly HttpStatusCode _statusCode;