ASP.NET Custom ErrorMessage for Model Enum field
Asked Answered
P

3

6

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

Padget answered 31/10, 2018 at 21:42 Comment(3)
Enum is an int. Maybe 0 is your problem. Have you tried to set the first enum at 1?Prankster
@Prankster Yes I have. In fact, that's how I first noticed the issue. MyEnum is populated form a select box, which at first only contained each element form the enum. I then decided I waned a 'Choose One...' option at the top of the list, and set its value to 0.Padget
Sort of feels like you are searching for this: https://mcmap.net/q/162557/-is-there-out-of-the-box-validator-for-enum-values-in-dataannotations-namespace/125981Zonda
V
5
  • 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 (it will just get set to default value) 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. Using a nullable ValueType solves this problem too.

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.

So your 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.

Vagrant answered 24/11, 2018 at 19:4 Comment(0)
M
2

I think you are searching for the EnumDataTypeAttribute:

[EnumDataType(typeof(MyEnum), ErrorMessage = "Select an item form the list.")]
public MyEnum MyEnum { get; set; }
Mayman answered 31/10, 2018 at 22:44 Comment(4)
This is useful, it appears to do exactly what my EnumCheck attribute class does, so I can remove that. However, if I declare the field as public MyEnum MyEnum { get; set;} then I get the The value '0' is invalid message rather than the specified ErrorMessage. I still have to declare the field as an int.Padget
That is weird, i got the proper validation message in my sample project, how do you make the validation exactly?Bettencourt
Exactly as you have it. I've gone and made a new soultion/project as a minimal example with the same results. Perhaps I need a different method of displaying the error? I have tried both: @Html.ValidationMessageFor(m => m.MyEnum) and <span asp-validation-for="@Model.MyEnum" class="text-danger"></span>Padget
I think this plan will never work. If the posted value can't be turned into an enum, then the ValidationAttributes are never reached. The error happens a stage earlier, in the model-binding. If the posted value can be turned into an enum, then validation always passes! Either way, this ValidationAttribute can never return a fail.Vagrant
S
0

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>
Scarlett answered 17/11, 2021 at 10:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.