Model Metadata and Validation Localization using Conventions
By default, ASP.NET MVC leverages Data Annotations to provide validation. The approach is easy to get started with and allows the validation applied on the server to “float” to the client without any extra work.
However, once you get localization involved, using Data Annotations can really clutter your models. For example, the following is a simple model class with two properties.
public class Character { public string FirstName { get; set; } public string LastName { get; set; } }
Nothing to write home about, but it is nice, clean, and simple. To make it more useful, I’ll add validation and format how the properties are displayed.
public class Character { [Display(Name="First Name")] [Required] [StringLength(50)]] public string FirstName { get; set; } [Display(Name="Last Name")] [Required] [StringLength(50)]] public string LastName { get; set; } }
That’s busier, but not horrible. It sure is awful Anglo-centric though. I’ll fix that by making sure the property labels and error messages are pulled from a resource file.
public class Character { [Display(Name="Character_FirstName", ResourceType=typeof(ClassLib1.Resources))] [Required(ErrorMessageResourceType=typeof(ClassLib1.Resources), ErrorMessageResourceName="Character_FirstName_Required")] [StringLength(50, ErrorMessageResourceType = typeof(ClassLib1.Resources), ErrorMessageResourceName = "Character_FirstName_StringLength")] public string FirstName { get; set; } [Display(Name="Character_LastName", ResourceType=typeof(ClassLib1.Resources))] [Required(ErrorMessageResourceType=typeof(ClassLib1.Resources), ErrorMessageResourceName="Character_LastName_Required")] [StringLength(50, ErrorMessageResourceType = typeof(ClassLib1.Resources), ErrorMessageResourceName = "Character_LastName_StringLength")] public string LastName { get; set; } }
Wow! I don’t know about you, but I feel a little bit dirty typing all that in. Allow me a moment as I go wash up.So what can I do to get rid of all that noise? Conventions to the rescue! By employing a simple set of conventions, I should be able to look up error messages in resource files as well as property labels without having to specify all that information. In fact, by convention I shouldn’t even need to use theDisplayAttribute.What Conventions Does It Apply?
The nice thing about this convention based model metadata provider is it allows you to specify as little or as much of the metadata you need and it fills in the rest.Providing minimal metadata
For example, the following is a class with one simple property.
public class Character { [Required] [StringLength(50)] public string FirstName {get; set;} }
When displayed as a label, the custom metadata provider looks up the resource key, {ClassName}_{PropertyName}, and uses the resource value as the label. For example, for theFirstNameproperty, the provider uses the keyCharacter_FirstNameto look up the label in the resource file. I’ll cover how resource type is specified later.If a value for that resource is not found, the code falls back to using the property name as the label, but splits it using Pascal/Camel casing as a guide. Therefore in this case, the label is “First Name”.The error message for a validation attribute uses a resource key of{ClassName}_{PropertyName}_{AttributeName}. For example, to locate the error message for aRequiredAttribute, the provider finds the resource keyCharacter_FirstName_Required.Partial Metadata
There may be cases where you can provide some metadata, but not all of it. Ideally, the metadata that you don’t supply is inferred based on the conventions. Going back to previous example again:public class Character { [Required(ErrorMessageResourceType=typeof(MyResources))] [StringLength(50, ErrorMessageResourceName="StringLength_Error")] [Display(Name="First Name")] public string FirstName {get; set;} }
Notice that the first attribute only specifies the error message resource type. In this case, the specified resource type will override the default resource type. But the resource key is still inferred by convention (akaCharacter_FirstName_Required).In contrast, notice that the secondStringLengthAttribute, only specifies the resource name, and doesn’t specify a resource type. In this case, the specified resource name is used to look up the error message using the default resource type. As you might expect, if theErrorMessageproperty is specified, that takes precedence over the conventions.TheDisplayAttributeworks slightly differently. By default, theNameproperty is used as a resource key if a resource type is also specified. If no resource type is specified, theNameproperty is used directly. In the case of this convention based provider, an attempt to lookup a resource value using theNameproperty as a resource always occurs before falling back to the default behavior.Configuration
One detail I haven’t covered yet is what resource type is used to find these messages? Is that determined by convention?Deteriming this by convention would be tricky so it’s the one bit of information that must be explicitly specified when configuring the provider itself. The following code in Global.asax.cs shows how to configure this.ModelMetadataProviders.Current = new ConventionalModelMetadataProvider( requireConventionAttribute: false, defaultResourceType: typeof(MyResources.Resource) );
The model metadata provider’s constructor has two arguments used to configure it.Some developers will want the conventions to apply to every model, while others will want to be explicit and have models opt in to this behavior. The first argument,requireConventionAttribute, determines whether the conventions only apply to classes with theMetadataConventionsAttributeapplied.The explicit folks will want to set this value to true so that only classes with theMetadataConventionsAttributeapplied to them (or classes in an assembly where the attribute is applied to the assembly) will use these conventions.The attribute can also be used to specify the resource type for resource strings.The second property specifies the default resource type to use for resource strings. Note that this can be overridden by any attribute that specifies its own resource type.Caveats, Issues, Potholes
This code is something I hacked together and there are a few issues to consider that I could not easily work around. First of all, the implementation has to mutate properties of attributes. In general, this is not a good thing to do because attributes tend to be global. If other code relies on the attributes having their original values, this could cause issues.I think for most ASP.NET MVC applications (in fact most web applications period) this will not be an issue.Another issue is that the conventions don’t work for implied validation. For example, if you have a property of a simple value type (such as int), theDataAnnotationsValidatorProvidersupplies aRequiredValidatorto validate the value. Since this validator didn’t come from an attribute, it won’t use my convention based lookup for its error messages.I thought about making this work, but it the hooks I need to do this without a large amount of code don’t appear to be there. I’d have to write my own validator provider (as far as I can tell) or register my own validator adapters in place of the default ones. I wasn’t up to the task just yet.
Hi there! I simply want to offer you a big thumbs up for the excellent info you have right here on this post.
ReplyDeleteI will be returning to your site for more soon.
Also visit my blog led lighting
always i used to read smaller posts which also clear their motive,
ReplyDeleteand that is also happening with this piece of
writing which I am reading at this place.
Have a look at my website ... Golf Schools Las Vegas
My spouse and I stumbled over here different website and thought I may as well check things
ReplyDeleteout. I like what I see so i am just following you.
Look forward to going over your web page for a second time.
Feel free to surf to my page; shoulder exercises At Home
Outstanding quest there. What happened after? Good luck!
ReplyDeletemy homepage ... http://fonzation.com/
If you wish for to improve your know-how simply keep visiting this web site and be updated with the newest
ReplyDeletenews posted here.
my page: sushi cat 2 the great parade walkthrough
I think the admin of this web page is truly working hard in
ReplyDeletesupport of his web page, as here every data is quality based data.
Have a look at my homepage; Commercial Led Lighting