AttributeRouting icon indicating copy to clipboard operation
AttributeRouting copied to clipboard

Culture and Home Route

Open mdmoura opened this issue 11 years ago • 6 comments

Hello,

I have the following AR configuration:

RouteTable.Routes.MapAttributeRoutes(x => {
    x.AddRoutesFromAssemblyOf<HomeController>();
    x.AddDefaultRouteConstraint(@"^id$", new RegexRouteConstraint(@"^\d+$"));
    x.AddTranslationProvider<RouteTranslationProvider>();
    x.ConstrainTranslatedRoutesByCurrentUICulture = true;
    x.UseRouteHandler(() => new CultureRouteHandler());
    x.CurrentUICultureResolver = (context, data) => { return (String)data.Values["culture"] ?? Thread.CurrentThread.CurrentUICulture.Name; };
});

Where the CultureRouteHandler is the following (quite simple for testing):

protected override IHttpHandler GetHttpHandler(RequestContext context) {

    String culture = (String)context.RouteData.Values[_culture];

    if (culture == null) {
        culture = "en";
    }

    if (culture != null) {
        Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
        Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
        context.RouteData.Values[_culture] = culture;
    }

    return base.GetHttpHandler(context);

}

I have the following action to modify culture:

[RoutePrefix("{culture}")]
public partial class CultureController : Controller {

    [GET("{culture}/Culture/Modify"), AllowAnonymous]
    public virtual ActionResult Modify(String culture) {
        return RedirectToAction(MVC.Contact.Index());
    } // Modify
} // CultureController

I called this action with PT value and I am redirected to Contact/Index with PT culture as expected.

But when I try to access Home/Index, and only this action, the culture goes back to EN.

In Home/Index I have the following:

public partial class HomeController : Controller {
    [GET(""), AllowAnonymous, GoogleAnalytics]
    public virtual ActionResult Index() {
      return View();
    } // Index
} // HomeController

If I add [RoutePrefix("{culture}")] and GET("culture") to Home/Index action then I get a 404 error.

What am I missing here?

Thank You, Miguel

mdmoura avatar Nov 15 '12 18:11 mdmoura

If you changed your home controller as you mention you'll have a number of issues:

[RoutePrefix("{culture}")]
public partial class HomeController : Controller 
{
    [GET("{culture}"), AllowAnonymous, GoogleAnalytics]
    public virtual ActionResult Index() 
    {
      return View();
    }
}

First, your generated URL will be "{culture}" (AR will not doubly apply the culture URL param). Second, requesting ~/ will not match, since your culture parameter is not optional. If you want culture to optional, do this:

[RoutePrefix("{culture?}")]

Your CultureRouteHandler, when responding to requests for ~/, will find no value in route data for culture (since there's no value for a culture URL param). So by your logic, the culture will be set to en.

mccalltd avatar Nov 16 '12 00:11 mccalltd

Hello,

Let me explain the configuration I am looking for: A) Have localized routes as 'en/signup' and 'pt/inscrever"; B) A route such as 'en/inscrever" would be invalid. C) An action to change from one culture to another and reload the current page.

I did a few changes and followed the Wiki and I still have a few problems. So I have:

[RoutePrefix("{culture?}")]
public partial class HomeController : Controller 
{
  [GET("{culture?}"), AllowAnonymous]
  public virtual ActionResult Index() 
  {
    return View();
  }
}

[RoutePrefix("{culture}")]
public partial class UserController : Controller 
{
  [GET("SignUp"), AllowAnonymous]
  public virtual ActionResult SignUp() 
  {
    return View();
  }
}

[RoutePrefix("{culture}")] public partial class CultureController : Controller { [GET("Culture/Modify"), AllowAnonymous] public virtual ActionResult Modify(String culture) { return Redirect(Request.UrlReferrer.AbsoluteUri); } // Modify }

Then I use the following configuration:

RouteTable.Routes.MapAttributeRoutes(x => {
  x.AddRoutesFromAssemblyOf<HomeController>();
  x.AddTranslationProvider<RouteTranslationProvider>();
  x.ConstrainTranslatedRoutesByCurrentUICulture = true;
  x.UseLowercaseRoutes = true;
  x.UseRouteHandler(() => new CultureRouteHandler());
  x.CurrentUICultureResolver = (context, data) => { 
    return (String)data.Values["culture"] ?? Thread.CurrentThread.CurrentUICulture.Name; 
  };
});

Where CultureRouteHandler is as follows:

public class CultureRouteHandler : MvcRouteHandler { private const String _culture = "culture"; protected override IHttpHandler GetHttpHandler(RequestContext context) { String culture = (String)context.RouteData.Values[_culture]; if (culture == null) { culture = "en"; } Thread.CurrentThread.CurrentCulture = new CultureInfo(culture); Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture); context.RouteData.Values[_culture] = culture; return base.GetHttpHandler(context); } }

Which results in the following error when I run the site: Unable to cast object of type 'System.Web.Mvc.UrlParameter' to type 'System.String'.

