.NET Zone is brought to you in partnership with:

I'm a software developer working as a senior consultant at Kentor in Stockholm, Sweden. My core competence is as a technical specialist within development and system architecture. In my heart I am, and probably will remain, a programmer. I still think programming is tremendously fun, more than 20 years after I first tried it. That's why my blog is named Passion for Coding.  Anders is a DZone MVB and is not an employee of DZone and has posted 84 posts at DZone. You can read more from them at their website. View Full User Profile

EF Code First/MVC NotNullAttribute

05.03.2012
| 3400 views |
  • submit to reddit

Unfortunately MVC3 doesn’t respect the [Required] attribute’s AllowEmptyStrings property. When using a class both as an MVC model and as an EF Code First entity this is a problem. What if I want to allow empty strings, but not null values in the database?

The problem lies in the client side validation performed by MVC3. If a property in the model is marked with [Required] the jquery required validator will be enabled. It requires a non-empty string. I would prefer MVC to not emit a required validation if AllowEmptyStrings is true. Unfortunately the MVC code doesn’t honor that flag, but there is a workaround. Create a small attribute derived from RequiredAttribute.

public sealed class NotNullAttribute : RequiredAttribute
{
    public NotNullAttribute()
    {
        AllowEmptyStrings = true;
    }
}

To make it work, another attribute has to be set on the property to prevent the model binder from converting an empty string to null.

[NotNull]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string SomeProperty { get; set; }

It is surprisingly simple, but works thanks to a difference in how EF Code First and MVC3 handles attributes.

EF Code First recognizes the code above as a RequiredAttribute and sets the column in the database to not null accordingly. That is te normal behavior when querying for attributes – check if the attribute is of a given class, or any class derived from it. What is a bit surprising is that the MVC3 client validation does something else.

To understand why we first have to catch up on client side handling of validation attributes. When writing a custom validation attribute, client side validation is handled by implementing the IClientValidatable interface. However, the built in attributes belong to the System.ComponentModel.DataAnnotations and are not aware of client validation in MVC. For the built in attributes, there is a bit of special handling in MVC3 to give them client validation. The relevant part of the code is in DataAnnotationsModelValidatorProvider.cs:

// Produce a validator for each validation attribute we find
foreach (ValidationAttribute attribute in attributes.OfType<ValidationAttribute>()) {
    DataAnnotationsModelValidationFactory factory;
    if (!AttributeFactories.TryGetValue(attribute.GetType(), out factory)) {
        factory = DefaultAttributeFactory;
    }
    results.Add(factory(metadata, context, attribute));
}

The code checks for all attributes derived from ValidationAttribute. It then looks up the type of the attribute in a dictionary, to get a factory instance. That lookup is done on the exact type, not checking derived types. I don’t know if this is intentional or not, but it provides an excellent extension point. If a RequiredAttribute is passed in, a special factory producing a client side required rule is returned. For our NotNullAttribute there is no match so a DefaultAttributeFactory is returned. It will eventually produce client validation rules for any attribute that implements IClientValidatable. The NotNullAttribute doesn’t, so no client rules are created.

In a future version of MVC I would prefer if the RequiredAttributeAdapter checked the AllowEmptyStrings property before emitting the client validation rule. Until then, the small NotNullAttribute provides a workaround.

Published at DZone with permission of Anders Abel, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)