As commented by Jeremy Lakeman
, this is a known issue that is planned to be fixed in a future release.
See https://github.com/dotnet/runtime/issues/58247
I was able to work it out this morning with a custom model binder:
internal class StringOnlyEnumTypeModelBinderProvider : IModelBinderProvider
{
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.UnderlyingOrModelType.IsEnum)
{
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new StringOnlyEnumTypeModelBinder(suppressBindingUndefinedValueToEnumType: true, context.Metadata.UnderlyingOrModelType, loggerFactory);
}
return null;
}
private class StringOnlyEnumTypeModelBinder : EnumTypeModelBinder
{
public StringOnlyEnumTypeModelBinder(bool suppressBindingUndefinedValueToEnumType, Type modelType, ILoggerFactory loggerFactory)
: base(suppressBindingUndefinedValueToEnumType, modelType, loggerFactory)
{
}
protected override void CheckModel(ModelBindingContext bindingContext, ValueProviderResult valueProviderResult, object? model)
{
if (bindingContext is null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
var paramName = bindingContext.ModelName;
var paramValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).FirstValue;
if (int.TryParse(paramValue, out var _))
{
bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, bindingContext.ModelMetadata.ModelBindingMessageProvider.AttemptedValueIsInvalidAccessor(paramValue, paramName));
}
else
{
base.CheckModel(bindingContext, valueProviderResult, model);
}
}
}
}
// options are MvcOptions
options.ModelBinderProviders.Remove(options.ModelBinderProviders.Single(x => x.GetType() == typeof(EnumTypeModelBinderProvider)));
options.ModelBinderProviders.Insert(0, new StringEnumTypeModelBinderProvider());