From d8721197b75e0f6c9eb7ae7dc2b55badabcb6f6d Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 19 Feb 2013 07:30:46 +0600 Subject: [PATCH] Fixes up pull request so that there is no breaking changes. We now type check for a new base class called UmbracoControllerFactory to get the controller type. Adds SurfaceRouteHandler to ensure that UmbracoMvcHandler is used for all SurfaceControllers, and updates all routes accordingly. Streamlines the RenderRouteHandler to then just look up surface routes in the route table for posted values. This now ensures that UmbracoMvcHandler is used even with plugin surface controllers. --- .../Mvc/AreaRegistrationExtensions.cs | 3 +- .../Mvc/IFilteredControllerFactory.cs | 8 +- .../Mvc/MasterControllerFactory.cs | 20 +++- src/Umbraco.Web/Mvc/PluginControllerArea.cs | 4 +- .../Mvc/RenderControllerFactory.cs | 91 +------------------ src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 60 +++++++----- src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs | 16 ++++ .../Mvc/UmbracoControllerFactory.cs | 82 +++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + src/Umbraco.Web/WebBootManager.cs | 4 +- 10 files changed, 165 insertions(+), 125 deletions(-) create mode 100644 src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs create mode 100644 src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs diff --git a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs index c867a0823d..cc7cbbca19 100644 --- a/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs +++ b/src/Umbraco.Web/Mvc/AreaRegistrationExtensions.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Mvc /// The DataToken value to set for the 'umbraco' key, this defaults to 'backoffice' /// /// - internal static void RouteControllerPlugin(this AreaRegistration area, string controllerName, Type controllerType, RouteCollection routes, + internal static Route RouteControllerPlugin(this AreaRegistration area, string controllerName, Type controllerType, RouteCollection routes, string controllerSuffixName, string defaultAction, object defaultId, string umbracoTokenValue = "backoffice") { @@ -72,6 +72,7 @@ namespace Umbraco.Web.Mvc controllerPluginRoute.DataTokens.Add("area", area.AreaName); controllerPluginRoute.DataTokens.Add("umbraco", umbracoTokenValue); //ensure the umbraco token is set + return controllerPluginRoute; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs b/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs index e6697103ad..214532b6e4 100644 --- a/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs @@ -5,6 +5,7 @@ using System.Web.Routing; namespace Umbraco.Web.Mvc { + public interface IFilteredControllerFactory : IControllerFactory { /// @@ -15,12 +16,5 @@ namespace Umbraco.Web.Mvc /// bool CanHandle(RequestContext request); - /// - /// Returns the controller type for the controller name otherwise null if not found - /// - /// - /// - /// - Type GetControllerType(RequestContext requestContext, string controllerName); } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs index 1f4d4b0a5f..4a58fb7251 100644 --- a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs @@ -57,9 +57,23 @@ namespace Umbraco.Web.Mvc internal Type GetControllerTypeInternal(RequestContext requestContext, string controllerName) { var factory = _slaveFactories.Factories.FirstOrDefault(x => x.CanHandle(requestContext)); - return factory != null - ? factory.GetControllerType(requestContext, controllerName) - : base.GetControllerType(requestContext, controllerName); + if (factory != null) + { + //check to see if the factory is of type UmbracoControllerFactory which exposes the GetControllerType method so we don't have to create + // an instance of the controller to figure out what it is. This is a work around for not having a breaking change for: + // http://issues.umbraco.org/issue/U4-1726 + + var umbFactory = factory as UmbracoControllerFactory; + if (umbFactory != null) + { + return umbFactory.GetControllerType(requestContext, controllerName); + } + //we have no choice but to instantiate the controller + var instance = factory.CreateController(requestContext, controllerName); + return instance.GetType(); + } + + return base.GetControllerType(requestContext, controllerName); } /// diff --git a/src/Umbraco.Web/Mvc/PluginControllerArea.cs b/src/Umbraco.Web/Mvc/PluginControllerArea.cs index 591d53480a..42652eafd4 100644 --- a/src/Umbraco.Web/Mvc/PluginControllerArea.cs +++ b/src/Umbraco.Web/Mvc/PluginControllerArea.cs @@ -67,7 +67,9 @@ namespace Umbraco.Web.Mvc { foreach (var s in surfaceControllers) { - this.RouteControllerPlugin(s.ControllerName, s.ControllerType, routes, "Surface", "Index", UrlParameter.Optional, "surface"); + var route = this.RouteControllerPlugin(s.ControllerName, s.ControllerType, routes, "Surface", "Index", UrlParameter.Optional, "surface"); + //set the route handler to our SurfaceRouteHandler + route.RouteHandler = new SurfaceRouteHandler(); } } } diff --git a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs index 770c57c3bb..9482815a03 100644 --- a/src/Umbraco.Web/Mvc/RenderControllerFactory.cs +++ b/src/Umbraco.Web/Mvc/RenderControllerFactory.cs @@ -1,109 +1,26 @@ -using System; -using System.Web.Mvc; using System.Web.Routing; -using System.Web.SessionState; -using Umbraco.Core; namespace Umbraco.Web.Mvc { - /// + /// /// A controller factory for the render pipeline of Umbraco. This controller factory tries to create a controller with the supplied /// name, and falls back to UmbracoController if none was found. /// /// - public class RenderControllerFactory : IFilteredControllerFactory + public class RenderControllerFactory : UmbracoControllerFactory { - private readonly OverridenDefaultControllerFactory _innerFactory = new OverridenDefaultControllerFactory(); - - /// - /// Initializes a new instance of the class. - /// - public RenderControllerFactory() - { - - } - - + /// /// Determines whether this instance can handle the specified request. /// /// The request. /// true if this instance can handle the specified request; otherwise, false. /// - public virtual bool CanHandle(RequestContext request) + public override bool CanHandle(RequestContext request) { var dataToken = request.RouteData.DataTokens["area"]; return dataToken == null || string.IsNullOrWhiteSpace(dataToken.ToString()); } - /// - /// Returns the controller type for the controller name otherwise null if not found - /// - /// - /// - /// - public Type GetControllerType(RequestContext requestContext, string controllerName) - { - return _innerFactory.GetControllerType(requestContext, controllerName); - } - - /// - /// Creates the specified controller by using the specified request context. - /// - /// - /// The controller. - /// - /// The request context.The name of the controller. - public virtual IController CreateController(RequestContext requestContext, string controllerName) - { - Type controllerType = GetControllerType(requestContext, controllerName) ?? - _innerFactory.GetControllerType(requestContext, ControllerExtensions.GetControllerName(typeof(RenderMvcController))); - - return _innerFactory.GetControllerInstance(requestContext, controllerType); - } - - /// - /// Gets the controller's session behavior. - /// - /// - /// The controller's session behavior. - /// - /// The request context.The name of the controller whose session behavior you want to get. - public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) - { - return ((IControllerFactory)_innerFactory).GetControllerSessionBehavior(requestContext, controllerName); - } - - /// - /// Releases the specified controller. - /// - /// The controller. - public void ReleaseController(IController controller) - { - _innerFactory.ReleaseController(controller); - } - - /// - /// By default, only exposes which throws an exception - /// if the controller is not found. Since we want to try creating a controller, and then fall back to if one isn't found, - /// this nested class changes the visibility of 's internal methods in order to not have to rely on a try-catch. - /// - /// - internal class OverridenDefaultControllerFactory : DefaultControllerFactory - { - public new IController GetControllerInstance(RequestContext requestContext, Type controllerType) - { - return base.GetControllerInstance(requestContext, controllerType); - } - - public new Type GetControllerType(RequestContext requestContext, string controllerName) - { - if (controllerName.IsNullOrWhiteSpace()) - { - return null; - } - return base.GetControllerType(requestContext, controllerName); - } - } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index a0c943745c..680d94ff79 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -185,34 +185,44 @@ namespace Umbraco.Web.Mvc requestContext.RouteData.Values["action"] = postedInfo.ActionName; requestContext.RouteData.DataTokens["area"] = postedInfo.Area; - IHttpHandler handler = new UmbracoMvcHandler(requestContext); + IHttpHandler handler; + + //get the route from the defined routes + using (RouteTable.Routes.GetReadLock()) + { + Route surfaceRoute; + if (postedInfo.Area == standardArea) + { + //find the controller in the route table without an area + surfaceRoute = RouteTable.Routes.OfType() + .SingleOrDefault(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString() == postedInfo.ControllerName && + !x.DataTokens.ContainsKey("area")); + } + else + { + //find the controller in the route table with the specified area + surfaceRoute = RouteTable.Routes.OfType() + .SingleOrDefault(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString() == postedInfo.ControllerName && + x.DataTokens.ContainsKey("area") && + x.DataTokens["area"].ToString() == postedInfo.Area); + } - //ensure the controllerType is set if found, meaning it is a plugin, not locally declared - if (postedInfo.Area != standardArea) - { - //requestContext.RouteData.Values["controllerType"] = postedInfo.ControllerType; - //find the other data tokens for this route and merge... things like Namespace will be included here - using (RouteTable.Routes.GetReadLock()) - { - var surfaceRoute = RouteTable.Routes.OfType() - .SingleOrDefault(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - x.Defaults["controller"].ToString() == postedInfo.ControllerName && - x.DataTokens.ContainsKey("area") && - x.DataTokens["area"].ToString() == postedInfo.Area); - if (surfaceRoute == null) - throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for controller name " + postedInfo.ControllerName + " and within the area of " + postedInfo.Area); - //set the 'Namespaces' token so the controller factory knows where to look to construct it - if (surfaceRoute.DataTokens.ContainsKey("Namespaces")) - { - requestContext.RouteData.DataTokens["Namespaces"] = surfaceRoute.DataTokens["Namespaces"]; - } - handler = surfaceRoute.RouteHandler.GetHttpHandler(requestContext); - } + if (surfaceRoute == null) + throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for controller name " + postedInfo.ControllerName); + //set the 'Namespaces' token so the controller factory knows where to look to construct it + if (surfaceRoute.DataTokens.ContainsKey("Namespaces")) + { + requestContext.RouteData.DataTokens["Namespaces"] = surfaceRoute.DataTokens["Namespaces"]; + } + handler = surfaceRoute.RouteHandler.GetHttpHandler(requestContext); - } + } - //store the original URL this came in on + //store the original URL this came in on requestContext.RouteData.DataTokens["umbraco-item-url"] = requestContext.HttpContext.Request.Url.AbsolutePath; //store the original route definition requestContext.RouteData.DataTokens["umbraco-route-def"] = routeDefinition; diff --git a/src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs b/src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs new file mode 100644 index 0000000000..8e56cd715f --- /dev/null +++ b/src/Umbraco.Web/Mvc/SurfaceRouteHandler.cs @@ -0,0 +1,16 @@ +using System.Web; +using System.Web.Routing; + +namespace Umbraco.Web.Mvc +{ + /// + /// Assigned to all SurfaceController's so that it returns our custom SurfaceMvcHandler to use for rendering + /// + internal class SurfaceRouteHandler : IRouteHandler + { + public IHttpHandler GetHttpHandler(RequestContext requestContext) + { + return new UmbracoMvcHandler(requestContext); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs b/src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs new file mode 100644 index 0000000000..a8f3817b6c --- /dev/null +++ b/src/Umbraco.Web/Mvc/UmbracoControllerFactory.cs @@ -0,0 +1,82 @@ +using System; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.SessionState; +using Umbraco.Core; + +namespace Umbraco.Web.Mvc +{ + /// + /// Abstract filtered controller factory used for all Umbraco controller factory implementations + /// + public abstract class UmbracoControllerFactory : IFilteredControllerFactory + { + private readonly OverridenDefaultControllerFactory _innerFactory = new OverridenDefaultControllerFactory(); + + public abstract bool CanHandle(RequestContext request); + + public virtual Type GetControllerType(RequestContext requestContext, string controllerName) + { + return _innerFactory.GetControllerType(requestContext, controllerName); + } + + /// + /// Creates the specified controller by using the specified request context. + /// + /// + /// The controller. + /// + /// The request context.The name of the controller. + public virtual IController CreateController(RequestContext requestContext, string controllerName) + { + Type controllerType = GetControllerType(requestContext, controllerName) ?? + _innerFactory.GetControllerType(requestContext, ControllerExtensions.GetControllerName(typeof(RenderMvcController))); + + return _innerFactory.GetControllerInstance(requestContext, controllerType); + } + + /// + /// Gets the controller's session behavior. + /// + /// + /// The controller's session behavior. + /// + /// The request context.The name of the controller whose session behavior you want to get. + public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName) + { + return ((IControllerFactory)_innerFactory).GetControllerSessionBehavior(requestContext, controllerName); + } + + /// + /// Releases the specified controller. + /// + /// The controller. + public void ReleaseController(IController controller) + { + _innerFactory.ReleaseController(controller); + } + + /// + /// By default, only exposes which throws an exception + /// if the controller is not found. Since we want to try creating a controller, and then fall back to if one isn't found, + /// this nested class changes the visibility of 's internal methods in order to not have to rely on a try-catch. + /// + /// + internal class OverridenDefaultControllerFactory : DefaultControllerFactory + { + public new IController GetControllerInstance(RequestContext requestContext, Type controllerType) + { + return base.GetControllerInstance(requestContext, controllerType); + } + + public new Type GetControllerType(RequestContext requestContext, string controllerName) + { + if (controllerName.IsNullOrWhiteSpace()) + { + return null; + } + return base.GetControllerType(requestContext, controllerName); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ef55c9055f..22238d67e7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -300,9 +300,11 @@ + + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 9e130e8fe9..6e68259fb2 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -153,7 +153,9 @@ namespace Umbraco.Web umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, new[] { meta.ControllerNamespace }); //only match this namespace - route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set + route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set + //make it use our custom/special SurfaceMvcHandler + route.RouteHandler = new SurfaceRouteHandler(); } //need to get the plugin controllers that are unique to each area (group by)