.netvalidationlocalizationvalidation-application-bl

Validation Application block and model localization


I can use ErrorMessageResourceName and ErrorMessageResourceType to translate rules into my language. But how do I translate class name and properties?

Currently I get something like Valideringsmeddelande för 'LastName' as validation message. I want LastName to be localized too.


Solution

  • As far as I know that ErrorMessage, ErrorMessageResourceName, and ErrorMessageResourceType properties are not used by Validation Application Block. They are included in Validation Application Block 5.0, because VAB's BaseValidationAttribute now inherits from System.ComponentModel.DataAnnotations.ValidationAttribute. These properties are defined in DataAnnotations' ValidationAttribute. By inheriting from DataAnnotations, the applications and frameworks can validate a model without having a dependency on VAB (such as the validation that ASP.NET MVC does for instance).

    What you can do is use VAB's MessageTemplateResourceName and MessageTemplateResourceType and don't use any markers and use a text specific to the property. For instance, put this complete text in your resource: "Valideringsmeddelande för efternamn" (sorry if the translation is crappy; I used Google translate).


    Update:

    I did some digging around in the library and unfortunately there is no easy way around this. Below is the code for the GetMessage method that is defined in the Validator base class that is located in Microsoft.Practices.EnterpriseLibrary.Validation.

    protected internal virtual string GetMessage(
        object objectToValidate, string key)
    {
        return string.Format(CultureInfo.CurrentCulture, 
            this.MessageTemplate, 
            new object[] { objectToValidate, key, this.Tag });
    }
    

    As you can see, the error message is generated using the MessageTemplate as format string together with the objectToValidate, key, and Tag as formatting arguments. The Tag is a value that you can define in a ValidationAttribute and is therefore static and can not be culture specific. When you are validating a property, the supplied key will always be the name of that property. This name will be supplied by the framework by reflecting over the validated type.

    You can make the name more user friendly and language specific, by defining a new Validator and overriding the GetMessage. This way you can fetch a culture dependent text based on the property name from a resource. The problem is however, that you will have to create a sub class for every validation attribute you wish to use and for each validation attribute you must inherit the supporting validator class. While this shouldn’t be that much code per attribute and per validator, it would still be very painful to maintain such a code base.

    I think it would be easier to simply define the complete message including user friendly, language specific name in the resource.


    Update 2:

    I thought of something that might possibly help. When your application only has to be able to display a single language at a time, there may be a work around. This won't work for web applications were the displayed messages are localized based on the thread's current culture, but it might work for desktop applications were you can configure language specific texts in the application's configuration file.

    When you define the validation rules in the application's configuration file (which is a model VAB supports), you can use the Tag attribute with a language specific string.

    <validation>
      <type name="Company.Application.Domain.Person" 
        defaultRuleset="Default"
        assemblyName="Company.Application.Domain">
        <ruleset name="Default">
          <properties>
            <property name="LastName">
              <validator type="StringLengthValidator" tag="efternamn" 
                upperBound="30" lowerBound="1" 
                lowerBoundType="Inclusive" upperBoundType="Inclusive"
                negated="false" messageTemplate=""
                messageTemplateResourceName=""
                messageTemplateResourceType="" 
                name="String Length Validator" />
            </property>
          </properties>
        </ruleset>
      </type>
    </validation>
    

    Take a close look at the tag tag="efternamn". When you put the {2} placeholder in the format string in your resource, it will get replaced by the string defined in tag. In other words, this resource string:

    Valideringsmeddelande för '{2}'
    

    will result in this validation message:

    Valideringsmeddelande för 'efternamn'
    

    Of course for this to work, your configuration must be localized. This wouldn’t be a good solution for web applications.

    But... you can also go one step further and build an IConfigurationSource implementation that returns a ValidationSettings instance that is based on the thread's current culture. In this scenario you will have to build the configuration in code. Here is an example of how to do this:

    public class MyConfigurationSource : IConfigurationSource
    {
        private Dictionary<CultureInfo, ValidationSettings> settings =
            new Dictionary<CultureInfo, ValidationSettings>();
    
        public ConfigurationSection GetSection(string sectionName)
        {
            if (sectionName == ValidationSettings.SectionName)
            {
                var culture = CultureInfo.CurrentCulture;
                lock (this.settings)
                {
                    if (!this.settings.ContainsKey(culture))
                    {
                        this.settings[culture] = 
                            this.BuildSettingsFor(culture);
                    }
                    return this.settings[culture];
                }
            }
            return null;
        }
    
        private ValidationSettings BuildSettingsFor(
            CultureInfo culture)
        {
            // TODO: Build up your configuration here. Example:
    
            new StringLengthValidatorData("LastName_Smaller100")
            {
                Tag = MyResources.GetValue(
                    "Customer.LastName", culture),
                LowerBound = 1,
                LowerBoundType = RangeBoundaryType.Inclusive,
                UpperBound = 100,
                UpperBoundType = RangeBoundaryType.Inclusive
            }
        }
    }
    

    You can supply an instance of that MyConfigurationSource to the ValidationFactory or, if you want better integration, hook up this type it in the VAB configuration.

    Note however that building validation rules in code is currently a lot of work, especially because VAB does not (yet) have a decent fluent configuration API (I complained about this here). I'm in the process of writing an API that makes it much easier to do so (keep an eye on my blog for that).

    Good luck.