In the following configuration code line:

x.CurrentUICultureResolver = (context, data) => { 
   return (String)data.Values["culture"] ?? Thread.CurrentThread.CurrentUICulture.Name; 
};

To make this work I needed to do a few changes. In configuration:

x.CurrentUICultureResolver = (context, data) => { 
  return data.Values["culture"] == null ? Thread.CurrentThread.CurrentUICulture.Name : data.Values["culture"].ToString(); 
};

And in the CurrentHandler:

String culture = context.RouteData.Values[_culture].ToString();
if (String.IsNullOrWhiteSpace(culture)) {
  culture = "en";
}      

Now points (A) and (B) seems to be solved. But there is a problem with C.

When I try to change the culture by calling Culture/Modify it does not change. It works only if use RedirectToAction instead of Redirect. Something like:

return RedirectToAction(MVC.Home.Index());

I then though the problem might be having {culture} in Culture / Modify action.

But if I remove it I then get the error: Object reference not set to an instance of an object.

In my Culture route handler:

 String culture = context.RouteData.Values[_culture].ToString();

Probably I am missing something on how culture is being defined in the various situations.

When I fix one I get a problem in the other part.

Do you know what am I missing?

Thank You, Miguel

mdmoura avatar Nov 16 '12 14:11 mdmoura

I am having a few more problems. Consider the CultureHandler code I mentioned before:

  String culture = context.RouteData.Values[_culture].ToString();
  if (String.IsNullOrWhiteSpace(culture)) {
    culture = "en";
  }      
  Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
  Thread.CurrentThread.CurrentUICulture = new CultureInfo(culture);
  context.RouteData.Values[_culture] = culture;
  return base.GetHttpHandler(context);

1 - When I try to access routes.axd é get, in this code, the error "Culture is not supported". I checked and culture values is "routes.axd" ...

2 - I have an error controller as follows:

 [RoutePrefix("{culture?}")]
 public partial class ErrorController : Controller 
 {
   [GET("{culture?}/Error/404"), AllowAnonymous]
   public virtual ActionResult NotFound() {
   }
 }

 I am able to access it using en/error/404 and pt/error/404 but not using error/404.
 Why is that if culture is allowed to be null ...

3 - I occasionally get an error where culture in my Culture Handler is invalid. I checked the culture values in those cases and it is "favicon.ico". This is strange since I have a specific Ignore route for that.

Here is my routing configuration:

  RouteTable.Routes.MapAttributeRoutes(x => {
    x.AddRoutesFromAssemblyOf<HomeController>();
    x.AddDefaultRouteConstraint(@"^id$", new RegexRouteConstraint(@"^\d+$"));
    x.AddTranslationProvider<RouteTranslationProvider>();
    x.AddTranslationProvider<BlomRouteTranslationProvider>();
    x.ConstrainTranslatedRoutesByCurrentUICulture = true;
    x.UseLowercaseRoutes = true;
    x.UseRouteHandler(() => new CultureRouteHandler());
    x.CurrentUICultureResolver = (context, data) => { return data.Values["culture"] == null ? Thread.CurrentThread.CurrentUICulture.Name : data.Values["culture"].ToString(); };
    // x.CurrentUICultureResolver = (context, data) => { return (String)data.Values["culture"] ?? Thread.CurrentThread.CurrentUICulture.Name; };
  });

  GlobalConfiguration.Configuration.Routes.MapHttpAttributeRoutes(x => {
    x.AddRoutesFromAssemblyOf<HomeController>();
  });

  AreaRegistration.RegisterAllAreas();

  RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
  RouteTable.Routes.IgnoreRoute("{*favicon}", new { favicon = @"(.*/)?favicon.([iI][cC][oO]|[gG][iI][fF])(/.*)?" });
  RouteTable.Routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = UrlParameter.Optional }, typeof(HomeController).Assembly);

Thank You, Miguel

mdmoura avatar Nov 17 '12 13:11 mdmoura

Sorry Miguel. Will reply to this. Would be totally awesome if you could create a new web app and repro the problem for me, then put that app on github so i can fork it.

mccalltd avatar Dec 07 '12 04:12 mccalltd

I will create a sample application during next week, ok?

I have been trying a few approaches with localization and AR. And I made a list of the problems and examples of possible solutions ...

I will upload the the new app and post those problems.

mdmoura avatar Dec 07 '12 11:12 mdmoura

Hello,

I just placed the default MVC web site using AR for localization: https://github.com/shapper/MVCLAR

I started with a small initial commit to try to clarify two problems:

1 - When the site starts the default culture in the controller is "en-US". Not the expected "en" set in CultureRouteHandler ... And when it reaches the view it is PT. No idea why.

2 - I am using a Culture/Modify action to change the culture. But this works only when redirecting to another action.

 Isn't possible to have a link for the current page with the desired culture in "culture"?

Thank you, Miguel

mdmoura avatar Dec 14 '12 00:12 mdmoura