public static void SeoRedirect(this Controller controller, QueryStringBehavior stripQueryStrings, [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) { var key = CityHash.CityHash.CityHash64(filePath, (ulong)lineNumber); CachedMethod cachedMethod; if (MethodCache.TryGetValue(key, out cachedMethod)) { string destination; if (DetermineSeoRedirect(controller, cachedMethod, stripQueryStrings, out destination)) { controller.Response.RedirectPermanent(destination); } return; } var callingMethod = new StackFrame(1).GetMethod(); SeoRedirect(controller, stripQueryStrings, null, callingMethod, key); }
internal CacheKeyQueryStringActionParameters(CacheKeyQueryStringActionParametersOdataType odataType, QueryStringBehavior queryStringBehavior, string queryParameters) { OdataType = odataType; QueryStringBehavior = queryStringBehavior; QueryParameters = queryParameters; }
public CacheKeyQueryStringActionParameters(CacheKeyQueryStringActionParametersOdataType odataType, QueryStringBehavior queryStringBehavior) { OdataType = odataType; QueryStringBehavior = queryStringBehavior; }
internal CacheKeyQueryStringActionParameters(CacheKeyQueryStringActionParametersTypeName typeName, QueryStringBehavior queryStringBehavior, string queryParameters) { TypeName = typeName; QueryStringBehavior = queryStringBehavior; QueryParameters = queryParameters; }
public CacheKeyQueryStringActionParameters(CacheKeyQueryStringActionParametersTypeName typeName, QueryStringBehavior queryStringBehavior) { TypeName = typeName; QueryStringBehavior = queryStringBehavior; }
/// <summary> /// Converts the <see cref="sourceValue" /> parameter to the <see cref="destinationType" /> parameter using <see cref="formatProvider" /// /> and <see cref="ignoreCase" /> /// </summary> /// <param name="sourceValue">the <see cref="System.Object"/> to convert from</param> /// <param name="destinationType">the <see cref="System.Type" /> to convert to</param> /// <param name="formatProvider">not used by this TypeConverter.</param> /// <param name="ignoreCase">when set to <c>true</c>, will ignore the case when converting.</param> /// <returns> /// an instance of <see cref="QueryStringBehavior" />, or <c>null</c> if there is no suitable conversion. /// </returns> public override object ConvertFrom(object sourceValue, global::System.Type destinationType, global::System.IFormatProvider formatProvider, bool ignoreCase) => QueryStringBehavior.CreateFrom(sourceValue);
private static bool DetermineSeoRedirect(Controller controller, CachedMethod method, QueryStringBehavior stripQueryStrings, out string destination) { bool redirect = false; string currentAction = (string)controller.RouteData.Values["action"]; string currentController = (string)controller.RouteData.Values["controller"]; //tentatively.... //note: not using a string builder because the assumption is that most requests are correct destination = HttpContext.Current.Request.Url.AbsolutePath; //Case-based redirect if (currentAction != method.Action) { redirect = true; destination = destination.Replace(currentAction, method.Action); } //Case-based redirect if (currentController != method.Controller) { redirect = true; destination = destination.Replace(currentController, method.Controller); } //Trailing-backslash configuration (this is the simplification of the NOT XNOR) if ((method.IsIndex != destination.EndsWith("/"))) { redirect = true; destination = string.Format("{0}{1}", destination.TrimEnd('/'), method.IsIndex ? "/" : ""); } //No Index in link if (method.IsIndex && destination.EndsWith("/Index/")) { redirect = true; const string search = "Index/"; destination = destination.Remove(destination.Length - search.Length); } //Query strings if (stripQueryStrings == QueryStringBehavior.StripAll || method.PreservedParameters == null) { redirect = redirect || HttpContext.Current.Request.QueryString.Count > 0; } else if (stripQueryStrings == QueryStringBehavior.KeepActionParameters) { if (HttpContext.Current.Request.QueryString.AllKeys.Any(k => Array.BinarySearch(method.PreservedParameters, k) < 0)) { redirect = true; var i = 0; StringBuilder qsBuilder = null; foreach (var key in HttpContext.Current.Request.QueryString.AllKeys.Where(k => Array.BinarySearch(method.PreservedParameters, k) >= 0)) { var value = HttpContext.Current.Request.QueryString[key]; qsBuilder = qsBuilder ?? new StringBuilder(); qsBuilder.AppendFormat("{0}{1}{2}{3}", i == 0 ? '?' : '&', key, string.IsNullOrEmpty(value) ? "" : "=", string.IsNullOrEmpty(value) ? "" : HttpUtility.UrlEncode(value)); ++i; } if (qsBuilder != null) { destination += qsBuilder.ToString(); } } else { //Keep query string parameters as-is destination += HttpContext.Current.Request.Url.Query; } } else //QueryStringBehavior.KeepAll { destination += HttpContext.Current.Request.Url.Query; } return(redirect); }
private static string MakeLegalQueryString(Controller controller, CachedMethod method, QueryStringBehavior stripQueryStrings) { if (method.PreservedParameters != null) { var sb = new StringBuilder(); int i = 0; foreach (var preserved in method.PreservedParameters.Intersect(controller.Request.QueryString.AllKeys)) { sb.AppendFormat($"{(i++ == 0 ? '?' : '&')}{HttpUtility.UrlEncode(preserved)}={HttpUtility.UrlEncode(controller.Request.Params.Get(preserved))}"); } return(sb.ToString()); } return(string.Empty); }
private static void SeoRedirect(Controller controller, QueryStringBehavior stripQueryStrings, string[] additionalPreservedKeys, MethodBase callingMethod, ulong key) { var cachedMethod = new CachedMethod { Action = callingMethod.Name, Controller = callingMethod.DeclaringType.Name.Remove(callingMethod.DeclaringType.Name.Length - "Controller".Length), IsIndex = callingMethod.Name == "Index" }; if (stripQueryStrings == QueryStringBehavior.KeepActionParameters) { //Optimization: if no explicitly preserved query strings and no action parameters, avoid creating HashSet and strip all if ((additionalPreservedKeys == null || additionalPreservedKeys.Length == 0) && callingMethod.GetParameters().Length == 0) { //This won't actually persist anywhere because it's only set once during the reflection phase and not included in the cache entry //we're relying on checking if HashSet == null in DetermineSeoRedirect #if DEBUG //I'd leave it in here and rely on the compiler to strip it out, but... stripQueryStrings = QueryStringBehavior.StripAll; #endif } else { int i = 0; bool skippedId = false; bool routeHasId = controller.RouteData.Values.ContainsKey("id"); bool requestHasId = HttpContext.Current.Request.QueryString.Keys.Cast <string>().Any(queryKey => queryKey == "id"); cachedMethod.PreservedParameters = new string[(callingMethod.GetParameters().Length + (additionalPreservedKeys?.Length ?? 0))]; foreach (var preserved in callingMethod.GetParameters()) { //Optimization: remove the 'id' parameter if it's determined by the route, potentially saving on HashSet lookup entirely if (!skippedId && routeHasId && !requestHasId && preserved.Name == "id") { //The parameter id is part of the route and not obtained via query string parameters if (cachedMethod.PreservedParameters.Length == 1) { //Bypass everything, no need for parameter preservation //This won't actually persist anywhere because it's only set once during the reflection phase and not included in the cache entry //we're relying on checking if HashSet == null in DetermineSeoRedirect #if DEBUG //I'd leave it in here and rely on the compiler to strip it out, but... stripQueryStrings = QueryStringBehavior.StripAll; #endif cachedMethod.PreservedParameters = null; break; } skippedId = true; var newPreserved = new string[cachedMethod.PreservedParameters.Length - 1]; Array.Copy(cachedMethod.PreservedParameters, 0, newPreserved, 0, i); cachedMethod.PreservedParameters = newPreserved; continue; } cachedMethod.PreservedParameters[i++] = preserved.Name; } if (additionalPreservedKeys != null) { foreach (var preserved in additionalPreservedKeys) { cachedMethod.PreservedParameters[i++] = preserved; } } if (cachedMethod.PreservedParameters != null && cachedMethod.PreservedParameters.Length > 1) { Array.Sort(cachedMethod.PreservedParameters); } } } MethodCache.TryAdd(key, cachedMethod); string destination; if (DetermineSeoRedirect(controller, cachedMethod, stripQueryStrings, out destination)) { controller.Response.Headers.Add("X-Redirect-Reason", "NeoSmart SEO Rule"); controller.Response.RedirectPermanent(destination); } }
internal CacheKeyQueryStringActionDefinition(CacheKeyQueryStringActionType typeDefinition, QueryStringBehavior queryStringBehavior, string queryParameters) { TypeDefinition = typeDefinition; QueryStringBehavior = queryStringBehavior; QueryParameters = queryParameters; }
public CacheKeyQueryStringActionDefinition(CacheKeyQueryStringActionType typeDefinition, QueryStringBehavior queryStringBehavior) { TypeDefinition = typeDefinition; QueryStringBehavior = queryStringBehavior; }
internal CacheKeyQueryStringActionProperties(CacheKeyQueryStringActionType actionType, QueryStringBehavior queryStringBehavior, string queryParameters) { ActionType = actionType; QueryStringBehavior = queryStringBehavior; QueryParameters = queryParameters; }
public CacheKeyQueryStringActionProperties(CacheKeyQueryStringActionType actionType, QueryStringBehavior queryStringBehavior) { ActionType = actionType; QueryStringBehavior = queryStringBehavior; }