json.netasp.net-coremodel-binding

Custom ModelBinder for Interface .NET API


I'm trying to write a custom model binder for a .NET API Endpoint. I have a Post Endpoint with one parameter of type ICar. I would like to call this endpoint with different implementations of ICar:

Classes and Interface

public interface ICar
{
    CarType Type { get; set; }
}

class FunCar : ICar
{
    public CarType Type { get; set; } = CarType.Fun;
    public string Color { get; set; } = "red";
}

class SportCar : ICar
{
    public CarType Type { get; set; } = CarType.Sport;
}

public enum CarType
{
    Fun,
    Sport
}

I would like to Convert the passed Object to the correct type with a ModelBinder CarModelBinder.

public class CarModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        ArgumentNullException.ThrowIfNull(bindingContext);
        var valueProviderResult = bindingContext.ValueProvider.GetValue("type");
        if (valueProviderResult == ValueProviderResult.None)
        {
            return Task.CompletedTask;
        }
        // ... do the matching - never reaching that point
        return Task.CompletedTask;
    }
}

My Endpoint looks like this:

[HttpPost("AddCar")]
public ActionResult<ICar> AddCar([FromBody] [ModelBinder(typeof(CarModelBinder))] ICar car)
{
    Console.WriteLine(car.Type);
    return new FunCar();
}

Example Request Body

{
  "type": 1
}

Question


Solution

  • You could try following code which read body from httpcontext and use newtonsoft to parse the "type" before de-serialize.

    CarModelBinder.cs

        public class CarModelBinder : IModelBinder
        {
            public Task BindModelAsync(ModelBindingContext bindingContext)
            {
                string jsonString;
                using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body, Encoding.UTF8))
                {
                    jsonString = reader.ReadToEndAsync().GetAwaiter().GetResult();
                }
                var jsonObject = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(jsonString);
                int enumNumber = (int)jsonObject["type"];
    
    
                if (enumNumber == 0)
                {
                    var model = JsonConvert.DeserializeObject<FunCar>(jsonString);
                    bindingContext.Result = ModelBindingResult.Success(model);
                    return Task.CompletedTask;
    
                }
                if (enumNumber == 1)
                {
                    var model = JsonConvert.DeserializeObject<SportCar>(jsonString);
                    bindingContext.Result = ModelBindingResult.Success(model);
                    return Task.CompletedTask;
                }
                else
                {
                    return Task.CompletedTask;
                }
            }
        }
    

    Test result
    enter image description here
    enter image description here