Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.InvalidOperationException : Unable to generate redirect url - after applied custom culture constraint in register route #1

Open
davidngaks opened this issue Jun 22, 2016 · 2 comments

Comments

@davidngaks
Copy link

davidngaks commented Jun 22, 2016

I am applying custom route constraints suggested by NightOwl888
http://stackoverflow.com/questions/32764989/asp-net-mvc-5-culture-in-route-and-url

public bool Match( HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { if (routeDirection == RouteDirection.UrlGeneration && this.defaultCulture.Equals(values[parameterName])) { return false; } else { return Regex.IsMatch((string)values[parameterName], "^" + pattern + "$"); } }

when it match the condition in

this.defaultCulture.Equals(values[parameterName])

it will throw Unable to generate redirect url exception due to the first match return false and it suppose to retrieve from next MapRoute

@dipunm
Copy link
Owner

dipunm commented Jan 6, 2019

This is very late, I know, however I want to address this issue anyway:

  • I read through the stack overflow issue and managed to reproduce the issue you are facing.
  • The reason this is happening, is that the Route that is used to direct traffic to the controller is not a valid route for generating links.

I believe that your particular scenario expects that a user visiting http://website.com/nl/Home/Index should be redirected (or have the canonicalUrl set) to http://website.com/Home/Index. The way that this would typically be is done, is to set the preferred Route using the Cannonical attribute (see https://github.com/dipunm/SEO.DuplicateContent/blob/master/ReturnNull.CanonicalRoutes/Mvc/CanonicalAttribute.cs) on the action so that when the library tries to build a url, it uses one that is known to work.

In your case, because the preferred route changes conditionally (only if the culture is 'nl'), we would need to create a more tailored solution. We could create a ISeoRequestRule (see: https://github.com/dipunm/SEO.DuplicateContent/blob/b8f339988b72e0a4926f483c28cef74c0540d67f/rules.md) that effectively checks if the route is DefaultWithCulture and if it is, but the route returns null when calling GetVirtualPath then change the route to Default. This is the safest, most non-obtrusive way I can think of to solve the problem. It avoids making the same check (culture === "nl") twice and couples the two related routes without affecting any other routes that you have set up.

See: https://github.com/dipunm/SEO.DuplicateContent/blob/b8f339988b72e0a4926f483c28cef74c0540d67f/ReturnNull.CanonicalRoutes/Rules/EnforceCorrectRoute.cs for an example of a constraint that changes the preferred route.

@dipunm
Copy link
Owner

dipunm commented Jan 6, 2019

Here is an example rule that I created:

using ReturnNull.CanonicalRoutes.Models;
using ReturnNull.CanonicalRoutes.Rules.Abstract;
using System.Web.Routing;
using System;

namespace Demo.Web.App_Start
{
    internal class EnableFallbackRoute : ISeoRequestRule
    {
        private string matchedRoute;
        private string fallbackRoute;
        private bool forceRedirect;

        public EnableFallbackRoute(string matchedRoute, string fallbackRoute, bool forceRedirect = true)
        {
            this.matchedRoute = matchedRoute;
            this.forceRedirect = forceRedirect;
            this.fallbackRoute = fallbackRoute;
        }

        public bool HasBeenViolated(RequestData requestData, UserProvisions provisions)
        {
            /*
             * This rule must be placed in the RedirectRules to ensure that
             * generating a url to redirect to can happen properly.
             * 
             * If this rule is expected to be a re-write only rule, we can prevent
             * the HasBeenViolated method from flagging any issues.
             * 
             * - If another redirect rule has been violated, the CorrectPlan
             *   method will ensure that the Route used to generate the redirect
             *   url is compatible.
             * - If no other redirect rules have been violated, then the rule
             *   will not cause any redirects. This allows the rewrite rules to
             *   be applied instead.
             * - If we are not forcing a redirect, we should add another instance of 
             *   this rule in the rewrite rules collection to ensure that the Route 
             *   used to generate the cannonical url is compatible. 
             */
            if (!forceRedirect)
                return false;
            
            return this.RequestUsesIncompatibleRoute(requestData, provisions);
        }

        private bool RequestUsesIncompatibleRoute(RequestData requestData, UserProvisions provisions)
        {
            var expectedRoute = RouteTable.Routes[this.matchedRoute];
            return expectedRoute == requestData.Route && requestData.Route.GetVirtualPath(
                requestData.HttpContext.Request.RequestContext,
                requestData.RouteValues) == null;
        }

        public void CorrectPlan(UrlPlan plan, RequestData requestData, UserProvisions provisions)
        {
            if (this.RequestUsesIncompatibleRoute(requestData, provisions))
            {
                plan.Route = RouteTable.Routes[this.fallbackRoute];
            }
        }
    }
}

You can add this rule to the ruleset collection like this:

 public static void SetupDuplicateContentRules(SeoRequestRulesetCollection rules)
        {
            var defaultRuleset = SeoRequestRuleset.Recommended();
            defaultRuleset.RedirectRules.Add(new EnableFallbackRoute("DefaultWithCulture", "Default", forceRedirect: false));
            defaultRuleset.RewriteRules.Add(new EnableFallbackRoute("DefaultWithCulture", "Default"));
            rules.Add("Default", defaultRuleset);
        }

If you DO want the library to redirect, then you can omit the RewriteRule part, but forceRedirect must be true.

 public static void SetupDuplicateContentRules(SeoRequestRulesetCollection rules)
        {
            var defaultRuleset = SeoRequestRuleset.Recommended();
            defaultRuleset.RedirectRules.Add(new EnableFallbackRoute("DefaultWithCulture", "Default", forceRedirect: true));
            rules.Add("Default", defaultRuleset);
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants