0

I have a localization for 3 languages on my web app, scenario is this: When user opens up the page, URL example.com but I want to make it example.com/en.

I am trying to achieve it by using RedirectToRouteResult but on the first try when I open the page, I get error. I have taken the mechanism from this article

The main reason why I am trying to achieve this is the user-level defined culture info, so that when user from one country sets his/her language, his/her won't have to re-do it every time they enter the page, since I am storing the data in cache and database; And OutputCache So that when someone enters the page they won't face other users rendered page which might not the default language for the new user.

public class InternationalizationAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    // This is a dummy constraint, if the culture is zz, it means that URL does not contain correct culture
    bool redirectToLanguagedVersion = filterContext.RouteData.Values["culture"].ToString() == "zz"; 
    string language = "en", culture = "GB";

    if (redirectToLanguagedVersion)
    {
        if (filterContext.HttpContext.User.Identity.IsAuthenticated)
        {
            var user = IdentityManager.GetUser(filterContext.HttpContext.User.Identity.GetUserId());
            language = user.CultureInfoLanguage ?? language;
            culture = user.CultureInfoCulture ?? culture;
        }
        else
        {
            language = filterContext.RequestContext.HttpContext.Cache["UserCultureInfoLanguage"]?.ToString() ?? language;
            culture = filterContext.RequestContext.HttpContext.Cache["UserCultureInfoCulture"]?.ToString() ?? culture;
        }

        RouteValueDictionary values = filterContext.RouteData.Values;
        values.Remove("culture");
        values.Add("culture", culture);
        filterContext.Result = new RedirectToRouteResult("DefaultWithCulture", values); // Issue hits here
    }
    else
    {
        // ... Define the culture user should use and update cache/db if needed

        Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(string.Format("{0}-{1}", language, culture));
    }
}}

And this is RouteConfig

public class RouteConfig
{
    public static void RegisterRoutes(RouteCollection routes)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        // Call to register your localized and default attribute routes
        routes.MapLocalizedMvcAttributeRoutes(
            urlPrefix: "{culture}/",
            constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") }
        );

        routes.MapRoute(
            name: "DefaultWithCulture",
            url: "{culture}/{controller}/{action}/{id}",
            defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional },
            constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") }
        );

        routes.MapRoute(
            name: "Default",
            url: "{controller}/{action}/{id}",
            defaults: new { culture = "zz", controller = "Home", action = "Index", id = UrlParameter.Optional }
        );

        routes.MapRoute(
            name: "TourWithCulture",
            url: "{culture}/{controller}/{action}/{id}/{title}",
            defaults: new { controller = "Tours", action = "Explore", id = UrlParameter.Optional, title = UrlParameter.Optional },
            constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") }
        );

        routes.MapRoute(
            name: "Tour",
            url: "{controller}/{action}/{id}/{title}",
            defaults: new { controller = "Tours", action = "Explore", id = UrlParameter.Optional, title = UrlParameter.Optional },
            constraints: new { culture = new CultureConstraint(defaultCulture: "zz", pattern: "[a-z]{2}") }
        );
    }
}
  • What error You get? – garret Jan 18 '18 at 10:09
  • No Route in route table matches the supplied values – DavidShukvani Jan 18 '18 at 10:15
  • 1
    How about you show us your actual code so we can see the problem and try to fix it? At the very least, please create a [mcve] – DavidG Jan 18 '18 at 10:20
  • I have added the code which you requested. – DavidShukvani Jan 18 '18 at 10:28
  • You clearly didn't read [this answer](https://stackoverflow.com/a/32839796/). You don't need to set the culture per user because it is already part of the request if it is in the URL redirecting is also totally unnecessary (and expensive). See the Language Selection part. – NightOwl888 Jan 18 '18 at 10:28
  • I read and understood that part and it works just fine and btw thank you so much for such a good article!. What I am asking about is this scenario: I have a outputcache and also I want to have a possibility to save culture info per user, so when someone enters the page system will save the culture that user has chosen and, so it'll be re-used in future. – DavidShukvani Jan 18 '18 at 10:31
  • I do understand that what I am asking is a good thing to have and not a necessity at all, but still at least for curiosity, I want to know why it still throws this error. – DavidShukvani Jan 18 '18 at 10:34
  • It is difficult to understand what you are doing. The culture constraint and culture of the routes you have configured don't match the culture you are supplying to `CurrentCulture` (which are supported by routing, BTW). Your routing is also misconfigured in the wrong order (see [this](https://stackoverflow.com/a/35674633)). Your [output cache should be setup per user](https://stackoverflow.com/q/17080416) to fix that issue. "GB" is not a valid culture to pass as a route value. `HttpContext.Cache` is application wide so it will be the same for all users. Many issues. – NightOwl888 Jan 18 '18 at 11:33
  • Also read the comments on my original answer. The chances a user will need to reset the culture are rare because they will always bookmark or come to the site off of the search engine in their own culture. Storing it in the database might make sense in some scenarios (a portal where the user always types in the URL of the login screen, for example). – NightOwl888 Jan 18 '18 at 11:38
  • I suggest you fix the above mentioned issues, then break this down into individual goals and ask individual questions. The output caching issue should be a separate StackOverflow question from the "culture based on user login" that you are attempting. – NightOwl888 Jan 18 '18 at 11:41

1 Answers1

0

The reason why you are getting the exception is due to the fact you are not supplying matching values that are needed to build the URL. To build a URL you need to supply all of the required values (and the optional ones if needed). But additional values will cause it not to match.

RouteValueDictionary values = filterContext.RouteData.Values;
values.Remove("culture");
values.Add("culture", culture);
filterContext.Result = new RedirectToRouteResult("DefaultWithCulture", values);

Controller and action are always required. So, for this route you need to pass:

  1. culture
  2. controller
  3. action
  4. id (optional)

This means that if your route value collection has more than this (such as title), it will not match DefaultWithCulture. Note that DefaultWithCulture is an extra set of criteria for the route, but you still must pass all of the required route values in order to match it.

That said, you should never pass a route name to RedirectToRouteResult when using routing based on this answer because there are in fact 2 routes that represent the possible matches - DefaultWithCulture and Default in this case. You have to remove the extra name criteria for the match, otherwise it will not be possible to match Default.

RouteValueDictionary values = filterContext.RouteData.Values;
values.Remove("culture");
values.Add("culture", culture);
filterContext.Result = new RedirectToRouteResult(values);
NightOwl888
  • 55,572
  • 24
  • 139
  • 212
  • At the moment when the code runs, filterContext.RouteData.Values contains all the values (culture, controller and action). It seems that my main issue was using 'gb' instead of en. – DavidShukvani Jan 18 '18 at 12:28