1

this seems like any other question about culture or routing, but I can't figure out why this is happening.

When I try to access an [Authorize] element without being authenticated, I'm redirected to login page with a ReturnUrl param in query string. That's perfect. (boilerplate app)

Now my problem is: if I do the same with localisation within url (from routeConfig) ie: url: "{lang}/{controller}/{action}", I'm being redirected a first time with this parameter, but after been passed by the base controller that redirect me to a localized route. After that, ReturnUrl is null while login page (.cshtml) is loaded...

I've loooked into lots of articles, and tried numerous tricks/options and maybe on the way I did something that's breaks everything, so here are some code:

First RouteConfig :

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

    routes.MapRoute(
       name: "LocalizedDefault",
       url: "{lang}/{controller}/{action}",
       defaults: new { lang = UrlParameter.Optional, controller = "Home", action = "Index" } ,
       constraints: new { lang = "fr-CA|en-US" }
   );

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

}

In BaseController:

protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
{
    string cultureName = RouteData.Values["lang"] as string;
    // Attempt to read the culture cookie from Request
    if (cultureName == null)
    {
        cultureName = Request.UserLanguages != null && 
        Request.UserLanguages.Length > 0 ? 
                            Request.UserLanguages[0] :
                            null; // obtain it from HTTP header AcceptLanguages
    }

    // Validate culture name
    cultureName = CultureHelper.GetImplementedCulture(StartupMVC.SupportedLangs, cultureName);

    if (RouteData.Values["lang"] as string != cultureName)
    {
        // Force a valid culture in the URL
        RouteData.Values["lang"] = cultureName.ToLowerInvariant(); // lower case too 

        // Redirect user
        //Response.SuppressFormsAuthenticationRedirect = false;
****HERE**** 
if I comment this line, I keep returnUrl but my route 
is like {controller}/{action}/{id} => So default language   
    Response.RedirectToRoute(RouteData.Values);  
otherwise route ok {lang}/{controller}/{action}/{id} but no ReturnUrl 
    }                                                                                
    SetCurrentCultureOnThread(cultureName);
    return base.BeginExecuteCore(callback, state);
}

and a bit of Login.cshtml:

@using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
        ... 
}

I hope this is silly and there is some obvious solution you could point out to me. Otherwise, does anyone understand this? and maybe help me to fix it?

PS : if I not being clear enough or you need some more info, please comment what you need! :)

Edit : from NightOwl888's comment, I'd like to add some more questions:

_I kwow there is at least 3 major ways of handling localization (locale in query string, in route, translating the url), but is there any documented or commonly used Best Practices regarding localization and culture?

_How does the .NET framework handles the redirect to login and how does it handles the query string? (since we can not register route with query string values)

Minus
  • 729
  • 8
  • 20
  • 1
    Too many problems with this approach to list here. See [ASP.NET MVC 5 culture in route and url](https://stackoverflow.com/a/32839796) and be sure to read the comments about redirecting the user automatically to their culture. – NightOwl888 Feb 17 '18 at 07:08
  • thanks for this comment. I saw this answer, and I thought it was somewhat complicated for something that nowadays must widely be used... Since you seem to know a lot on that subject, and I'm curious to know more, I'll add on my question some additional questions, as it might be valuable for other people. – Minus Feb 19 '18 at 14:50
  • 1
    There is nothing built-in to MVC to make it skip over the localized route and go to the non-localized one (without resorting to 302 redirects). In fact, there are no built-in extensions for URL localization at all - the only choices are to customize or go 3rd party. However, I can't stress enough how bad it is to let the request go all the way to a controller and then redirect to another controller. Redirecting has **one** purpose - to change the URL in the browser. Routing's purpose is to make the *first* request go directly to the right controller, eliminating the need for a second request. – NightOwl888 Feb 20 '18 at 08:02
  • BTW - the above link is my highest voted answer on SO. It didn't get to be that way because it is too complicated for people to implement. It is literally 2 classes to copy, paste and configure plus a little bit of modification to a view to allow the user to switch cultures. The attribute routing code is only required if you need to localize attribute routes. – NightOwl888 Feb 20 '18 at 08:24

2 Answers2

3

Added QueryStrings to end of Routes:

if (cultureOnUrl.ToLower() != culture.ToLower())
        {
            try
            {
                var routes = new RouteValueDictionary(filterContext.RouteData.Values);
                foreach (string key in filterContext.HttpContext.Request.QueryString.Keys)
                {
                    routes[key] = filterContext.HttpContext.Request.QueryString[key];
                }
                if (!routes.ContainsKey("lang"))
                    routes.Add("lang", culture);
                else
                    routes["lang"] = culture;  
           filterContext.HttpContext.Response.RedirectToRoute("LocalizedDefault",routes);     
            }
            catch (Exception ex)
            {

            }
            return;
        }
Maral
  • 31
  • 4
1

Specifically tell MVC which route to use. So instead of this:

Response.RedirectToRoute(RouteData.Values);

do this:

Response.RedirectToRoute("LocalizedDefault", RouteData.Values);
CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
  • @Minus Be more specific please. What does not work? Do you get an error? – CodingYoshi Feb 19 '18 at 14:21
  • I ment using the name of the route (in my case 'LocalizedDefault' ) does not correct the problem as the returnUrl still got to null after beeing redirected to that route. – Minus Feb 19 '18 at 14:42
  • @Minus What values do you have in `RouteData.Values`? Please check in the debugger before you make the redirect. – CodingYoshi Feb 19 '18 at 14:43
  • In RouteData.Values before the call to rediretToRoute are : Account, Login, and en-US. – Minus Feb 19 '18 at 14:55
  • 1
    @Minus that is the issue. You need to do this before you call the redirect: `RouteData.Values.Add("returnUrl", "whatever...");` – CodingYoshi Feb 19 '18 at 15:09
  • RouteData Values are different that query string values. If I understand correctly how routes works, what you're suggesting implies adding a "Verb" in my route. So instead of "{lang}/{controller}/{action}/{id}", I would have a url like "{lang}/{controller}/{action}/{id}/{ReturnUrl}"? This might conflicts with other routes as the applications grows? – Minus Feb 19 '18 at 15:33
  • @Minus No you do not need to add a verb in your route. Whatever is not in your route table will be treated as query string. Please try it. – CodingYoshi Feb 19 '18 at 15:35
  • That works! nice... Could you maybe explain a bit more how this works (see Edit in question and comment from @NightOwl888)? – Minus Feb 19 '18 at 15:58