简体   繁体   English

Asp.net 内核 Controller 的自定义模型绑定问题

[英]Custom ModelBinding Issues With Asp.net Core Controller

I encounter an issue with Polymorphe Model Binding.我遇到了 Polymorphe Model 绑定问题。

I tried a different ModelBinder but without success.我尝试了不同的 ModelBinder,但没有成功。

Sometimes I got an infinite loop with DefaultModelBuilder as Describe in the doc.有时我得到一个无限循环,其中DefaultModelBuilder作为文档中的描述。 And Sometimes I got a unsupported type with BodyModelBinding.有时我的 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 Postman

在此处输入图像描述

Found I need to just change this part of the custom binder发现我只需要更改自定义活页夹的这一部分

   else
        {
            **bindingContext.Result = ModelBindingResult.Failed();**
            return;
        }

To

  else
            {
                return;
            }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM