This commit is contained in:
Shannon Deminick
2013-02-19 07:32:57 +06:00
12 changed files with 293 additions and 156 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

@@ -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
{
/// <summary>
@@ -13,5 +15,6 @@ namespace Umbraco.Web.Mvc
/// <returns><c>true</c> if this instance can handle the specified request; otherwise, <c>false</c>.</returns>
/// <remarks></remarks>
bool CanHandle(RequestContext request);
}
}

View File

@@ -45,6 +45,37 @@ namespace Umbraco.Web.Mvc
: base.CreateController(requestContext, controllerName);
}
/// <summary>
/// Retrieves the controller type for the specified name and request context.
/// </summary>
///
/// <returns>
/// The controller type.
/// </returns>
/// <param name="requestContext">The context of the HTTP request, which includes the HTTP context and route data.</param>
/// <param name="controllerName">The name of the controller.</param>
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);
}
/// <summary>
/// Releases the specified controller.
/// </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>
protected 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

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

View File

@@ -1,3 +1,4 @@
using System;
using System.Web.Mvc;
using Umbraco.Web.Routing;
@@ -16,6 +17,12 @@ namespace Umbraco.Web.Mvc
/// </summary>
public ControllerBase Controller { get; set; }
/// <summary>
/// The Controller type found for routing to
/// </summary>
public Type ControllerType { get; set; }
/// <summary>
/// The current RenderModel found for the request
/// </summary>

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

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

View File

@@ -300,9 +300,12 @@
<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" />
<Compile Include="ExamineExtensions.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)