问题描述
有人有使用多态模型绑定的自定义模型绑定的工作示例吗?我正在尝试this example(这是针对MVC而不是Api项目的),但它不适用于API项目。我认为在填充ValueProvider方面缺少一些步骤,但我找不到任何与此相关的资源(AspNet Core 3.1)。
我到目前为止的尝试:
DTO:
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, }; } } }
我按如下方式注册模型绑定器提供程序:
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只有一个包含action和controller键的路由值提供者。因此,它看起来甚至没有将主体解析为值提供程序。
JSON
当推荐答案数据不与模型bindingValue提供器子系统的其余部分交互时,将使用它。对于此场景,您必须为您正在使用的JSON库编写一个转换器。
详细信息:
- Microsoft docs: How to write custom converters for JSON serialization (marshalling) in .NET
- Newtonsoft docs: Custom JsonConverter
相关信息
- pranavkm's answer on GitHub(此答案归功于pranavkm)
- Polymorphic model binding in AspNetCore 3.1 Api