anyone has a working example of a custom model binding with polymorphic model binding? I'm trying this example (which is for Mvc not Api projects) with a web api project but it's not working for API projects. I think some steps are missing in terms of populating the ValueProvider
but I can't find any resources related to this (AspNet Core 3.1).
My attempt so far:
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; }
}
Custom model binder implementation:
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()));
}
Then my controller:
[ApiController]
[Route("test")]
public class TestController : ControllerBase
{
[HttpPost]
public IActionResult Test(Device dto)
{
var x = dto;
return Ok();
}
}
I'm posting a json request body like:
{
"ScreenSize": "1",
"Kind": "SmartPhone"
}
Really fedup with the documentation on this as there's too much magic going on. My fallback is to manually parse the HttpContent from the request and deserialise. But I'm hoping to use the model binder approach like in the example. The only two strange things I'm seeing are, the bindingContext.ModelName
is empty and bindingContext.ValueProvider
only has a route value provider containing action
and controller
keys. So, it looks like the body is not even parsed into the value provider.
Formatters, which is what's used when JSON data, do not interact with the rest of the model binding\value provider subsystem. For this scenario, you'd have to write a converter for the JSON library that you're using.
I have tried exact same code you posted and its working for me.
Here is image of its value.
and here is screenshot of postman request.
CURL request from postman.
curl --location --request POST 'https://localhost:44332/test' \
--header 'Content-Type: application/json' \
--form 'ScreenSize=1' \
--form 'Kind=SmartPhone'
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.