diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index b80523a4a9..bfa2063f7a 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -16,90 +16,90 @@ using System.Collections.Generic; namespace Umbraco.Web.Mvc { - public class RenderRouteHandler : IRouteHandler - { - // Define reserved dictionary keys for controller, action and area specified in route additional values data - private static class ReservedAdditionalKeys - { - internal const string Controller = "c"; - internal const string Action = "a"; - internal const string Area = "ar"; - } + public class RenderRouteHandler : IRouteHandler + { + // Define reserved dictionary keys for controller, action and area specified in route additional values data + private static class ReservedAdditionalKeys + { + internal const string Controller = "c"; + internal const string Action = "a"; + internal const string Area = "ar"; + } - public RenderRouteHandler(IControllerFactory controllerFactory) - { - if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); - _controllerFactory = controllerFactory; - } + public RenderRouteHandler(IControllerFactory controllerFactory) + { + if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); + _controllerFactory = controllerFactory; + } - /// - /// Contructor generally used for unit testing - /// - /// - /// - internal RenderRouteHandler(IControllerFactory controllerFactory, UmbracoContext umbracoContext) - { - if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); - if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); - _controllerFactory = controllerFactory; - _umbracoContext = umbracoContext; - } + /// + /// Contructor generally used for unit testing + /// + /// + /// + internal RenderRouteHandler(IControllerFactory controllerFactory, UmbracoContext umbracoContext) + { + if (controllerFactory == null) throw new ArgumentNullException("controllerFactory"); + if (umbracoContext == null) throw new ArgumentNullException("umbracoContext"); + _controllerFactory = controllerFactory; + _umbracoContext = umbracoContext; + } - private readonly IControllerFactory _controllerFactory; - private readonly UmbracoContext _umbracoContext; - - /// - /// Returns the current UmbracoContext - /// - public UmbracoContext UmbracoContext - { - get { return _umbracoContext ?? UmbracoContext.Current; } - } + private readonly IControllerFactory _controllerFactory; + private readonly UmbracoContext _umbracoContext; - #region IRouteHandler Members + /// + /// Returns the current UmbracoContext + /// + public UmbracoContext UmbracoContext + { + get { return _umbracoContext ?? UmbracoContext.Current; } + } - /// - /// Assigns the correct controller based on the Umbraco request and returns a standard MvcHandler to prcess the response, - /// this also stores the render model into the data tokens for the current RouteData. - /// - /// - /// - public IHttpHandler GetHttpHandler(RequestContext requestContext) - { - if (UmbracoContext == null) - { - throw new NullReferenceException("There is not current UmbracoContext, it must be initialized before the RenderRouteHandler executes"); - } - var docRequest = UmbracoContext.PublishedContentRequest; - if (docRequest == null) - { - throw new NullReferenceException("There is not current PublishedContentRequest, it must be initialized before the RenderRouteHandler executes"); - } - - SetupRouteDataForRequest( - new RenderModel(docRequest.PublishedContent, docRequest.Culture), - requestContext, - docRequest); + #region IRouteHandler Members - return GetHandlerForRoute(requestContext, docRequest); - - } + /// + /// Assigns the correct controller based on the Umbraco request and returns a standard MvcHandler to prcess the response, + /// this also stores the render model into the data tokens for the current RouteData. + /// + /// + /// + public IHttpHandler GetHttpHandler(RequestContext requestContext) + { + if (UmbracoContext == null) + { + throw new NullReferenceException("There is not current UmbracoContext, it must be initialized before the RenderRouteHandler executes"); + } + var docRequest = UmbracoContext.PublishedContentRequest; + if (docRequest == null) + { + throw new NullReferenceException("There is not current PublishedContentRequest, it must be initialized before the RenderRouteHandler executes"); + } - #endregion + SetupRouteDataForRequest( + new RenderModel(docRequest.PublishedContent, docRequest.Culture), + requestContext, + docRequest); - /// - /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views - /// - /// - /// - /// - internal void SetupRouteDataForRequest(RenderModel renderModel, RequestContext requestContext, PublishedContentRequest docRequest) - { - //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine - requestContext.RouteData.DataTokens.Add("umbraco", renderModel); //required for the RenderModelBinder and view engine - requestContext.RouteData.DataTokens.Add("umbraco-doc-request", docRequest); //required for RenderMvcController - requestContext.RouteData.DataTokens.Add("umbraco-context", UmbracoContext); //required for UmbracoTemplatePage - } + return GetHandlerForRoute(requestContext, docRequest); + + } + + #endregion + + /// + /// Ensures that all of the correct DataTokens are added to the route values which are all required for rendering front-end umbraco views + /// + /// + /// + /// + internal void SetupRouteDataForRequest(RenderModel renderModel, RequestContext requestContext, PublishedContentRequest docRequest) + { + //put essential data into the data tokens, the 'umbraco' key is required to be there for the view engine + requestContext.RouteData.DataTokens.Add("umbraco", renderModel); //required for the RenderModelBinder and view engine + requestContext.RouteData.DataTokens.Add("umbraco-doc-request", docRequest); //required for RenderMvcController + requestContext.RouteData.DataTokens.Add("umbraco-context", UmbracoContext); //required for UmbracoTemplatePage + } private void UpdateRouteDataForRequest(RenderModel renderModel, RequestContext requestContext) { @@ -110,41 +110,41 @@ namespace Umbraco.Web.Mvc // the rest should not change -- it's only the published content that has changed } - /// - /// Checks the request and query strings to see if it matches the definition of having a Surface controller - /// posted/get value, if so, then we return a PostedDataProxyInfo object with the correct information. - /// - /// - /// - private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); + /// + /// Checks the request and query strings to see if it matches the definition of having a Surface controller + /// posted/get value, if so, then we return a PostedDataProxyInfo object with the correct information. + /// + /// + /// + private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); - //if it is a POST/GET then a value must be in the request - if (requestContext.HttpContext.Request.QueryString["ufprt"].IsNullOrWhiteSpace() + //if it is a POST/GET then a value must be in the request + if (requestContext.HttpContext.Request.QueryString["ufprt"].IsNullOrWhiteSpace() && requestContext.HttpContext.Request.Form["ufprt"].IsNullOrWhiteSpace()) - { - return null; - } + { + return null; + } - string encodedVal; - - switch (requestContext.HttpContext.Request.RequestType) - { + string encodedVal; + + switch (requestContext.HttpContext.Request.RequestType) + { case "POST": //get the value from the request. //this field will contain an encrypted version of the surface route vals. encodedVal = requestContext.HttpContext.Request.Form["ufprt"]; - break; + break; case "GET": //this field will contain an encrypted version of the surface route vals. encodedVal = requestContext.HttpContext.Request.QueryString["ufprt"]; - break; + break; default: - return null; - } + return null; + } + - string decryptedString; try { @@ -157,99 +157,99 @@ namespace Umbraco.Web.Mvc } var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); - var decodedParts = new Dictionary(); + var decodedParts = new Dictionary(); - foreach (var key in parsedQueryString.AllKeys) - { - decodedParts[key] = parsedQueryString[key]; - } + foreach (var key in parsedQueryString.AllKeys) + { + decodedParts[key] = parsedQueryString[key]; + } - //validate all required keys exist + //validate all required keys exist - //the controller - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) - return null; - //the action - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) - return null; - //the area - if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) - return null; - - foreach (var item in decodedParts.Where(x => new[] { + //the controller + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) + return null; + //the action + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) + return null; + //the area + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) + return null; + + foreach (var item in decodedParts.Where(x => new[] { ReservedAdditionalKeys.Controller, ReservedAdditionalKeys.Action, ReservedAdditionalKeys.Area }.Contains(x.Key) == false)) - { - // Populate route with additional values which aren't reserved values so they eventually to action parameters - requestContext.RouteData.Values[item.Key] = item.Value; - } + { + // Populate route with additional values which aren't reserved values so they eventually to action parameters + requestContext.RouteData.Values[item.Key] = item.Value; + } - //return the proxy info without the surface id... could be a local controller. - return new PostedDataProxyInfo - { - ControllerName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Controller).Value), + //return the proxy info without the surface id... could be a local controller. + return new PostedDataProxyInfo + { + ControllerName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Controller).Value), ActionName = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Action).Value), Area = HttpUtility.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Area).Value), - }; - } + }; + } - /// - /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that - /// the right DataTokens are set. - /// - /// - /// - private IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (postedInfo == null) throw new ArgumentNullException("postedInfo"); + /// + /// Handles a posted form to an Umbraco Url and ensures the correct controller is routed to and that + /// the right DataTokens are set. + /// + /// + /// + private IHttpHandler HandlePostedValues(RequestContext requestContext, PostedDataProxyInfo postedInfo) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (postedInfo == null) throw new ArgumentNullException("postedInfo"); - //set the standard route values/tokens - requestContext.RouteData.Values["controller"] = postedInfo.ControllerName; - requestContext.RouteData.Values["action"] = postedInfo.ActionName; + //set the standard route values/tokens + requestContext.RouteData.Values["controller"] = postedInfo.ControllerName; + requestContext.RouteData.Values["action"] = postedInfo.ActionName; IHttpHandler handler; - - //get the route from the defined routes - using (RouteTable.Routes.GetReadLock()) - { - Route surfaceRoute; - if (postedInfo.Area.IsNullOrWhiteSpace()) - { - //find the controller in the route table without an area - var surfaceRoutes = RouteTable.Routes.OfType() - .Where(x => x.Defaults != null && - x.Defaults.ContainsKey("controller") && - String.Equals(x.Defaults["controller"].ToString(), postedInfo.ControllerName, StringComparison.InvariantCultureIgnoreCase) && - !x.DataTokens.ContainsKey("area")).ToList(); - // If more than one route is found, find one with a matching action - if (surfaceRoutes.Count() > 1) - { - surfaceRoute = surfaceRoutes.SingleOrDefault(x => - String.Equals(x.Defaults["action"].ToString(), postedInfo.ActionName, StringComparison.InvariantCultureIgnoreCase)); - } - else - { - surfaceRoute = surfaceRoutes.SingleOrDefault(); - } - - } - else - { + //get the route from the defined routes + using (RouteTable.Routes.GetReadLock()) + { + Route surfaceRoute; + if (postedInfo.Area.IsNullOrWhiteSpace()) + { + //find the controller in the route table without an area + var surfaceRoutes = RouteTable.Routes.OfType() + .Where(x => x.Defaults != null && + x.Defaults.ContainsKey("controller") && + x.Defaults["controller"].ToString().InvariantEquals(postedInfo.ControllerName) && + x.DataTokens.ContainsKey("area") == false).ToList(); + + // If more than one route is found, find one with a matching action + if (surfaceRoutes.Count() > 1) + { + surfaceRoute = surfaceRoutes.SingleOrDefault(x => + x.Defaults["action"].ToString().InvariantEquals(postedInfo.ActionName)); + } + else + { + surfaceRoute = surfaceRoutes.SingleOrDefault(); + } + + } + 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["area"].ToString().InvariantEquals(postedInfo.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["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); - + //set the area if one is there. if (surfaceRoute.DataTokens.ContainsKey("area")) { @@ -263,33 +263,33 @@ namespace Umbraco.Web.Mvc } handler = surfaceRoute.RouteHandler.GetHttpHandler(requestContext); - } + } - return handler; - } + return handler; + } - /// - /// Returns a RouteDefinition object based on the current renderModel - /// - /// - /// - /// - internal virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, PublishedContentRequest publishedContentRequest) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); + /// + /// Returns a RouteDefinition object based on the current renderModel + /// + /// + /// + /// + internal virtual RouteDefinition GetUmbracoRouteDefinition(RequestContext requestContext, PublishedContentRequest publishedContentRequest) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); - var defaultControllerType = DefaultRenderMvcControllerResolver.Current.GetDefaultControllerType(); + var defaultControllerType = DefaultRenderMvcControllerResolver.Current.GetDefaultControllerType(); var defaultControllerName = ControllerExtensions.GetControllerName(defaultControllerType); - //creates the default route definition which maps to the 'UmbracoController' controller - var def = new RouteDefinition - { - ControllerName = defaultControllerName, + //creates the default route definition which maps to the 'UmbracoController' controller + var def = new RouteDefinition + { + ControllerName = defaultControllerName, ControllerType = defaultControllerType, - PublishedContentRequest = publishedContentRequest, - ActionName = ((Route)requestContext.RouteData.Route).Defaults["action"].ToString(), - HasHijackedRoute = false - }; + 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 @@ -302,60 +302,60 @@ namespace Umbraco.Web.Mvc def.ActionName = templateName; } - //check if there's a custom controller assigned, base on the document type alias. + //check if there's a custom controller assigned, base on the document type alias. var controllerType = _controllerFactory.GetControllerTypeInternal(requestContext, publishedContentRequest.PublishedContent.DocumentTypeAlias); - //check if that controller exists - if (controllerType != null) - { + //check if that controller exists + if (controllerType != null) + { //ensure the controller is of type 'IRenderMvcController' and ControllerBase if (TypeHelper.IsTypeAssignableFrom(controllerType) && TypeHelper.IsTypeAssignableFrom(controllerType)) - { - //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 implement '{2}' and inherit from '{3}'.", - () => publishedContentRequest.PublishedContent.DocumentTypeAlias, - () => controllerType.FullName, + { + //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 implement '{2}' and inherit from '{3}'.", + () => publishedContentRequest.PublishedContent.DocumentTypeAlias, + () => controllerType.FullName, () => typeof(IRenderMvcController).FullName, () => typeof(ControllerBase).FullName); - + //we cannot route to this custom controller since it is not of the correct type so we'll continue with the defaults // that have already been set above. - } - } + } + } //store the route definition requestContext.RouteData.DataTokens["umbraco-route-def"] = def; - return def; - } + return def; + } - internal IHttpHandler GetHandlerOnMissingTemplate(PublishedContentRequest pcr) - { - if (pcr == null) throw new ArgumentNullException("pcr"); + internal IHttpHandler GetHandlerOnMissingTemplate(PublishedContentRequest pcr) + { + if (pcr == null) throw new ArgumentNullException("pcr"); - // missing template, so we're in a 404 here + // missing template, so we're in a 404 here // so the content, if any, is a custom 404 page of some sort - if (!pcr.HasPublishedContent) - // means the builder could not find a proper document to handle 404 - return new PublishedContentNotFoundHandler(); + if (!pcr.HasPublishedContent) + // means the builder could not find a proper document to handle 404 + return new PublishedContentNotFoundHandler(); - if (!pcr.HasTemplate) - // means the engine could find a proper document, but the document has no template - // at that point there isn't much we can do and there is no point returning - // to Mvc since Mvc can't do much - return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); + if (!pcr.HasTemplate) + // means the engine could find a proper document, but the document has no template + // at that point there isn't much we can do and there is no point returning + // to Mvc since Mvc can't do much + return new PublishedContentNotFoundHandler("In addition, no template exists to render the custom 404."); // so we have a template, so we should have a rendering engine if (pcr.RenderingEngine == RenderingEngine.WebForms) // back to webforms ? @@ -363,81 +363,81 @@ namespace Umbraco.Web.Mvc else if (pcr.RenderingEngine != RenderingEngine.Mvc) // else ? return new PublishedContentNotFoundHandler("In addition, no rendering engine exists to render the custom 404."); - return null; - } + return null; + } - /// - /// this will determine the controller and set the values in the route data - /// - /// - /// - internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, PublishedContentRequest publishedContentRequest) - { - if (requestContext == null) throw new ArgumentNullException("requestContext"); - if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); + /// + /// this will determine the controller and set the values in the route data + /// + /// + /// + internal IHttpHandler GetHandlerForRoute(RequestContext requestContext, PublishedContentRequest publishedContentRequest) + { + if (requestContext == null) throw new ArgumentNullException("requestContext"); + if (publishedContentRequest == null) throw new ArgumentNullException("publishedContentRequest"); - var routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); - - //Need to check for a special case if there is form data being posted back to an Umbraco URL - var postedInfo = GetFormInfo(requestContext); - if (postedInfo != null) - { - return HandlePostedValues(requestContext, postedInfo); - } + var routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); - //here we need to check if there is no hijacked route and no template assigned, if this is the case - //we want to return a blank page, but we'll leave that up to the NoTemplateHandler. - if (!publishedContentRequest.HasTemplate && !routeDef.HasHijackedRoute) - { - publishedContentRequest.UpdateOnMissingTemplate(); // will go 404 + //Need to check for a special case if there is form data being posted back to an Umbraco URL + var postedInfo = GetFormInfo(requestContext); + if (postedInfo != null) + { + return HandlePostedValues(requestContext, postedInfo); + } + + //here we need to check if there is no hijacked route and no template assigned, if this is the case + //we want to return a blank page, but we'll leave that up to the NoTemplateHandler. + if (!publishedContentRequest.HasTemplate && !routeDef.HasHijackedRoute) + { + publishedContentRequest.UpdateOnMissingTemplate(); // will go 404 // HandleHttpResponseStatus returns a value indicating that the request should // not be processed any further, eg because it has been redirect. then, exit. - if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, publishedContentRequest)) - return null; + if (UmbracoModule.HandleHttpResponseStatus(requestContext.HttpContext, publishedContentRequest)) + return null; var handler = GetHandlerOnMissingTemplate(publishedContentRequest); - // if it's not null it can be either the PublishedContentNotFoundHandler (no document was - // found to handle 404, or document with no template was found) or the WebForms handler - // (a document was found and its template is WebForms) + // if it's not null it can be either the PublishedContentNotFoundHandler (no document was + // found to handle 404, or document with no template was found) or the WebForms handler + // (a document was found and its template is WebForms) - // if it's null it means that a document was found and its template is Mvc + // if it's null it means that a document was found and its template is Mvc - // if we have a handler, return now - if (handler != null) - return handler; + // if we have a handler, return now + if (handler != null) + return handler; - // else we are running Mvc + // else we are running Mvc // update the route data - because the PublishedContent has changed UpdateRouteDataForRequest( new RenderModel(publishedContentRequest.PublishedContent, publishedContentRequest.Culture), requestContext); // update the route definition - routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); - } + routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); + } - //no post values, just route to the controller/action requried (local) + //no post values, just route to the controller/action requried (local) - requestContext.RouteData.Values["controller"] = routeDef.ControllerName; - if (!string.IsNullOrWhiteSpace(routeDef.ActionName)) - { - requestContext.RouteData.Values["action"] = routeDef.ActionName; - } + requestContext.RouteData.Values["controller"] = routeDef.ControllerName; + if (!string.IsNullOrWhiteSpace(routeDef.ActionName)) + { + 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); + // 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 UmbracoMvcHandler(requestContext); - } + } private SessionStateBehavior GetSessionStateBehavior(RequestContext requestContext, string controllerName) { return _controllerFactory.GetControllerSessionBehavior(requestContext, controllerName); } - } + } } \ No newline at end of file