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 9fbb3ac34c..214532b6e4 100644
--- a/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs
+++ b/src/Umbraco.Web/Mvc/IFilteredControllerFactory.cs
@@ -1,9 +1,11 @@
+using System;
using System.Linq;
using System.Web.Mvc;
using System.Web.Routing;
namespace Umbraco.Web.Mvc
{
+
public interface IFilteredControllerFactory : IControllerFactory
{
///
@@ -13,5 +15,6 @@ namespace Umbraco.Web.Mvc
/// true if this instance can handle the specified request; otherwise, false.
///
bool CanHandle(RequestContext request);
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs
index 357dbf899c..4a58fb7251 100644
--- a/src/Umbraco.Web/Mvc/MasterControllerFactory.cs
+++ b/src/Umbraco.Web/Mvc/MasterControllerFactory.cs
@@ -45,6 +45,37 @@ namespace Umbraco.Web.Mvc
: base.CreateController(requestContext, controllerName);
}
+ ///
+ /// Retrieves the controller type for the specified name and request context.
+ ///
+ ///
+ ///
+ /// The controller type.
+ ///
+ /// The context of the HTTP request, which includes the HTTP context and route data.
+ /// The name of the controller.
+ internal Type GetControllerTypeInternal(RequestContext requestContext, string controllerName)
+ {
+ var factory = _slaveFactories.Factories.FirstOrDefault(x => x.CanHandle(requestContext));
+ 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);
+ }
+
///
/// Releases the specified controller.
///
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 56270ea36e..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
- ///
- ///
- ///
- ///
- protected 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 e0cf509b72..e8ebc21516 100644
--- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs
+++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs
@@ -4,6 +4,7 @@ using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
+using System.Web.SessionState;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Configuration;
@@ -181,35 +182,46 @@ namespace Umbraco.Web.Mvc
requestContext.RouteData.Values["controller"] = postedInfo.ControllerName;
requestContext.RouteData.Values["action"] = postedInfo.ActionName;
- IHttpHandler handler = new MvcHandler(requestContext);
-
- //ensure the controllerType is set if found, meaning it is a plugin, not locally declared
+ IHttpHandler handler;
+
+ //get the route from the defined routes
if (!postedInfo.Area.IsNullOrWhiteSpace())
- {
- //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") &&
+ 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().InvariantEquals(postedInfo.ControllerName) &&
- x.DataTokens.ContainsKey("area") &&
+ x.DataTokens.ContainsKey("area") &&
x.DataTokens["area"].ToString().InvariantEquals(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);
+ }
+
+ if (surfaceRoute == null)
+ throw new InvalidOperationException("Could not find a Surface controller route in the RouteTable for controller name " + postedInfo.ControllerName);
requestContext.RouteData.DataTokens["area"] = surfaceRoute.DataTokens["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);
- }
+ //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 route definition
requestContext.RouteData.DataTokens["umbraco-route-def"] = routeDefinition;
@@ -230,57 +242,53 @@ namespace Umbraco.Web.Mvc
var def = new RouteDefinition
{
ControllerName = defaultControllerName,
- Controller = new RenderMvcController(UmbracoContext),
+ ControllerType = typeof(RenderMvcController),
PublishedContentRequest = publishedContentRequest,
ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString(),
HasHijackedRoute = false
};
+ //check that a template is defined), if it doesn't and there is a hijacked route it will just route
+ // to the index Action
+ if (publishedContentRequest.HasTemplate)
+ {
+ //the template Alias should always be already saved with a safe name.
+ //if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed
+ // with the action name attribute.
+ var templateName = publishedContentRequest.TemplateAlias.Split('.')[0].ToSafeAlias();
+ def.ActionName = templateName;
+ }
+
//check if there's a custom controller assigned, base on the document type alias.
- var controller = _controllerFactory.CreateController(requestContext, publishedContentRequest.PublishedContent.DocumentTypeAlias);
-
-
+ var controllerType = ((MasterControllerFactory)_controllerFactory).GetControllerTypeInternal(requestContext, publishedContentRequest.PublishedContent.DocumentTypeAlias);
+
//check if that controller exists
- if (controller != null)
- {
+ if (controllerType != null)
+ {
+ //ensure the controller is of type 'RenderMvcController'
+ if (controllerType.IsSubclassOf(typeof (RenderMvcController)))
+ {
+ //set the controller and name to the custom one
+ def.ControllerType = controllerType;
+ def.ControllerName = ControllerExtensions.GetControllerName(controllerType);
+ if (def.ControllerName != defaultControllerName)
+ {
+ def.HasHijackedRoute = true;
+ }
+ }
+ else
+ {
+ LogHelper.Warn(
+ "The current Document Type {0} matches a locally declared controller of type {1}. Custom Controllers for Umbraco routing must inherit from '{2}'.",
+ () => publishedContentRequest.PublishedContent.DocumentTypeAlias,
+ () => controllerType.FullName,
+ () => typeof (RenderMvcController).FullName);
+ //exit as we cannnot route to the custom controller, just route to the standard one.
+ return def;
+ }
+ }
- //ensure the controller is of type 'RenderMvcController'
- if (controller is RenderMvcController)
- {
- //set the controller and name to the custom one
- def.Controller = (ControllerBase)controller;
- def.ControllerName = ControllerExtensions.GetControllerName(controller.GetType());
- if (def.ControllerName != defaultControllerName)
- {
- def.HasHijackedRoute = true;
- }
- }
- else
- {
- LogHelper.Warn(
- "The current Document Type {0} matches a locally declared controller of type {1}. Custom Controllers for Umbraco routing must inherit from '{2}'.",
- () => publishedContentRequest.PublishedContent.DocumentTypeAlias,
- () => controller.GetType().FullName,
- () => typeof(RenderMvcController).FullName);
- //exit as we cannnot route to the custom controller, just route to the standard one.
- return def;
- }
-
- //check that a template is defined), if it doesn't and there is a hijacked route it will just route
- // to the index Action
- if (publishedContentRequest.HasTemplate)
- {
- //the template Alias should always be already saved with a safe name.
- //if there are hyphens in the name and there is a hijacked route, then the Action will need to be attributed
- // with the action name attribute.
- var templateName = publishedContentRequest.TemplateAlias.Split('.')[0].ToSafeAlias();
- def.ActionName = templateName;
- }
-
- }
-
-
- return def;
+ return def;
}
internal IHttpHandler GetHandlerOnMissingTemplate(PublishedContentRequest pcr)
@@ -363,11 +371,19 @@ namespace Umbraco.Web.Mvc
requestContext.RouteData.Values["action"] = routeDef.ActionName;
}
+ // Set the session state requirements
+ requestContext.HttpContext.SetSessionStateBehavior(GetSessionStateBehavior(requestContext, routeDef.ControllerName));
+
// reset the friendly path so in the controllers and anything occuring after this point in time,
//the URL is reset back to the original request.
requestContext.HttpContext.RewritePath(UmbracoContext.OriginalRequestUrl.PathAndQuery);
- return new MvcHandler(requestContext);
+ return new UmbracoMvcHandler(requestContext);
}
+
+ private SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext, string controllerName)
+ {
+ return _controllerFactory.GetControllerSessionBehavior(requestContext, controllerName);
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Web/Mvc/RouteDefinition.cs b/src/Umbraco.Web/Mvc/RouteDefinition.cs
index e9b7ffe378..49ba64fac5 100644
--- a/src/Umbraco.Web/Mvc/RouteDefinition.cs
+++ b/src/Umbraco.Web/Mvc/RouteDefinition.cs
@@ -1,3 +1,4 @@
+using System;
using System.Web.Mvc;
using Umbraco.Web.Routing;
@@ -16,6 +17,12 @@ namespace Umbraco.Web.Mvc
///
public ControllerBase Controller { get; set; }
+ ///
+ /// The Controller type found for routing to
+ ///
+ public Type ControllerType { get; set; }
+
+
///
/// The current RenderModel found for the request
///
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/Mvc/UmbracoMvcHandler.cs b/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs
new file mode 100644
index 0000000000..1ace297bfd
--- /dev/null
+++ b/src/Umbraco.Web/Mvc/UmbracoMvcHandler.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Web;
+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.
+ ///
+ /// The alternate option for this is to completely rewrite all MvcHandler methods.
+ ///
+ /// This is currently needed for the 'return CurrentUmbracoPage()' surface controller functionality
+ /// because it needs to send data back to the page controller.
+ ///
+ internal class UmbracoMvcHandler : MvcHandler
+ {
+ public UmbracoMvcHandler(RequestContext requestContext)
+ : 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;
+ }
+
+ 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/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj
index a09aa5ccb7..22238d67e7 100644
--- a/src/Umbraco.Web/Umbraco.Web.csproj
+++ b/src/Umbraco.Web/Umbraco.Web.csproj
@@ -300,9 +300,12 @@
+
+
+
diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs
index ac72fe30c6..7fa62a6c8a 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)