I encounter an issue with Polymorphe Model Binding.
I tried a different ModelBinder but without success.
Sometimes I got an infinite loop with DefaultModelBuilder as Describe in the doc. And Sometimes I got a unsupported type with BodyModelBinding.
I share my code snippest. Maybe Someone have tried to solve this problem before
public class CustomOptionTypeCreateDtoModelBinderProvider : IModelBinderProvider
{
private readonly Collection<IInputFormatter> _formatters;
private readonly IHttpRequestStreamReaderFactory _readerFactory;
private BodyModelBinderProvider _defaultProvider;
private ILoggerFactory loggerFactory;
public CustomOptionTypeCreateDtoModelBinderProvider(Collection<IInputFormatter> optionsInputFormatters,
IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory _loggerFactory)
{
_formatters = optionsInputFormatters;
_readerFactory = readerFactory;
_defaultProvider = new BodyModelBinderProvider(optionsInputFormatters, readerFactory);
loggerFactory = _loggerFactory;
}
public IModelBinder? GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(DemoOptionValueBaseDto))
{
return null;
}
var subclasses = new[] { typeof(DemoTextOptionValueCreateDto), typeof(DemoTextSwatchOptionValueCreateDto), };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in subclasses)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new CustomOptionTypeCreateDtoModelBinder(binders, _formatters, _readerFactory, _defaultProvider,loggerFactory);
}
}
Custom ModelBinder
public class CustomOptionTypeCreateDtoModelBinder : IModelBinder
{
private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;
private readonly IList<IInputFormatter> formatters;
private readonly IHttpRequestStreamReaderFactory readerFactory;
private BodyModelBinderProvider _defaultProvider;
private ILoggerFactory loggerFactory;
public CustomOptionTypeCreateDtoModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> _binders,
IList<IInputFormatter> inputFormatters,
IHttpRequestStreamReaderFactory httpRequestStreamReaderFactory,
BodyModelBinderProvider defaultProvider, ILoggerFactory _loggerFactory)
{
formatters = inputFormatters;
readerFactory = httpRequestStreamReaderFactory;
_defaultProvider = defaultProvider;
binders = _binders;
loggerFactory = _loggerFactory;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
bindingContext.BindingSource = BindingSource.Form;
var modelKindName =
ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(OptionValueType));
var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName);
IModelBinder modelBinder = default!;
ModelMetadata modelMetadata = default!;
if (Enum.TryParse(modelTypeValue.FirstValue, out OptionValueType type))
{
if (type == OptionValueType.TextSwatchOptionValue)
{
(modelMetadata, modelBinder) = binders[typeof(DemoTextSwatchOptionValueCreateDto)];
}
if (type == OptionValueType.TextOptionValue)
{
(modelMetadata, modelBinder) = binders[typeof(DemoTextOptionValueCreateDto)];
}
}
else
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
if (newBindingContext.Result.IsModelSet)
{
// Setting the ValidationState ensures properties on derived types are correctly
bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
Models
public class DemoVariantOptionCreateDto : DemoOptionCreateDto
{
}
public abstract class DemoOptionCreateDto
{
// [ModelBinder(typeof(CustomOptionTypeCreateDtoModelBinder))]
public IEnumerable<DemoOptionValueBaseDto>? Values { get; set; } = default!;
}
public abstract class DemoOptionValueBaseDto
{
public bool IsDefault { get; set; } = false;
public abstract OptionValueType OptionValueType { get; set; }
}
public class DemoTextOptionValueCreateDto : DemoOptionValueBaseDto
{
public string Value { get; set; } = default!;
public override OptionValueType OptionValueType { get; set; }= OptionValueType.TextOptionValue;
}
public class DemoTextSwatchOptionValueCreateDto : DemoOptionValueBaseDto
{
public string ColorName { get; set; } = default!;
public override OptionValueType OptionValueType { get; set; } = OptionValueType.TextSwatchOptionValue;
}
Inside the controllers
public async Task<IActionResult> AddSharedVariantOption(
[FromForm, SwaggerParameter("variant options are required", Required = true)]
DemoVariantOptionCreateDto dto)
{
return Ok();
}
Services
services.AddControllers(options =>
{
var readerFactory = services.BuildServiceProvider().GetRequiredService<IHttpRequestStreamReaderFactory>();
var loggerFactory = services.BuildServiceProvider().GetRequiredService<ILoggerFactory>();
options.ModelBinderProviders.Insert(0,
new CustomOptionTypeCreateDtoModelBinderProvider(options.InputFormatters, readerFactory, loggerFactory));
})
Postman
Found I need to just change this part of the custom binder
else
{
**bindingContext.Result = ModelBindingResult.Failed();**
return;
}
To
else
{
return;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.