/// <summary> /// If called from a "/submit" route, redirects to the *preceeding* route with the given message and the passed fields restored. /// /// If called from any other route type, this throws an exception. /// /// We fill values to be rendered into ViewData (after clearing it, for security purposes) to accomplish this. /// Anything that also appears in the target method signature will be set as a query parameter as well. /// /// So, if you call this from "/login/submit" with passBack = { username = "******" } it will render "/login" with ViewData["username"] = "******". /// message is stashed into ViewData["error_message"]. /// /// Note that this method does not work from route with parameters in the path. /// </summary> public ActionResult RecoverableError(string message, object passBack) { const string submit = "/submit"; var request = Current.RequestUri.AbsolutePath.ToLower(); if (!request.EndsWith(submit)) { throw new InvalidOperationException("Cannot recover from an error if a route isn't handling a POST"); } var previousRoute = request.Substring(0, request.Length - submit.Length); var trueRandom = Current.UniqueId().ToString(); var toStore = passBack.PropertiesAsStrings(); toStore["error_message"] = message; Current.AddToCache(trueRandom, toStore, TimeSpan.FromMinutes(5)); var queryString = "?recover=" + trueRandom; var route = RouteAttribute.GetDecoratedRoutes()[previousRoute.Substring(1)]; foreach (var param in route.GetParameters()) { if (toStore.ContainsKey(param.Name)) { queryString += "&" + param.Name + "=" + HttpUtility.UrlEncode(toStore[param.Name]); } } return(Redirect(previousRoute + queryString)); }
/// <summary> /// Returns true if this is a valid vanity id. /// /// Doesn't check if the id has already been issued, or similar /// </summary> /// <param name="id"></param> /// <returns></returns> public static bool IsValidVanityId(string id, out string errorMsg) { if (id.Length > 40) { errorMsg = "Vanity OpenId cannot be more than 40 characters long."; return(false); } if (!AllowedVanityIdRegex.IsMatch(id)) { errorMsg = "Vanity OpenId can only contain letters, numbers, dashes, and periods."; return(false); } var existingRoutes = RouteAttribute.GetDecoratedRoutes().Keys.Select(k => k.ToLower()); if (existingRoutes.Contains(id.ToLower())) { errorMsg = "This Vanity OpenId is reserved."; return(false); } // These two routes are manually mapped and *don't* contain illegal characters, // so we need to check for them seperately if (id.ToLower() == "ping" || id.ToLower() == "report") { errorMsg = "This Vanity OpenId is reserved."; return(false); } errorMsg = null; return(true); }
/// <summary> /// Redirect to a given method (which is decorated with a RouteAttribute), /// with the given parameters. /// /// Lets us centralize all parameter encoding to reduce the odds of mistakenly /// passing things unencoded. Also lets us catch re-named or unadorned routes /// a little easier. /// /// Makes it less tempting to resort to error prone string.Format() stuff everywhere /// too. /// /// Also, not that { Controller = "Blah", Action = "MoreBlah" } stuff that's just as nasty /// as string.Format IMO. /// /// Note does not work with routes with "in path" parameters, only query string passed /// parameters. /// /// As an aside, boy would it be handy if you could actually use MethodGroups for something, /// instead of just being a source of compiler errors. /// </summary> public RedirectResult SafeRedirect(Delegate target, object @params = null) { var toAction = target.Method; var routes = RouteAttribute.GetDecoratedRoutes(); if (!routes.Values.Contains(toAction)) { throw new ArgumentException("Method not decorated with RouteAttribute: " + toAction); } var registered = routes.Where(v => v.Value == toAction).Select(v => v.Key).Single(); return(UnsafeRedirect(registered, @params)); }