views:

138

answers:

3

I am using asp.net mvc localized routes. So when a user goes to the english site it is site.com/en/Controller/Action and the swedish is site.com/sv/Controller/Action.

But how do I make sure that that when a user enters the site he / she comes to the correct language directly? I do know how to get the language I want, that is not the issue. The thing I used to do is that I put that culture into the RegisterRoutes method. But because my page is in integrated mode I cannot get the request from Application_Start.

So how should I make sure that the route is correct right from the beginning?

A: 

You can ask in the global.asax BeginRequest if the url is well formed for your site. You can try to do this with routes also, but in my experience, your routes will be very unstable if you are not sure that the first param is the lang.

Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs)
    Dim lang As String = "es"
    If not Request.Path.ToLower.StartsWith("sv/") and _
       not Request.Path.ToLower.StartsWith("en/")
        ''//ask the browser for the preferred lang
        Select Case Mid(Request.UserLanguages(0).ToString(), 1, 2).ToLower
          Case "en"
             Response.Redirect("en/")
          Case "sv"
             Response.Redirect("sv/")
          Case Else
             Response.Redirect("sv/") ''//the default
        End Select
    end if
 end sub

Untested code. Pardon my VB

Eduardo Molteni
Thanks, I will test this monday and get back to you regarding if it is right for me or not :)
Oskar Kjellin
This seems to be creating some infite loops.. /
Oskar Kjellin
It also seems to be rewriting paths to CSS-files etc
Oskar Kjellin
+3  A: 

This is how I would do it.

~~ Disclaimer : psuedo code ~~

global.asax

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}",
        new { favicon = @"(.*/)?favicon.ico(/.*)?" });

    routes.MapRoute(
        "Question-Answer", // Route name
        "{languageCode}/{controller}/{action}", // URL with parameters
        new {controller = "home", action = "index"} // Parameter defaults
        );

}

Take note: the controller and/or action do NOT need to be first and second. in fact, they do not need to exist at all, in the url with parameters section.

Then ...

HomeController.cs

public ActionResult Index(string languageCode)
{
   if (string.IsNullOrEmpty(languageCode) ||
      languageCode != a valid language code)
   {
       // No code was provided OR we didn't receive a valid code 
       // which you can't handle... so send them to a 404 page.
       // return ResourceNotFound View ...
   }

   // .. do whatever in here ..
}

Bonus suggestion

You can also add a Route Constraint to your route, so it only accepts certain strings for the languageCode parameter. So stealing this dude's code ....

(more pseduo code)...

public class FromValuesListConstraint : IRouteConstraint
{
    public FromValuesListConstraint(params string[] values)
    {
        this._values = values;
    }

    private string[] _values;

    public bool Match(HttpContextBase httpContext,
        Route route,
        string parameterName,
        RouteValueDictionary values,
        RouteDirection routeDirection)
    {
        // Get the value called "parameterName" from the 
        // RouteValueDictionary called "value"
        string value = values[parameterName].ToString();

        // Return true is the list of allowed values contains 
        // this value.
        return _values.Contains(value);
    }
}

means you could do this ......

routes.MapRoute(
    "Question-Answer", // Route name
    "{languageCode}/{controller}/{action}", // URL with parameters
    new {controller = "home", action = "index"} // Parameter defaults
    new { languageCode = new FromValuesListConstraint("en", "sv", .. etc) }
    );

and there you have it :)

I do something like this for versioning my MVC Api.

GL :) Hope this helps.

Pure.Krome
Thanks for the suggestions! The problem with the first one is that I cannot do this check in every action, I have many. The problem with the second one is that I want to redirect to the default value if the languageCode is not matched in the constraint
Oskar Kjellin
@Oskar Kjellin : np - i'll drop another answer in 10 mins .. /me hacks up some code. brb
Pure.Krome
+2  A: 

Ok .. another suggestion.

To make sure I understand, you want ..

  • Every Action needs to be able to figure out what the LanguageCode is?
  • If an invalid languageCode is provided, then it needs to be reset to a valid default one.

if so .. this answer has three parts:-

  1. Add the route. (this is cut-paste from my previous answer).

