Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to disable ModelMetadata.IsRequired from always being true for non-nullable value type

I have a simple model:

public class Sample
{
    public bool A { get; set; }

    [Required]
    public bool B { get; set; }
}

A is obviously not required. Therefore, for validation have have set DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false in Global.asax.

I also have a simple html helper that prints true or false if the model is required:

public static class HtmlHelperExtensions
{
    public static MvcHtmlString IsRequired<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
    {
        var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);        
        return new MvcHtmlString(metadata.IsRequired.ToString());
    }
} 

I also wrote a view to showcase my issue:

@model MvcApplication10.Models.Sample

A: @Html.IsRequired(m => m.A), B: @Html.IsRequired(m => m.B)

I would have expected this to print A: false, B: true, however, it actually prints A: true, B: true.

Is there any way to make this print my expected result? IsRequired seems to always return true even though I have not explicitly set the RequiredAttribute. The docs state that it is true for non-nullable value types by default. How come there is no easy way to set this to false like we can with validation?

EDIT: I could write a custom provider like this, but I was wondering if there was an "easy" way around this:

public class ExtendedDataAnnotationsModelMetadataProvider : DataAnnotationsModelMetadataProvider
{
    private static bool addImplicitRequiredAttributeForValueTypes = false;

    public static bool AddImplicitRequiredAttributeForValueTypes
    {
        get
        {
            return addImplicitRequiredAttributeForValueTypes;
        }
        set
        {
            addImplicitRequiredAttributeForValueTypes = value;
        }
    }

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    {
        var result = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);

        if (!AddImplicitRequiredAttributeForValueTypes && modelType.IsValueType && !attributes.OfType<RequiredAttribute>().Any())
        {
            result.IsRequired = false;
        }

        return result;
    }
}
like image 243
TheCloudlessSky Avatar asked Dec 02 '11 23:12

TheCloudlessSky


2 Answers

As you noted, ValueTypes will default to true. To work around that, you can check for the RequiredAttribute if the type is a ValueType.

ModelMetadata metaData = ModelMetadata.FromLambdaExpression<TModel, TValue>(expression, html.ViewData);

if ((metaData.ModelType.IsValueType && metaData.ModelType.GetCustomAttributes(typeof(RequiredAttribute), false).Any()) ||
    (!metaData.ModelType.IsValueType && metaData.IsRequired))
{ ... }
like image 190
Adam Spicer Avatar answered Nov 18 '22 05:11

Adam Spicer


I guess you're facing a MVC bug. Required will always trigger on that situation, no matter what and even if you use

DataAnnotationsModelValidatorProvider
    .AddImplicitRequiredAttributeForValueTypes = false;

This was already discussed here and reported here. This example goes a step further and shows that when an implicit required triggers it doesn't prevent IValidatableObject from execute. If you run the demo from the second link you can reproduce your case, where required is always true.

Anyway, this is easy to solve because if you're saying that A is obviously not required is the same as saying that it is nullable, so just do it that way:

public bool? A { get; set; }
like image 4
Joao Avatar answered Nov 18 '22 05:11

Joao