When the value is incorrect, the model binder can't create the GroupBy instance and can't call custom validation on this instance.
A solution is to change the input parameter type to string and do manually the check and parse step :
public async Task<IActionResult> Dashboard(string groupBy)
{
if(!Enum.TryParse(groupBy, out GroupBy by))
{
ModelState.AddModelError(nameof(groupBy), $"The value is invalid. Valid value : {Enum.GetValues(typeof(GroupBy))}");
return BadRequest(ModelState);
}
return Ok(by);
}
Other solution is to override the model binder behavior.
For this, you need create a custom binder :
public class GroupByBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
// Try to fetch the value of the argument by name
var modelName = "groupBy";
var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (string.IsNullOrEmpty(value))
{
return Task.CompletedTask;
}
// Custom validation
if (!Enum.TryParse(value, out GroupBy groupBy))
{
bindingContext.ModelState.AddModelError(modelName, $"The value is invalid. Valid value : {Enum.GetValues(typeof(GroupBy))}");
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Success(groupBy);
return Task.CompletedTask;
}
}
And now you can :
public async Task<IActionResult> Dashboard2([ModelBinder(typeof(GroupByBinder))] GroupBy groupBy)
{
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
return Ok(groupBy);
}
You can override this behavior to all GroupBy input parameter :
public class GroupByBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(GroupBy))
{
return new BinderTypeModelBinder(typeof(GroupByBinder));
}
return null;
}
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.ModelBinderProviders.Insert(0, new GroupByBinderProvider());
});
}
}
Warning : Model Builder isn't used when the data come from JSON or XML content. More detail on the official documentation.
stringand manually check/parse the input value.