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)