0

Below is my Enum

public enum IdentifierType
{
    Customer = 1,
    Manager = 2,
    Director = 3
}

Using fluent validation in .Net core, is it possible to do validation where passing 1/"1" or 2/"2" or 3/"3" in the request should return validation error?

Passing "Customer" or "Manager" etc should work fine.

I know in C# enum type is 'int' but any thoughts if this is doable in first place?

Setup in startup.cs: Validator is registered before my converter.

services.AddControllers()
        .AddFluentValidation(configuration =>
        {
          configuration.RegisterValidatorsFromAssemblyContaining<Startup>();
        })
          .ConfigureApiBehaviorOptions(opt => { opt.SuppressModelStateInvalidFilter = true; })
          .AddJsonOptions(serializerOption =>
          {
            serializerOption.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
            serializerOption.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
            serializerOption.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
          });

Attached behaviour of API using postman

enter image description here enter image description here

5
  • Do you want to explicitly disallow 1, 2, 3 or allow both int and string to be passed in? Commented Apr 7, 2022 at 18:20
  • @DavidG everything expect "Customer" or "Manager" has to return error Commented Apr 7, 2022 at 18:22
  • Second question(s)... Are you wanting this validation to happen when a request is posted to your API? And does it need to be FluentValidation that handles it? Those two things are kinda separate. Commented Apr 7, 2022 at 19:05
  • @DavidG updated question with more information. Please see if that answers yours questions. The framework is converting the enum before reaching my validator. Commented Apr 7, 2022 at 19:19
  • @DavidG to your 2nd question, yes it would be better if FluentValidation handles this scenario. Commented Apr 7, 2022 at 19:35

2 Answers 2

2

The MVC framework has to convert JSON to a class for FluentValidation to work.

However, you can configure MVC to validate it first.

To allow the built-in JSON serialiser in any ASP.NET MVC Core app to allow strings to be converted into an enum, you need to add the JsonStringEnumConverter converter in your app startup. This converter also has a parameter you can set to false to disallow integer values. For example:

services
    .AddMvc()
    .AddJsonOptions(opts =>
    {
        opts.JsonSerializerOptions.Converters.Add(
            new JsonStringEnumConverter(allowIntegerValues: false));
    })

Since it seems that '' won't prevent an int being passed in as a string, you can write your own converter. For example, something like this would work:

public class IdentifierTypeConverter : JsonConverter<IdentifierType>{
    public override IdentifierType Read(ref Utf8JsonReader reader, 
        Type typeToConvert, JsonSerializerOptions options)
    {
        var value = reader.GetString();
        
        if(value == null)
        {
            throw new Exception("No null values thanks!");  
        }
        
        if(int.TryParse(value, out var _))
        {
            throw new Exception("No numbers thanks!");
        }
        
        return (IdentifierType)Enum.Parse(typeof(IdentifierType), value);
    }

    public override void Write(Utf8JsonWriter writer, IdentifierType value, 
        JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

You could make a generic version using a JsonConverterFactory. First make the converter generic:

public class EnumConverter<TEnum> : JsonConverter<TEnum> where TEnum : Enum
{
 public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;

    public override TEnum Read(ref Utf8JsonReader reader, 
        Type typeToConvert, JsonSerializerOptions options)
    {
        var value = reader.GetString();

        if (value == null)
        {
            throw new Exception("No null values thanks!");
        }

        if (int.TryParse(value, out var _))
        {
            throw new Exception("No numbers thanks!");
        }

        return (TEnum)Enum.Parse(typeof(TEnum), value);
    }

    public override void Write(Utf8JsonWriter writer, TEnum value, 
        JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString());
    }
}

And make your factory:

public class EnumConverterFactory : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum;

    public override JsonConverter? CreateConverter(Type typeToConvert, 
        JsonSerializerOptions options)
    {
        
        JsonConverter converter = (JsonConverter)Activator.CreateInstance(
            typeof(EnumConverter<>).MakeGenericType(typeToConvert))!;

        return converter;
    }
}

And now add the factory instead:

opts.JsonSerializerOptions.Converters.Add(new EnumConverterFactory());
Sign up to request clarification or add additional context in comments.

6 Comments

I have already tried adding allowIntegerValues: false to my converter but that is only restricting when "IdentifierType": 2 is passed. It is not working for "IdentifierType": "2" which I can understand is being treated as a string. I need passing "IdentifierType": "2" also return validation error.
Ah, in that case you can write your own converter.
Is it possible to make the converter generic to work any Enum type instead of just IdentifierType?
How does that work?
your EnumConverter is still using the enum IdentifierType in line return (TEnum)Enum.Parse(typeof(IdentifierType), value);
|
0

AFAIK there is no direct solution in fluent validations to solve your problem. However, I would handle this scenario something as stated below:

In the below code, I have created two properties - The IdentifierType field is public and it will accept a string value from your client. The other field is an enum and it can be used internally inside your project.

public class YourRequestModel
{
   internal IdentifierType IdType
   {
     get
     {
        return Enum.Parse<IdentifierType>(IdentifierType);
     }
    }
   public string IdentifierType { get; }
}

While doing fluent validation, validate the input only against the string value.

public class YourRequestModelValidator: AbstractValidator<YourRequestModel> 
{
  RuleFor(x => x.IdentifierType)
            .IsEnumName(typeof(IdentifierType), caseSensitive: false);
}

2 Comments

No, IdentifierType has to be an enum. I cannot change the contract and validate it on string.
But, you are accepting only the string value from the enduser?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.