views:

563

answers:

3

Hello

In an ASP.NET MVC 2 application, i'm having a route like this:

            routes.MapRoute(
            "Default",                                       // Route name
            "{lang}/{controller}/{action}/{id}",             // URL with parameters
            new                                              // Parameter defaults 
            {                                             
                controller = "Home",
                action = "Index",
                lang = "de",
                id = UrlParameter.Optional
            },
            new 
            { 
                lang = new AllowedValuesRouteConstraint(new string[] { "de", "en", "fr", "it" },
                                                        StringComparison.InvariantCultureIgnoreCase) 
            }

Now, basically I would like to set the thread's culture according the language passed in. But there is one exception: If the user requests the page for the first time, like calling "http://www.mysite.com" I want to set the initial language if possible to the one "preferred by the browser".

How can I distinguish in an early procesing stage (like global.asax), if the default parameter has been set because of the default value or mentioned explicit through the URL? (I would prefer a solution where the request URL is not getting parsed).

Is there a way to dynamically provide a default-value for a paramter? Something like a hook? Or where can I override the default value (good application event?).

This is the code i'm actually experimenting with:

        protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        string activeLanguage;
        string[] validLanguages;
        string defaultLanguage;
        string browsersPreferredLanguage;

        try
        {
            HttpContextBase contextBase = new HttpContextWrapper(Context);
            RouteData activeRoute = RouteTable.Routes.GetRouteData(new HttpContextWrapper(Context));                

            if (activeRoute == null)
            {
                return;
            }

            activeLanguage = activeRoute.GetRequiredString("lang");
            Route route = (Route)activeRoute.Route;
            validLanguages = ((AllowedValuesRouteConstraint)route.Constraints["lang"]).AllowedValues;
            defaultLanguage = route.Defaults["lang"].ToString();
            browsersPreferredLanguage = GetBrowsersPreferredLanguage();

            //TODO: Better way than parsing the url
            bool defaultInitialized = contextBase.Request.Url.ToString().IndexOf(string.Format("/{0}/", defaultLanguage), StringComparison.InvariantCultureIgnoreCase) > -1;
            string languageToActivate = defaultLanguage;
            if (!defaultInitialized)
            {
                if (validLanguages.Contains(browsersPreferredLanguage, StringComparer.InvariantCultureIgnoreCase))
                {
                    languageToActivate = browsersPreferredLanguage;
                }
            }

            //TODO: Where and how to overwrtie the default value that it gets passed to the controller? 
            contextBase.RewritePath(contextBase.Request.Path.Replace("/de/", "/en/"));

            SetLanguage(languageToActivate);
        }
        catch (Exception ex)
        {
            //TODO: Log
            Console.WriteLine(ex.Message);
        }

    }
    protected string GetBrowsersPreferredLanguage()
    {
        string acceptedLang = string.Empty;

        if (HttpContext.Current.Request.UserLanguages != null && HttpContext.Current.Request.UserLanguages.Length > 0)
        {
            acceptedLang = HttpContext.Current.Request.UserLanguages[0].Substring(0, 2);
        }

        return acceptedLang;
    }

    protected void SetLanguage(string languageToActivate)
    {
        CultureInfo cultureInfo = new CultureInfo(languageToActivate);

        if (!Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName.Equals(languageToActivate, StringComparison.InvariantCultureIgnoreCase))
        {
            Thread.CurrentThread.CurrentUICulture = cultureInfo;
        }

        if (!Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName.Equals(languageToActivate, StringComparison.InvariantCultureIgnoreCase))
        {
            Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name);
        }
    }

The RouteConstraint to reproduce the sample:

    public class AllowedValuesRouteConstraint : IRouteConstraint
{

    private string[] _allowedValues;
    private StringComparison _stringComparism;

    public string[] AllowedValues
    {
        get { return _allowedValues;  }
    }

    public AllowedValuesRouteConstraint(string[] allowedValues, StringComparison stringComparism)
    {
        _allowedValues = allowedValues;
        _stringComparism = stringComparism;
    }

    public AllowedValuesRouteConstraint(string[] allowedValues)
    {
        _allowedValues = allowedValues;
        _stringComparism = StringComparison.InvariantCultureIgnoreCase;
    }

    public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
    {
        if (_allowedValues != null)
        {
            return _allowedValues.Any(a => a.Equals(values[parameterName].ToString(), _stringComparism));
        }
        else
        {
            return false;
        }
    }
}

Can someone help me out with that problem?

Thanks, Martin

A: 

To prevent being inconsistent, I prefer to do a redirect on the root to the browser language, and then let the user change the language if he/she prefers.

Simply put this lines in default.aspx and you are good to go.

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    Select Case Mid(Request.UserLanguages(0).ToString(), 1, 2).ToLower
        Case "en"
            Response.Redirect("/en/")
        Case "pt"
            Response.Redirect("/pt/")
        Case Else
            Response.Redirect("/es/")
    End Select

End Sub
Eduardo Molteni
What if someone accesses the page like lets say Posts.aspx and not the Default.aspx. that won't work. This should be on an httpmodule or the global.asax.
JeremySpouken
It wont work with your route either.
Eduardo Molteni
A: 

How about using an action filter?

it could be something like this:

public class LocalizationAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.RouteData.Values["lang"] != null)
        {
            var lang = filterContext.RouteData.Values["lang"].ToString();
            //TODO: Validate the obtained value.
            Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang);
        }
        else
        {
            var langHeader = filterContext.HttpContext.Request.UserLanguages[0];
            //TODO: Validate the obtained value.
            Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(langHeader);
        }
    }
}

This is just an idea, of course you need to improve it. Then you simply add this attribute to your controllers or you create a base controller and set the attribute in it (and make all your controllers a subclass of this base controller).

Regards.

uvita
Thanks for your suggestion. When I'm using a default value for lang in the default route, "lang" will never be null. If I enter "ru" for example, I get a 404 what is correct (see the constraint).But how can I determine, if "de" came as default value or passed in explicitly? I only want to take the browser language if no value passed in explicitly (if possible not by parsing the url).The second problem I then would have is to change the "lang" parameter, so that all links rendered get an updated URL.
Example:- I have a default value of "de" for the param "lang" - Now i enter "http://www.mysite.com" - All my links looks like this: http://www.mysite.com/de/aboutNow lets suppose, my browser prefers "en" and I call"http://www.mysite.com". - The default lang should now be overwritten to "en"- Links should be rendered like http://www.mysite.com/en/about
Yes, you´re right, sorry for that. Maybe you should go in the way of creating your own route handler instead. Good luck!
uvita
A: 

try this:

        RouteData data=RouteTable.Routes.GetRouteData(httpContext);
        if (data != null)
        {
            Route route = data.Route as Route;
            if (route != null && route.Defaults != null)
                route.Defaults["lang"] = lang.Name;
        }
Aik