John Dorean
John Dorean

Reputation: 3874

ModelState.IsValid ignoring [Required] attribute

For some reason, the ModelState.IsValid check in my controller is returning true even though my entity and viewmodel property has a [Required] attribute on the field that is 0.

This is the entity:

public class Ticket
{
    public int Id { get; set; }

    [Required]
    public int FareId { get; set; }
    public virtual Fare Fare { get; set; }
}

And the viewmodel that my view uses:

public class BaseTicketViewModel
{
    [Required, Display(Name = "Fare")]
    public int FareId { get; set; }
    public Fare Fare { get; set; }
}

In my view, I've used a number of radio buttons instead of a drop down list, something which may be a cause of the problem, I just don't know how:

<div class="form-group">
    @Html.LabelFor(model => model.FareId, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @foreach (FareGroup fareGroup in Model.FareList)
        {
            <h5><strong>@fareGroup.Name</strong></h5>

            foreach (Fare fare in fareGroup.Fares)
            {
                <label class="checkbox-inline">
                    @Html.RadioButtonFor(model => model.FareId, fare.Id)
                    @fare.Name
                </label>
            }
        }
        @Html.ValidationMessageFor(model => model.FareId, "", new { @class = "text-danger" })
    </div>
</div>

When I submit the form without selecting a fare, my controller raises a DbUpdateException which tells me that the INSERT statement conflicted with a FOREIGN KEY constraint, basically that the Fare with ID 0 doesn't exist:

{"The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Tickets_dbo.Fares_FareId". The conflict occurred in database "redacted", table "dbo.Fares", column 'Id'.\r\nThe statement has been terminated."}

By my understanding, if the FareId property on the viewmodel/ticket entity is 0 then ModelState.IsValid should be false because of the [Required] annotation on that property?

Upvotes: 2

Views: 3020

Answers (1)

David L
David L

Reputation: 33833

Integers will default to 0 since they are value types. As a result, it is meeting the requirements of the Required attribute, although rather poorly for your scenario.

The Required Attribute documentation states:

The RequiredAttribute attribute specifies that when a field on a form is validated, the field must contain a value. A validation exception is raised if the property is null, contains an empty string (""), or contains only white-space characters.

Since your int is not nullable and since it clearly cannot be an empty string, it passes the validation.

If you wanted to ensure that your Id is not the default value, you could use the Range attribute, although if you did have an Id of 0 this would cause you problems.

[Range(1, int.MaxValue,
   ErrorMessage = "Value for {0} must be between {1} and {2}.")]

Finally, as Chris Peterson pointed out, if FareId is nullable, it will be able to properly utilize the Required attribute since it will default to null if a value isn't posted, triggering validation.

[Required]
public int? FareId { get; set; }

Upvotes: 7

Related Questions