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.
This commit is contained in:
Shannon Deminick
2013-02-19 07:30:46 +06:00
parent 55f06a7ca6
commit d8721197b7
10 changed files with 165 additions and 125 deletions

View File

@@ -26,7 +26,7 @@ namespace Umbraco.Web.Mvc
/// <param name="umbracoTokenValue">The DataToken value to set for the 'umbraco' key, this defaults to 'backoffice' </param>
/// <remarks>
/// </remarks>
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;
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Web.Routing;
namespace Umbraco.Web.Mvc
{
public interface IFilteredControllerFactory : IControllerFactory
{
/// <summary>
@@ -15,12 +16,5 @@ namespace Umbraco.Web.Mvc
/// <remarks></remarks>
bool CanHandle(RequestContext request);
/// <summary>
/// Returns the controller type for the controller name otherwise null if not found
/// </summary>
/// <param name="requestContext"></param>
/// <param name="controllerName"></param>
/// <returns></returns>
Type GetControllerType(RequestContext requestContext, string controllerName);
}
}

View File

@@ -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);
}
/// <summary>

View File

@@ -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();
}
}
}

View File

@@ -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
{
/// <summary>
/// <summary>
/// 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.
/// </summary>
/// <remarks></remarks>
public class RenderControllerFactory : IFilteredControllerFactory
public class RenderControllerFactory : UmbracoControllerFactory
{
private readonly OverridenDefaultControllerFactory _innerFactory = new OverridenDefaultControllerFactory();
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public RenderControllerFactory()
{
}
/// <summary>
/// Determines whether this instance can handle the specified request.
/// </summary>
/// <param name="request">The request.</param>
/// <returns><c>true</c> if this instance can handle the specified request; otherwise, <c>false</c>.</returns>
/// <remarks></remarks>
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());
}
/// <summary>
/// Returns the controller type for the controller name otherwise null if not found
/// </summary>
/// <param name="requestContext"></param>
/// <param name="controllerName"></param>
/// <returns></returns>
public Type GetControllerType(RequestContext requestContext, string controllerName)
{
return _innerFactory.GetControllerType(requestContext, controllerName);
}
/// <summary>
/// Creates the specified controller by using the specified request context.
/// </summary>
/// <returns>
/// The controller.
/// </returns>
/// <param name="requestContext">The request context.</param><param name="controllerName">The name of the controller.</param>
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);
}
/// <summary>
/// Gets the controller's session behavior.
/// </summary>
/// <returns>
/// The controller's session behavior.
/// </returns>
/// <param name="requestContext">The request context.</param><param name="controllerName">The name of the controller whose session behavior you want to get.</param>
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return ((IControllerFactory)_innerFactory).GetControllerSessionBehavior(requestContext, controllerName);
}
/// <summary>
/// Releases the specified controller.
/// </summary>
/// <param name="controller">The controller.</param>
public void ReleaseController(IController controller)
{
_innerFactory.ReleaseController(controller);
}
/// <summary>
/// By default, <see cref="DefaultControllerFactory"/> only exposes <see cref="IControllerFactory.CreateController"/> which throws an exception
/// if the controller is not found. Since we want to try creating a controller, and then fall back to <see cref="RenderMvcController"/> if one isn't found,
/// this nested class changes the visibility of <see cref="DefaultControllerFactory"/>'s internal methods in order to not have to rely on a try-catch.
/// </summary>
/// <remarks></remarks>
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);
}
}
}
}

View File

@@ -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<Route>()
.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<Route>()
.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<Route>()
.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;

View File

@@ -0,0 +1,16 @@
using System.Web;
using System.Web.Routing;
namespace Umbraco.Web.Mvc
{
/// <summary>
/// Assigned to all SurfaceController's so that it returns our custom SurfaceMvcHandler to use for rendering
/// </summary>
internal class SurfaceRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new UmbracoMvcHandler(requestContext);
}
}
}

View File

@@ -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
{
/// <summary>
/// Abstract filtered controller factory used for all Umbraco controller factory implementations
/// </summary>
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);
}
/// <summary>
/// Creates the specified controller by using the specified request context.
/// </summary>
/// <returns>
/// The controller.
/// </returns>
/// <param name="requestContext">The request context.</param><param name="controllerName">The name of the controller.</param>
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);
}
/// <summary>
/// Gets the controller's session behavior.
/// </summary>
/// <returns>
/// The controller's session behavior.
/// </returns>
/// <param name="requestContext">The request context.</param><param name="controllerName">The name of the controller whose session behavior you want to get.</param>
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return ((IControllerFactory)_innerFactory).GetControllerSessionBehavior(requestContext, controllerName);
}
/// <summary>
/// Releases the specified controller.
/// </summary>
/// <param name="controller">The controller.</param>
public void ReleaseController(IController controller)
{
_innerFactory.ReleaseController(controller);
}
/// <summary>
/// By default, <see cref="DefaultControllerFactory"/> only exposes <see cref="IControllerFactory.CreateController"/> which throws an exception
/// if the controller is not found. Since we want to try creating a controller, and then fall back to <see cref="RenderMvcController"/> if one isn't found,
/// this nested class changes the visibility of <see cref="DefaultControllerFactory"/>'s internal methods in order to not have to rely on a try-catch.
/// </summary>
/// <remarks></remarks>
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);
}
}
}
}

View File

@@ -300,9 +300,11 @@
<Compile Include="DefaultPublishedMediaStore.cs" />
<Compile Include="Dictionary\UmbracoCultureDictionary.cs" />
<Compile Include="Dictionary\UmbracoCultureDictionaryFactory.cs" />
<Compile Include="Mvc\SurfaceRouteHandler.cs" />
<Compile Include="Mvc\UmbracoAuthorizeAttribute.cs" />
<Compile Include="Mvc\UmbracoAuthorizedController.cs" />
<Compile Include="Mvc\UmbracoController.cs" />
<Compile Include="Mvc\UmbracoControllerFactory.cs" />
<Compile Include="Mvc\UmbracoMvcHandler.cs" />
<Compile Include="Mvc\UmbracoViewPage.cs" />
<Compile Include="PublishedContentExtensions.cs" />

View File

@@ -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)