global.asax

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.IgnoreRoute("{*favicon}",
        new { favicon = @"(.*/)?favicon.ico(/.*)?" });

    routes.MapRoute(
        "Question-Answer", // Route name
        "{languageCode}/{controller}/{action}", // URL with parameters
        new {controller = "home", action = "index"} // Parameter defaults
        );

}

Update (based on comments)

So, if you want to have the route http://www.mywebsite.com/sv/account/logon then the above route will work.

LanguageCode == sv (or en or fr or whatever language you're supporting)

account == the controller: AccountController

login == action.

the fact that i've said controller = "home" and action="index" only mean that those two parameters are defaulted to those values IF none were provided. So, if you goto http://www.mywebsite.com/sv/account/logon then the MVC framework is smart enough to know (based on that route) that the languageCode paramters == sv, controller == action and action (method) == index.

NOTE: The order of your routes is IMPORTANT. critically important. This route needs to be one (if not the) of the first routes (after the IgonoreRoute's) when you register your routes.


  1. You need to create a custom ActionFilter which will get called BEFORE the action is executed. here's my quick attempt...

.

using System.Linq;
using System.Web.Mvc;

namespace YourNamespace.Web.Application.Models
{
    public class LanguageCodeActionFilter : ActionFilterAttribute
    {
        // This checks the current langauge code. if there's one missing, it defaults it.
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            const string routeDataKey = "languageCode";
            const string defaultLanguageCode = "sv";
            var validLanguageCodes = new[] {"en", "sv"};

            // Determine the language.
            if (filterContext.RouteData.Values[routeDataKey] == null ||
                !validLanguageCodes.Contains(filterContext.RouteData.Values[routeDataKey]))
            {
                // Add or overwrite the langauge code value.
                if (filterContext.RouteData.Values.ContainsKey(routeDataKey))
                {
                    filterContext.RouteData.Values[routeDataKey] = defaultLanguageCode;
                }
                else
                {
                    filterContext.RouteData.Values.Add(routeDataKey, defaultLanguageCode);    
                }
            }

            base.OnActionExecuting(filterContext);
        }
    }
}
  1. Now you'll need to make a BaseController, which all your controllers inherit from. This will then create an easily accessabily property which all your actions can access .. and then display whatever they want, based on that value.

here we go ... (pesduo code again....)

public abstract class BaseController : Controller
{
    protected string LanguageCode
    {
        get { return (string) ControllerContext.RouteData.Values["LanguageCode"]; }
    }   
}

So then we decorate our controllers like this :)

[LanguageCodeActionFilter]
public class ApiController : BaseController
{
    public ActionResult Index()
    {
        if (this.LanguageCode == "sv") ... // whatever.. etc..
    }
}

Notice how i've decorated the class .. not just each action. this means ALL actions in the class will be affected by the ActionFilter :)

Also, you might want to add a new route in the global.asax that handles NO languageCode .. and hardcode default that value ...

like (also untested) ...

routes.MapRoute(
    "Question-Answer", // Route name
    "{controller}/{action}", // URL with parameters
    new {controller = "home", action = "index", languageCode = "sv"} // Parameter defaults
);

Does this help?

Pure.Krome
Thanks for your efforts! Seems like we are on to something. The one thing that is bothering me is that you are not using the same route as I am. Sorry for not stating that previously. I want a route like "page.com/sv/account/logon".
Oskar Kjellin
@Oskar Kjellin : added extra info to this answer. please re-read and post any comments. :)
Pure.Krome
The problem I am facing is that I want page.com/home/index to redirect to page.com/sv/home/index if that is the country.. :)
Oskar Kjellin
will _all_ /page.com/home/index redirect to _sv_ ?
Pure.Krome
It depends on what language my code wants the users to go to. I have a functon like "GetLanguageCode" that checks some stuff and returns the code
Oskar Kjellin
Ok. So you have some code `GetLanguageCode` that determines the language code, for the current request. It could be via domain name, Accept-Languages, querystring, whatever. Then, when a user goes to the resource `/home/index` (notice no language code in there), your custom code needs to be called AND THEN a 301 redirect to the verbose resource?
Pure.Krome
Exactly! :) I want to be able to send links like page.com/news to everyone and they will see it in their language with the same link
Oskar Kjellin