diff --git a/src/Umbraco.Web.Common/Controllers/ProxyViewDataFeature.cs b/src/Umbraco.Web.Common/Controllers/ProxyViewDataFeature.cs new file mode 100644 index 0000000000..a672fdfd3c --- /dev/null +++ b/src/Umbraco.Web.Common/Controllers/ProxyViewDataFeature.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Umbraco.Web.Common.Controllers +{ + /// + /// A request feature to allowing proxying viewdata from one controller to another + /// + public sealed class ProxyViewDataFeature + { + /// + /// Initializes a new instance of the class. + /// + public ProxyViewDataFeature(ViewDataDictionary viewData) => ViewData = viewData; + + /// + /// Gets the + /// + public ViewDataDictionary ViewData { get; } + } +} diff --git a/src/Umbraco.Web.Common/Controllers/RenderController.cs b/src/Umbraco.Web.Common/Controllers/RenderController.cs index 2359d6d13c..48fe50facc 100644 --- a/src/Umbraco.Web.Common/Controllers/RenderController.cs +++ b/src/Umbraco.Web.Common/Controllers/RenderController.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; @@ -149,6 +150,20 @@ namespace Umbraco.Web.Common.Controllers break; case UmbracoRouteResult.Success: default: + + // Check if there's a ProxyViewDataFeature in the request. + // If there it is means that we are proxying/executing this controller + // from another controller and we need to merge it's ViewData with this one + // since this one will be empty. + ProxyViewDataFeature saveViewData = HttpContext.Features.Get(); + if (saveViewData != null) + { + foreach (KeyValuePair kv in saveViewData.ViewData) + { + ViewData[kv.Key] = kv.Value; + } + } + // continue normally await next(); break; diff --git a/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs b/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs index 3d97b893c3..8a106c0846 100644 --- a/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs +++ b/src/Umbraco.Web.Website/ActionResults/RedirectToUmbracoPageResult.cs @@ -16,7 +16,7 @@ namespace Umbraco.Web.Website.ActionResults /// /// Redirects to an Umbraco page by Id or Entity /// - public class RedirectToUmbracoPageResult : IActionResult + public class RedirectToUmbracoPageResult : IActionResult, IKeepTempDataResult { private IPublishedContent _publishedContent; private readonly QueryString _queryString; @@ -122,19 +122,15 @@ namespace Umbraco.Web.Website.ActionResults throw new ArgumentNullException(nameof(context)); } - var httpContext = context.HttpContext; - var ioHelper = httpContext.RequestServices.GetRequiredService(); - var destinationUrl = ioHelper.ResolveUrl(Url); + HttpContext httpContext = context.HttpContext; + IIOHelper ioHelper = httpContext.RequestServices.GetRequiredService(); + string destinationUrl = ioHelper.ResolveUrl(Url); if (_queryString.HasValue) { destinationUrl += _queryString.ToUriComponent(); } - var tempDataDictionaryFactory = context.HttpContext.RequestServices.GetRequiredService(); - var tempData = tempDataDictionaryFactory.GetTempData(context.HttpContext); - tempData?.Keep(); - httpContext.Response.Redirect(destinationUrl); return Task.CompletedTask; diff --git a/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs b/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs index 4eb40e8e4e..c2b3a1221b 100644 --- a/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs +++ b/src/Umbraco.Web.Website/ActionResults/UmbracoPageResult.cs @@ -1,21 +1,11 @@ using System; -using System.Collections.Generic; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -using Umbraco.Core; using Umbraco.Core.Logging; -using Umbraco.Extensions; -using Umbraco.Web.Common.Controllers; using Umbraco.Web.Common.Routing; using Umbraco.Web.Website.Controllers; using Umbraco.Web.Website.Routing; @@ -25,14 +15,19 @@ namespace Umbraco.Web.Website.ActionResults /// /// Used by posted forms to proxy the result to the page in which the current URL matches on /// + /// + /// This page does not redirect therefore it does not implement because TempData should + /// only be used in situations when a redirect occurs. It is not good practice to use TempData when redirects do not occur + /// so we'll be strict about it and not save it. + /// public class UmbracoPageResult : IActionResult { private readonly IProfilingLogger _profilingLogger; - public UmbracoPageResult(IProfilingLogger profilingLogger) - { - _profilingLogger = profilingLogger; - } + /// + /// Initializes a new instance of the class. + /// + public UmbracoPageResult(IProfilingLogger profilingLogger) => _profilingLogger = profilingLogger; /// public async Task ExecuteResultAsync(ActionContext context) @@ -47,42 +42,15 @@ namespace Umbraco.Web.Website.ActionResults context.RouteData.Values[UmbracoRouteValueTransformer.ControllerToken] = umbracoRouteValues.ControllerName; context.RouteData.Values[UmbracoRouteValueTransformer.ActionToken] = umbracoRouteValues.ActionName; - // Create a new context and excute the original controller - - // TODO: We need to take into account temp data, view data, etc... all like what we used to do below - // so that validation stuff gets carried accross - - var renderActionContext = new ActionContext(context.HttpContext, context.RouteData, umbracoRouteValues.ControllerActionDescriptor); + // Create a new context and excute the original controller... + // Copy the action context - this also copies the ModelState + var renderActionContext = new ActionContext(context) + { + ActionDescriptor = umbracoRouteValues.ControllerActionDescriptor + }; IActionInvokerFactory actionInvokerFactory = context.HttpContext.RequestServices.GetRequiredService(); IActionInvoker actionInvoker = actionInvokerFactory.CreateInvoker(renderActionContext); await ExecuteControllerAction(actionInvoker); - - //ResetRouteData(context.RouteData); - //ValidateRouteData(context); - - //IControllerFactory factory = context.HttpContext.RequestServices.GetRequiredService(); - //Controller controller = null; - - //if (!(context is ControllerContext controllerContext)) - //{ - // // TODO: Better to throw since this is not expected? - // return Task.FromCanceled(CancellationToken.None); - //} - - //try - //{ - // controller = CreateController(controllerContext, factory); - - // CopyControllerData(controllerContext, controller); - - // ExecuteControllerAction(controllerContext, controller); - //} - //finally - //{ - // CleanupController(controllerContext, controller, factory); - //} - - //return Task.CompletedTask; } /// @@ -95,83 +63,5 @@ namespace Umbraco.Web.Website.ActionResults await actionInvoker.InvokeAsync(); } } - - /// - /// Creates action execution delegate from ActionExecutingContext - /// - private static ActionExecutionDelegate CreateActionExecutedDelegate(ActionExecutingContext context) - { - var actionExecutedContext = new ActionExecutedContext(context, context.Filters, context.Controller) - { - Result = context.Result, - }; - return () => Task.FromResult(actionExecutedContext); - } - - /// - /// Ensure ModelState, ViewData and TempData is copied across - /// - private static void CopyControllerData(ControllerContext context, Controller controller) - { - controller.ViewData.ModelState.Merge(context.ModelState); - - foreach (KeyValuePair d in controller.ViewData) - { - controller.ViewData[d.Key] = d.Value; - } - - // We cannot simply merge the temp data because during controller execution it will attempt to 'load' temp data - // but since it has not been saved, there will be nothing to load and it will revert to nothing, so the trick is - // to Save the state of the temp data first then it will automatically be picked up. - // http://issues.umbraco.org/issue/U4-1339 - - var targetController = controller; - var tempDataDictionaryFactory = context.HttpContext.RequestServices.GetRequiredService(); - var tempData = tempDataDictionaryFactory.GetTempData(context.HttpContext); - - targetController.TempData = tempData; - targetController.TempData.Save(); - } - - /// - /// Creates a controller using the controller factory - /// - private static Controller CreateController(ControllerContext context, IControllerFactory factory) - { - if (!(factory.CreateController(context) is Controller controller)) - { - throw new InvalidOperationException("Could not create controller with name " + context.ActionDescriptor.ControllerName + "."); - } - - return controller; - } - - /// - /// Cleans up the controller by releasing it using the controller factory, and by disposing it. - /// - private static void CleanupController(ControllerContext context, Controller controller, IControllerFactory factory) - { - if (!(controller is null)) - { - factory.ReleaseController(context, controller); - } - - controller?.DisposeIfDisposable(); - } - - private class DummyView : IView - { - public DummyView(string path) - { - Path = path; - } - - public Task RenderAsync(ViewContext context) - { - return Task.CompletedTask; - } - - public string Path { get; } - } } } diff --git a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs index f7098e316d..4db3c605c9 100644 --- a/src/Umbraco.Web.Website/Controllers/SurfaceController.cs +++ b/src/Umbraco.Web.Website/Controllers/SurfaceController.cs @@ -101,6 +101,9 @@ namespace Umbraco.Web.Website.Controllers /// Returns the currently rendered Umbraco page /// protected UmbracoPageResult CurrentUmbracoPage() - => new UmbracoPageResult(ProfilingLogger); + { + HttpContext.Features.Set(new ProxyViewDataFeature(ViewData)); + return new UmbracoPageResult(ProfilingLogger); + } } }