3
votes

Whilst this may concern to other topics as well, I'm just heading the following trouble with validation:

Model Annotation, Controller and Action

I use validation attributes from System.ComponentModel.DataAnnotations. The model looks like

public class NewUserModel
{
    [Required]
    public string Username { get; set; }
}

So, nothing special. According to that, a pretty much default controller action

public ActionResult New()
{
    return View(new NewUserModel());
}

and the view

@using (Html.BeginForm())
{
    @Html.LabelFor(m => m.Username)
    @Html.EditorFor(m => m.Username)
    @Html.ValidationMessageFor(m => m.Username)
    <button type="submit">save</button>
}

Setting Culture With Filter

Based on user preferences, culture is set via following filter

public class CultureFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // simplified for this example
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de");
    }
}

which is registered in global.asax with

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
    filters.Add(new CultureFilter());
}

Default language is english, so the web.config contains

<globalization culture="en-US" uiCulture="en"/>

With that done, client-side validation works perfectly. Data attributes for unobtrusive jQuery validation are localized as you can see in the resulting HTML:

<input data-val="true"
       data-val-required="Das Feld Username ist erforderlich."
       id="Username" name="Username" type="text" value="" />

The Problem: Server-side Validation Without Localization

The trouble is, that forcing server-side validation by disabling JavaScript renders following unlocalized validation message, while the data attribute is still localized:

<input data-val="true"
       data-val-required="Das Feld Username ist erforderlich."
       id="Username" name="Username" type="text" value="" />
<span class="field-validation-error" data-valmsg-for="Username"
      data-valmsg-replace="true">The Username field is required.</span>

Looks pretty funny, doesn't it? ;-)

What Did I Try?

First, I checked if the server-side culture is set by web.config. Yes, it is. If I change the <globalization/> attributes to german cultures (or just remove the node as by system language is german), the server-side validation message is german too.

This let me believe there might be a difference in times when server-side and client-side take their messages from resources. And maybe the server-side does it prior to executing the action, which means CultureFilter.OnActionExecuting() is called after that, which of course is too late in this case.

So I tried to set the culture on begin request (global.asax):

protected void Application_BeginRequest()
{
    Thread.CurrentThread.CurrentUICulture = new CultureInfo("de");
}

Well, that did the trick. But I really think this is an ugly solution. So...

The Question: Am I Right and Do I Have Any Other Options?

Is it true, that server-side validation takes the messages from resource before a filter could set the culture? If not, what am I doing wrong? If yes, is there another, cleaner option for setting the culture than the global.asax "hack"?

Thank you in advance.

3
How did you manage to get german validation messages anyway? When I put <globalization culture="de-DE" uiCulture="de" /> in the web.config of a default MVC application the validation messages are still english.mwijnands
@MarcelW81: Nothing special. I guess I didn't have to do anything because english is installed per default and with my system and VS in german, these resources are also part of the setup. If I change the culture to fr for example, messages come in english, not french. So you may need to install some language packs.Linus Caldwell

3 Answers

1
votes

The problem is that the DefaultModelBinder creates the model before yourFilter is executed, and its during the creation of the model that validation errors are added (and at this point the culture is en-US as defined in your web.config file.

One option may be to create a custom ModelBinder (and register it as the DefaultModelBinder) and override the OnPropertyValidating() method to set the culture.

0
votes

I believe you are right when you set the culture based on User Settings, in the Application_BeginRequest method. Its not even a hack. You would want your culture to be set to the correct one as early as possible in the request life cycle.

Though one thing should be taken care of is that this Application_BeginRequest would be called more than 1 times for a single url request for, other files such as images and stuff. So the task of loading user details, should be done just once. Or some programming effort so that user preferences are cached during multiple requests and cache should be reloaded only when user changes his/her preference

ameet

0
votes

Thanks to Stephen Muecke's answer, who pushed me in the right direction, I realized there are more filter interfaces which I could implement. A great overview of the MVC lifecycle can be found in this article. It points out that IAuthenticationFilter and IAuthorizationFilter implementations are invoked before the model binding happens.

I decided to use these interfaces (IAuthorizationFilter in my case because IAuthenticationFilter is not available in MVC 3) for my CultureFilter:

// would use IAuthenticationFilter in MVC 5
public class CultureFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        // simplified for this example
        Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de");
    }
}

This might not be best practice, but in my opinion it's cleaner than using Application_BeginRequest in global.asax and I think, especially with IAuthenticationFilter, setting culture there is the better place than on model binding.