ASP.NET Custom ErrorMessage for Model Enum field

3.8k views Asked by At

I am developing a website built on EntityFrameworkCore and targeting ASP.NET Core 2.1. I want to specify an error message for an enum field in my model like so:

[Required(ErrorMessage = "Select an item from the list.")]
public MyEnum MyEnum { get; set; }

However, the stock message is still generated: The value '0' is invalid. The problem appears to be that the Enum type is validated prior to any of my code being evaluated. The two approaches presented here (https://www.codeproject.com/Articles/1204077/ASP-NET-Core-MVC-Model-Validation), either creating a class that inherits from ValidationAttribute, or having the model inherit from IValidatableObject both suffer from this.

I have found a workaround: declare the field as an int, and then use a custom validation attribute:

[EnumCheck(typeof(MyEnum), ErrorMessage = "Select an item form the list.")]
public int MyEnum { get; set; }

...and then subclass from ValidationAttribute:

sealed public class EnumCheck : ValidationAttribute
{
    readonly Type t_;

    public EnumCheck(Type t)
    {
        t_ = t;
    }

    public override bool IsValid(object value)
    {
        return Enum.IsDefined(t_, value);
    }
}

This approach has some drawbacks as now I need to cast the field to the Enum type in many places that it is used.

Is there a way to provide an ErrorMessage for Enum field types?

UPDATE

The following is a minimal example (No longer using EnumCheck subclass from ValidationAttribute, but rather the EnumDataType mentioned by @PéterCsajtai):

Model

namespace web.Models
{
    public enum Day
    {
        Sunday = 1,
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday
    }

    public class Form
    {
        [EnumDataType(typeof(Day), ErrorMessage = "Select an item from the list.")]
        public Day Day { get; set; }
    }
}

Controller

namespace web.Controllers
{
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Save(Form model)
        {
            if(!ModelState.IsValid)
            {
                return View("Index");
            }

            return View("Index", model);
        }
    }
}

View

<form asp-controller="Home">
    <div asp-validation-summary="All" class="text-danger"></div>
    <fieldset>
        <label asp-for="@Model.Day"></label>
        <select asp-for="@Model.Day" asp-items="Html.GetEnumSelectList<Day>()">
            <option value="">Select...</option>
        </select>
        @Html.ValidationMessageFor(m => m.Day)
        <span asp-validation-for="@Model.Day" class="text-danger"></span>
    </fieldset>
    <fieldset>
        <input type="submit" asp-action="Save" />
    </fieldset>
</form>

And the output after form post:

form output

3

There are 3 answers

0
Chris F Carroll On BEST ANSWER
  • In your original case, [Required(ErrorMessage = "Select an item from the list.")] you are setting the message to be shown if MyEnum is missing. But like all ValueTypes, it can never be missing so it will never trigger that validation. The solution for this is the nullable ValueType.

  • Your second effort still doesn't work because the model-binding failure – "Can a blank value be converted to a Day? No it can't." kicks in before your validation can kick in.

Validation for a Type presupposes that you have an instance of that Type to validate. The way Aspnetcore turns a form post into that typed value, is modelbinding. If the posted value can't be model-bound -- for instance if you post "boo" to a property declared as an int, or an empty string to an Enum -- then validation never even starts. Instead the modelbinding error is shown.

The simple solution is

  • Use a nullable enum, Day? so that modelbinding a blank succeeds (blank resolves to null).
  • Use [Required()] so that that null value then fails validation.

Conclusion: change your form to:

public class Form
{
    [Required(ErrorMessage = "Select an item from the list.")]
    public Day? Day { get; set; }
}

And then it will work as you'd expect.

Reference: Model Validation in AspNet Core MVC

NB unlike other ValidationAttributes, the documentation for EnumDataType, although it inherits from ValidationAttribute, doesn't give an example of using it for validation. Instead the example is of using it for MetaData.

4
Péter Csajtai On

I think you are searching for the EnumDataTypeAttribute:

[EnumDataType(typeof(MyEnum), ErrorMessage = "Select an item form the list.")]
public MyEnum MyEnum { get; set; }
0
hamid reza shahshahani On

Define Your Model:

public enum Day
{
    None=0,
    Sunday = 1,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

and then define your Custome Validation Attribute:

sealed public class EnumCheck : ValidationAttribute
  {
    readonly Type t_;

    public EnumCheck(Type t)
     {
       t_ = t;
     }

   public override bool IsValid(object value)
    {

      if (((int)value)==0)
        {
            return false;

        }
      return Enum.IsDefined(t_, value);
    }
}

and finally use from this in your view:

<form asp-controller="Home">
<div asp-validation-summary="All" class="text-danger"></div>
<fieldset>
    <label asp-for="@Model.Day"></label>
    <select asp-for="@Model.Day" asp-items="Html.GetEnumSelectList<Day>()">
        <option value="0">Select...</option>
    </select>
    @Html.ValidationMessageFor(m => m.Day)
    <span asp-validation-for="@Model.Day" class="text-danger"></span>
</fieldset>
<fieldset>
    <input type="submit" asp-action="Save" />
</fieldset>