Example #1
0
        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);
Example #7
0
        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);
        }
Example #8
0
        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);
        }
Example #9
0
        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);
            }
        }
Example #10
0
 internal CacheKeyQueryStringActionDefinition(CacheKeyQueryStringActionType typeDefinition, QueryStringBehavior queryStringBehavior, string queryParameters)
 {
     TypeDefinition      = typeDefinition;
     QueryStringBehavior = queryStringBehavior;
     QueryParameters     = queryParameters;
 }
Example #11
0
 public CacheKeyQueryStringActionDefinition(CacheKeyQueryStringActionType typeDefinition, QueryStringBehavior queryStringBehavior)
 {
     TypeDefinition      = typeDefinition;
     QueryStringBehavior = queryStringBehavior;
 }
Example #12
0
 internal CacheKeyQueryStringActionProperties(CacheKeyQueryStringActionType actionType, QueryStringBehavior queryStringBehavior, string queryParameters)
 {
     ActionType          = actionType;
     QueryStringBehavior = queryStringBehavior;
     QueryParameters     = queryParameters;
 }
Example #13
0
 public CacheKeyQueryStringActionProperties(CacheKeyQueryStringActionType actionType, QueryStringBehavior queryStringBehavior)
 {
     ActionType          = actionType;
     QueryStringBehavior = queryStringBehavior;
 }