diff --git a/src/Umbraco.Web/Mvc/RouteDefinition.cs b/src/Umbraco.Web/Mvc/RouteDefinition.cs index 49ba64fac5..f0334202dd 100644 --- a/src/Umbraco.Web/Mvc/RouteDefinition.cs +++ b/src/Umbraco.Web/Mvc/RouteDefinition.cs @@ -11,12 +11,7 @@ namespace Umbraco.Web.Mvc { public string ControllerName { get; set; } public string ActionName { get; set; } - - /// - /// The Controller instance found for routing to - /// - public ControllerBase Controller { get; set; } - + /// /// The Controller type found for routing to /// diff --git a/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs b/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs index 649ac54f27..6e7a92ea10 100644 --- a/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs +++ b/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs @@ -1,23 +1,18 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Web; -using System.Web.Mvc; +using System.Web.Mvc; using System.Web.Routing; namespace Umbraco.Web.Mvc { /// - /// Mvc handler class to intercept creation of controller and store it for later use. - /// This means we create two instances of the same controller to support some features later on. + /// MVC handler to facilitate the TemplateRenderer. This handler can execute an MVC request and return it as a string. /// - /// The alternate option for this is to completely rewrite all MvcHandler methods. + /// Original: /// - /// This is currently needed for the 'return CurrentUmbracoPage()' surface controller functionality + /// This handler also used to intercept creation of controllers and store it for later use. + /// This was needed for the 'return CurrentUmbracoPage()' surface controller functionality /// because it needs to send data back to the page controller. + /// + /// The creation of this controller has been moved to the UmbracoPageResult class which will create a controller when needed. /// internal class UmbracoMvcHandler : MvcHandler { @@ -25,46 +20,13 @@ namespace Umbraco.Web.Mvc : base(requestContext) { } - - private void StoreControllerInRouteDefinition() - { - var routeDef = (RouteDefinition)RequestContext.RouteData.DataTokens["umbraco-route-def"]; - - if (routeDef == null) return; - - // Get the factory and controller and create a new instance of the controller - var factory = ControllerBuilder.Current.GetControllerFactory(); - var controller = factory.CreateController(RequestContext, routeDef.ControllerName) as ControllerBase; - - // Store the controller - routeDef.Controller = controller; - } - + /// /// This is used internally purely to render an Umbraco MVC template to string and shouldn't be used for anything else. /// internal void ExecuteUmbracoRequest() { - StoreControllerInRouteDefinition(); base.ProcessRequest(RequestContext.HttpContext); } - - protected override void ProcessRequest(HttpContextBase httpContext) - { - StoreControllerInRouteDefinition(); - - // Let MVC do its magic and continue the request - base.ProcessRequest(httpContext); - } - - protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, - object state) - { - StoreControllerInRouteDefinition(); - - return base.BeginProcessRequest(httpContext, callback, state); - } } - - } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs index 9ae5006d48..6b44959c8c 100644 --- a/src/Umbraco.Web/Mvc/UmbracoPageResult.cs +++ b/src/Umbraco.Web/Mvc/UmbracoPageResult.cs @@ -1,5 +1,6 @@ using System; using System.Web.Mvc; +using System.Web.Routing; using Umbraco.Core; namespace Umbraco.Web.Mvc @@ -11,54 +12,100 @@ namespace Umbraco.Web.Mvc { public override void ExecuteResult(ControllerContext context) { + ResetRouteData(context.RouteData); - //since we could be returning the current page from a surface controller posted values in which the routing values are changed, we - //need to revert these values back to nothing in order for the normal page to render again. - context.RouteData.DataTokens["area"] = null; - context.RouteData.DataTokens["Namespaces"] = null; - - //validate that the current page execution is not being handled by the normal umbraco routing system - if (!context.RouteData.DataTokens.ContainsKey("umbraco-route-def")) - { - throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + " in the context of an Http POST when using a SurfaceController form"); - } + ValidateRouteData(context.RouteData); var routeDef = (RouteDefinition)context.RouteData.DataTokens["umbraco-route-def"]; + var factory = ControllerBuilder.Current.GetControllerFactory(); - var targetController = ((Controller)routeDef.Controller); - var sourceController = ((Controller) context.Controller); - - //ensure the original template is reset context.RouteData.Values["action"] = routeDef.ActionName; - //ensure ModelState is copied across - routeDef.Controller.ViewData.ModelState.Merge(sourceController.ViewData.ModelState); + ControllerBase controller = null; - //ensure TempData and ViewData is copied across - foreach (var d in sourceController.ViewData) - routeDef.Controller.ViewData[d.Key] = d.Value; + try + { + controller = CreateController(context, factory, routeDef); + + controller.ViewData.ModelState.Merge(context.Controller.ViewData.ModelState); + CopyControllerData(context, controller); - //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 - targetController.TempDataProvider = sourceController.TempDataProvider; - targetController.TempData = sourceController.TempData; - targetController.TempData.Save(sourceController.ControllerContext, sourceController.TempDataProvider); - - using (DisposableTimer.TraceDuration("Executing Umbraco RouteDefinition controller", "Finished")) - { - try - { - ((IController)targetController).Execute(context.RequestContext); - } - finally - { - routeDef.Controller.DisposeIfDisposable(); - } - } - + ExecuteControllerAction(context, controller); + } + finally + { + CleanupController(controller, factory); + } } + + /// + /// Executes the controller action + /// + private static void ExecuteControllerAction(ControllerContext context, IController controller) + { + using (DisposableTimer.TraceDuration("Executing Umbraco RouteDefinition controller", "Finished")) + { + controller.Execute(context.RequestContext); + } + } + + /// + /// Since we could be returning the current page from a surface controller posted values in which the routing values are changed, we + /// need to revert these values back to nothing in order for the normal page to render again. + /// + private static void ResetRouteData(RouteData routeData) + { + routeData.DataTokens["area"] = null; + routeData.DataTokens["Namespaces"] = null; + } + + /// + /// Validate that the current page execution is not being handled by the normal umbraco routing system + /// + private static void ValidateRouteData(RouteData routeData) + { + if (!routeData.DataTokens.ContainsKey("umbraco-route-def")) + { + throw new InvalidOperationException("Can only use " + typeof(UmbracoPageResult).Name + + " in the context of an Http POST when using a SurfaceController form"); + } + } + + /// + /// Ensure TempData and ViewData is copied across + /// + private static void CopyControllerData(ControllerContext context, ControllerBase controller) + { + foreach (var d in context.Controller.ViewData) + controller.ViewData[d.Key] = d.Value; + + controller.TempData = context.Controller.TempData; + } + + /// + /// Creates a controller using the controller factory + /// + private static ControllerBase CreateController(ControllerContext context, IControllerFactory factory, RouteDefinition routeDef) + { + var controller = factory.CreateController(context.RequestContext, routeDef.ControllerName) as ControllerBase; + + if (controller == null) + throw new InvalidOperationException("Could not create controller with name " + routeDef.ControllerName + "."); + + return controller; + } + + /// + /// Cleans up the controller by releasing it using the controller factory, and by disposing it. + /// + private static void CleanupController(IController controller, IControllerFactory factory) + { + if (controller != null) + factory.ReleaseController(controller); + + if (controller != null) + controller.DisposeIfDisposable(); + } } } \ No newline at end of file