AspNet Core WebApi 中的多态模型绑定?

10

有没有人能够提供一个多态模型绑定的工作示例?我正在尝试使用这个示例(适用于Mvc而不是Api项目),但在API项目中无法运行。我认为在填充ValueProvider方面缺少一些步骤,但我找不到任何与此相关的资源(AspNet Core 3.1)。

我的尝试:

Dtos:

public abstract class Device
{
    public string Kind { get; set; }
}

public class Laptop : Device
{
    public string CPUIndex { get; set; }
}

public class SmartPhone : Device
{
    public string ScreenSize { get; set; }
}

自定义模型绑定器实现:

public class DeviceModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType != typeof(Device))
        {
            return null;
        }

        var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };

        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 DeviceModelBinder(binders);
    }
}

public class DeviceModelBinder : IModelBinder
{
    private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;

    public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
    {
        this.binders = binders;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Kind));
        var modelTypeValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        IModelBinder modelBinder;
        ModelMetadata modelMetadata;
        if (modelTypeValue.FirstValue == "Laptop")
        {
            (modelMetadata, modelBinder) = binders[typeof(Laptop)];
        }
        else if (modelTypeValue.FirstValue == "SmartPhone")
        {
            (modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
        }
        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,
            };
        }
    }
}

I register the model binder provider like so:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers(o => o.ModelBinderProviders.Insert(0, new DeviceModelBinderProvider()));
    }

然后我的控制器:

[ApiController]
[Route("test")]
public class TestController : ControllerBase
{
    [HttpPost]
    public IActionResult Test(Device dto)
    {
        var x = dto;
        return Ok();
    }
}

我要发送一个 JSON 请求体,类似这样:

{
    "ScreenSize": "1",
    "Kind": "SmartPhone"
}

这个文档真的让我感到很沮丧,因为其中涉及的魔法太多了。我的备选方案是手动解析请求中的HttpContent并反序列化。但是我希望能够像示例中一样使用模型绑定器方法。唯一奇怪的两件事是,bindingContext.ModelName为空,而bindingContext.ValueProvider仅包含一个路由值提供程序,其中包含actioncontroller键。所以,看起来请求正文甚至没有被解析成值提供程序。

2个回答

3

你能解释一下什么是json格式化程序不与模型绑定的其他部分交互吗?你的意思是json请求没有被正确解析吗? - kovac

1
我尝试了你发布的完全相同的代码,它对我有效。 这是它的值的图像。

enter image description here

这是一个关于Postman请求的截图。

enter image description here

来自 Postman 的 CURL 请求。

curl --location --request POST 'https://localhost:44332/test' \
--header 'Content-Type: application/json' \
--form 'ScreenSize=1' \
--form 'Kind=SmartPhone'

以下是图片中的 startup.cs 代码:

enter image description here


你使用的是哪个版本的dotnet core?你能否将整个项目上传到某个地方,以便我可以尝试运行它? - kovac
请问您能否展示一下Postman的完整curl请求以及您的完整Startup类?我在两台不同的机器上尝试了,但都无法正常工作。如果您能与我分享完整的项目,我将不胜感激。 - kovac
1
请查看以下信息:“格式化程序是在使用JSON数据时使用的,它们不与模型绑定/值提供程序子系统的其余部分交互。对于这种情况,您需要为您正在使用的JSON库编写转换器。” 源和更多信息在此处:AspNetCore 3.1 Api中的多态模型绑定 - richardsonwtr
1
这就是为什么当您不使用表单数据时它无法工作的原因。 - richardsonwtr
1
@richardsonwtr,通过你的评论,我也了解了真正的原因,这对我很有帮助,这就是我喜欢这个社区的原因。我们互相帮助,也从别人那里得到帮助,同时获得知识。对你的努力点赞。 - Karan
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接