41

I have the following model:

public class ViewDataItem
{
    public string viewName { get; set; }
    public UpdateIndicator updateIndicator { get; set; }
}

With the following enum:

public enum UpdateIndicator
{
    Original,
    Update,
    Delete
}

And the following Validator:

public class ViewValidator : AbstractValidator<ViewDataItem>
{
    public ViewValidator()
    {
        RuleFor(x => x.viewName).NotEmpty().WithMessage("View name must be specified");
        RuleFor(x => x.updateIndicator).SetValidator(new UpdateIndicatorEnumValidator<UpdateIndicator>());
    }
}

public class UpdateIndicatorEnumValidator<T> : PropertyValidator
{
    public UpdateIndicatorEnumValidator() : base("Invalid update indicator") {}

    protected override bool IsValid(PropertyValidatorContext context)
    {
        UpdateIndicator enumVal = (UpdateIndicator)Enum.Parse(typeof(UpdateIndicator), context.PropertyValue.ToString());

        if (!Enum.IsDefined(typeof(UpdateIndicator), enumVal))
          return false;

        return true;
    }
}

The code is in a WebAPI that receives data via JSON, deserialize it to an object and then validates, but for some reason I can send whatever I please in the updateIndicator, so long as I don't put in an integer value larger than the max index in the enum (i.e 1,2 or 3 works fine, but 7 will generate an error).

How can I get this to validate the input of the data I receive to see if that value is actually in the Enum?

5
  • You're trying to see if viewName is a text value within UpdateIndicator? Commented Jul 10, 2017 at 14:01
  • I'm trying to see if the text value sent as "updateIndicator" actually exists in the Enum UpdateIndicator. I.e if someone sends "bananas" it should return an error, but if someone sends "Original" (which exists in the enum) it should validate just fine. Commented Jul 10, 2017 at 14:03
  • 1
    That won't happen. Try calling your endpoint from Postman or Fiddler with an invalid value, and see what happens. It will give you the default value of the enum (first value). If you want to guard against that, you could make the property nullable, but have your validation not allow for nulls. Commented Jul 10, 2017 at 14:21
  • Thanks. You just helped me blew the box open. It's fixed with nullable Enum and check for null values. :) Commented Jul 10, 2017 at 14:38
  • I'll write up an answer for this then. I'm sure it would come in handy for someone down the line. Commented Jul 10, 2017 at 15:35

3 Answers 3

122

Try the built-in IsInEnum()

RuleFor(x => x.updateIndicator).IsInEnum();

This checks if the provided enum value is within the range of your enum, if not, the validation will fail:

"'updateIndicator' has a range of values which does not include '7'."

Sign up to request clarification or add additional context in comments.

2 Comments

Maybe it's better to check default value zero manually. or add NotNull() too.
This doesn't seem to work with .WithMessage() still providing the standard "Error converting value \"Blah\" to type "WhateverType"
18

The problem arises from the fact that the API model builder will convert what is sent to an enum. If a value isn't found, it doesn't populate it, and the default value is used (as it would be with any other property data type that isn't populated).

In order to easily tell if the value sent is a valid enum value, you should make your property nullable. That way, if a value isn't able to be parsed, it will be set to null. If you want to ensure that the property is set, just have your validator not allow null values for it.

public class ViewDataItem
{
    public string viewName { get; set; }
    public UpdateIndicator? updateIndicator { get; set; }
}

public class ViewValidator : AbstractValidator<ViewDataItem>
{
    public ViewValidator()
    {
        RuleFor(x => x.viewName).NotEmpty().WithMessage("View name must be specified");
        RuleFor(x => x.updateIndicator).NotNull();
    }
}

Without setting the property to null, your model will always have a valid value when you have it. Alternatively, you could have the first value of your enum be a dummy value, but that would be a code smell. A null model property makes far more sense.

If you want to find out what the actual value that was sent to the API endpoint was, you'll need to look at creating an HTTP Handler, which is beyond the scope of this question.

1 Comment

Stacked's answer is the right way to do it. It's good to make sure the values are within the range of the Enum.
11

This seems to work ok for me. But the answer from Stacked generally better if Foo the request is of type FooEnum

RuleFor(x => x.Foo).Must(i => Enum.IsDefined(typeof(FooEnum), i));

1 Comment

it's a valid answer

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.