Reputation: 484
public enum GroupBy
{
status = 0,
dueDate = 1,
requester = 2,
assignee = 3
}
I am using this enum in params of web api like this:-
public async Task<IActionResult> Dashboard(GroupBy groupBy)
My problem is when i am passing correct enum it will give output. But if I pass any invalid enum it will throw error which is built-in error of ASP.NET Core. I tried to implement but while calling this api it won't go inside my custom validator. When I am passing valid enum it will go inside my validator.
So, I want to implement custom validation for it. Somebody please help
Upvotes: 3
Views: 3592
Reputation: 1061
I am not entirely sure if I understand your problem correctly but I think what you are looking for is model validation. Here is a more generic approach than the already provided answer:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class DefinedEnumValueAttribute : ValidationAttribute
{
private readonly Type enumType;
public DefinedEnumValueAttribute(Type enumType)
{
if (!enumType.IsEnum)
{
throw new ArgumentException($"The given type is not an enum.");
}
this.enumType = enumType;
}
public override bool IsValid(object value)
{
if (value is IEnumerable enumerable)
{
return enumerable.Cast<object>().All(val => Enum.IsDefined(enumType, val));
}
else
{
return Enum.IsDefined(enumType, value);
}
}
}
public class Settings
{
[DefinedEnumValue(typeof(GroupBy))]
public GroupBy GroupBy { get; set; }
}
public async Task<IActionResult> Dashboard(Settings settings)
{
if(!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// do your thing
return Ok();
}
Please note that the attribute can be used for any enum and also for arrays and other enumerables:
public class Settings
{
[DefinedEnumValue(typeof(GroupBy))]
public GroupBy[] Groupings { get; set; }
}
Upvotes: 2
Reputation: 7590
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.
Upvotes: 5