From fee02af4eb9c7da5f20669c3eec78d0658823409 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 6 Sep 2013 11:40:37 +1000 Subject: [PATCH] Completes: U4-1639 Add support to generate GET urls to SurfaceController actions --- src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 52 ++----- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 69 ++++----- src/Umbraco.Web/Umbraco.Web.csproj | 1 + src/Umbraco.Web/UmbracoHelper.cs | 28 ++++ src/Umbraco.Web/UrlHelperRenderExtensions.cs | 133 ++++++++++++++++++ 5 files changed, 197 insertions(+), 86 deletions(-) create mode 100644 src/Umbraco.Web/UrlHelperRenderExtensions.cs diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 1a0bee2586..2a009dfaa2 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -188,21 +188,19 @@ namespace Umbraco.Web /// internal class UmbracoForm : MvcForm { - - /// /// Creates an UmbracoForm /// /// - /// - /// + /// + /// /// /// /// public UmbracoForm( ViewContext viewContext, - string surfaceController, - string surfaceAction, + string controllerName, + string controllerAction, string area, FormMethod method, object additionalRouteVals = null) @@ -210,21 +208,7 @@ namespace Umbraco.Web { _viewContext = viewContext; _method = method; - //need to create a params string as Base64 to put into our hidden field to use during the routes - var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", - viewContext.HttpContext.Server.UrlEncode(surfaceController), - viewContext.HttpContext.Server.UrlEncode(surfaceAction), - area); - - var additionalRouteValsAsQuery = additionalRouteVals != null ? additionalRouteVals.ToDictionary().ToQueryString() : null; - - if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false) - surfaceRouteParams += "&" + additionalRouteValsAsQuery; - - if (string.IsNullOrWhiteSpace(surfaceRouteParams) == false) - { - _encryptedString = surfaceRouteParams.EncryptWithMachineKey(); - } + _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); } private readonly ViewContext _viewContext; @@ -238,29 +222,8 @@ namespace Umbraco.Web return; this._disposed = true; - //Need to ensure any stale GET cookies are cleared before continuing - foreach (var c in _viewContext.HttpContext.Request.Cookies.AllKeys.Where(x => x.StartsWith("ufprt_"))) - { - _viewContext.HttpContext.Request.Cookies[c].Expires = DateTime.Now.AddDays(-1); - _viewContext.HttpContext.Response.SetCookie(_viewContext.HttpContext.Request.Cookies[c]); - } - - if (_method == FormMethod.Post) - { - //write out the hidden surface form routes - _viewContext.Writer.Write(""); - } - else - { - //since we are getting and we don't want this ugly value in the query string, we'll chuck it into a cookie with an id so we can retreive - //it on the server side with the same id and then remove it. - var id = Guid.NewGuid().ToString("N"); - var cookie = new HttpCookie("ufprt_" + id, _encryptedString); - _viewContext.HttpContext.Response.SetCookie(cookie); - - _viewContext.Writer.Write(""); - } - + //write out the hidden surface form routes + _viewContext.Writer.Write(""); base.Dispose(disposing); } @@ -544,6 +507,7 @@ namespace Umbraco.Web /// /// /// + /// /// public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index ef894d266f..1ebfebf1e4 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -116,8 +116,8 @@ namespace Umbraco.Web.Mvc private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) { //if it is a POST/GET then a value must be in the request - if ((requestContext.HttpContext.Request.RequestType == "POST" || requestContext.HttpContext.Request.RequestType == "GET") - && requestContext.HttpContext.Request["ufprt"].IsNullOrWhiteSpace()) + if (requestContext.HttpContext.Request.QueryString["ufprt"].IsNullOrWhiteSpace() + && requestContext.HttpContext.Request.Form["ufprt"].IsNullOrWhiteSpace()) { return null; } @@ -129,31 +129,28 @@ namespace Umbraco.Web.Mvc case "POST": //get the value from the request. //this field will contain an encrypted version of the surface route vals. - encodedVal = requestContext.HttpContext.Request["ufprt"]; + encodedVal = requestContext.HttpContext.Request.Form["ufprt"]; break; case "GET": - //get the value from the cookie based on the id sent as a query string. - var cookieId = "ufprt_" + requestContext.HttpContext.Request["ufprt"]; - if (requestContext.HttpContext.Request.Cookies[cookieId] == null || requestContext.HttpContext.Request.Cookies[cookieId].Value.IsNullOrWhiteSpace()) - { - LogHelper.Warn("Umbraco cannot process the GET form action, could not find the required cookie value for cookie name " + cookieId); - //we cannot continue if there is no cookie value - return null; - } - - //we need to ensure the cookie is gone - var outgoingCookie = requestContext.HttpContext.Request.Cookies[cookieId]; - outgoingCookie.Expires = DateTime.Now.AddDays(-1); - requestContext.HttpContext.Response.SetCookie(outgoingCookie); - //this field will contain an encrypted version of the surface route vals. - encodedVal = requestContext.HttpContext.Request.Cookies[cookieId].Value; + encodedVal = requestContext.HttpContext.Request.QueryString["ufprt"]; break; default: return null; } - var decryptedString = encodedVal.DecryptWithMachineKey(); + + string decryptedString; + try + { + decryptedString = encodedVal.DecryptWithMachineKey(); + } + catch (FormatException) + { + LogHelper.Warn("A value was detected in the ufprt parameter but Umbraco could not decrypt the string"); + return null; + } + var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); var decodedParts = new Dictionary(); @@ -165,31 +162,19 @@ namespace Umbraco.Web.Mvc //validate all required keys exist //the controller - if (!decodedParts.Any(x => x.Key == ReservedAdditionalKeys.Controller)) + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Controller)) return null; //the action - if (!decodedParts.Any(x => x.Key == ReservedAdditionalKeys.Action)) + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Action)) return null; //the area - if (!decodedParts.Any(x => x.Key == ReservedAdditionalKeys.Area)) + if (decodedParts.All(x => x.Key != ReservedAdditionalKeys.Area)) return null; - - ////the controller type, if it contains this then it is a plugin controller, not locally declared. - //if (decodedParts.Any(x => x.Key == "t")) - //{ - // return new PostedDataProxyInfo - // { - // ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "c").Value), - // ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "a").Value), - // Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "ar").Value), - // ControllerType = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == "t").Value) - // }; - //} - - foreach (var item in decodedParts.Where(x => !new string[] { - ReservedAdditionalKeys.Controller, - ReservedAdditionalKeys.Action, - ReservedAdditionalKeys.Area }.Contains(x.Key))) + + 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; @@ -198,9 +183,9 @@ namespace Umbraco.Web.Mvc //return the proxy info without the surface id... could be a local controller. return new PostedDataProxyInfo { - ControllerName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Controller).Value), - ActionName = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Action).Value), - Area = requestContext.HttpContext.Server.UrlDecode(decodedParts.Single(x => x.Key == ReservedAdditionalKeys.Area).Value), + 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), }; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ed3c6a1822..6ad8ce6049 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -405,6 +405,7 @@ + diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 9666506cb4..c0b59819c4 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -1305,6 +1305,34 @@ namespace Umbraco.Web #endregion + /// + /// This is used in methods like BeginUmbracoForm and SurfaceAction to generate an encrypted string which gets submitted in a request for which + /// Umbraco can decrypt during the routing process in order to delegate the request to a specific MVC Controller. + /// + /// + /// + /// + /// + /// + internal static string CreateEncryptedRouteString(string controllerName, string controllerAction, string area, object additionalRouteVals = null) + { + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + Mandate.ParameterNotNullOrEmpty(controllerAction, "controllerAction"); + Mandate.ParameterNotNull(area, "area"); + + //need to create a params string as Base64 to put into our hidden field to use during the routes + var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", + HttpUtility.UrlEncode(controllerName), + HttpUtility.UrlEncode(controllerAction), + area); + + var additionalRouteValsAsQuery = additionalRouteVals != null ? additionalRouteVals.ToDictionary().ToQueryString() : null; + + if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false) + surfaceRouteParams += "&" + additionalRouteValsAsQuery; + + return surfaceRouteParams.EncryptWithMachineKey(); + } } } diff --git a/src/Umbraco.Web/UrlHelperRenderExtensions.cs b/src/Umbraco.Web/UrlHelperRenderExtensions.cs new file mode 100644 index 0000000000..2eb1d8a060 --- /dev/null +++ b/src/Umbraco.Web/UrlHelperRenderExtensions.cs @@ -0,0 +1,133 @@ +using System; +using System.Linq; +using System.Web.Mvc; +using Umbraco.Core; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web +{ + /// + /// Extension methods for UrlHelper for use in templates + /// + public static class UrlHelperRenderExtensions + { + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, string controllerName) + { + return url.SurfaceAction(action, controllerName, null); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, string controllerName, object additionalRouteVals) + { + return url.SurfaceAction(action, controllerName, "", additionalRouteVals); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, string controllerName, string area, object additionalRouteVals) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + var encryptedRoute = UmbracoHelper.CreateEncryptedRouteString(controllerName, action, area, additionalRouteVals); + + var result = UmbracoContext.Current.OriginalRequestUrl.AbsolutePath.EnsureEndsWith('?') + "ufprt=" + encryptedRoute; + return result; + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, Type surfaceType) + { + return url.SurfaceAction(action, surfaceType, null); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, Type surfaceType, object additionalRouteVals) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + + var area = ""; + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (metaData.AreaName.IsNullOrWhiteSpace() == false) + { + //set the area to the plugin area + area = metaData.AreaName; + } + + var encryptedRoute = UmbracoHelper.CreateEncryptedRouteString(metaData.ControllerName, action, area, additionalRouteVals); + + var result = UmbracoContext.Current.OriginalRequestUrl.AbsolutePath.EnsureEndsWith('?') + "ufprt=" + encryptedRoute; + return result; + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action) + where T : SurfaceController + { + return url.SurfaceAction(action, typeof (T)); + } + + /// + /// Generates a URL based on the current Umbraco URL with a custom query string that will route to the specified SurfaceController + /// + /// + /// + /// + /// + /// + public static string SurfaceAction(this UrlHelper url, string action, object additionalRouteVals) + where T : SurfaceController + { + return url.SurfaceAction(action, typeof (T), additionalRouteVals); + } + + + } +} \ No newline at end of file