Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Model binding with inheritance in Asp.Core

I need to accept list of objects from user:

public async Task<IActionResult> CreateArticle(List<InformationBlockModel> informationBlocks)
    {
        ...
    }

ModelBinder should determine concrete types, but when I trying to cast InformationBlock to TextInformationBlock, exception throws.

Hierarchy:

public class InformationBlockModel
{
    public virtual InformationBlockType Type { get; set; }
}

public class TextInformationBlockModel : InformationBlockModel
{
    public string Text { get; set; }

    public override InformationBlockType Type { get; set; } = InformationBlockType.Text;
}

public class ImageInformationBlockModel : InformationBlockModel
{
    public override InformationBlockType Type { get; set; } = InformationBlockType.Image;
    public string Name { get; set; }
}
like image 261
Vladyslav Kolodka Avatar asked Dec 04 '17 00:12

Vladyslav Kolodka


People also ask

What is model binding in ASP.NET Core MVC?

Model binding allows controller actions to work directly with model types (passed in as method arguments), rather than HTTP requests. Mapping between incoming request data and application models is handled by model binders.

How can we register a custom model binder in ASP.NET Core?

To register custom model binder, we need to create a binder provider. The model binder provider class implement IModelBinderProvider interface. The all built-in model binders have their own model binder providers. We can also specify the type of argument model binder produces, not the input of our model binder.


1 Answers

Finally, I found a solution:

Startup.cs

services.AddMvc()
    .AddJsonOptions(options => options.SerializerSettings.Converters.Add(new InformationBlockConverter()));

JsonCreationConverter.cs

public abstract class JsonCreationConverter<T> : JsonConverter
{
    public override bool CanWrite { get; } = false;

    public override bool CanRead { get; } = true;

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
    }

    protected abstract T Create(Type objectType, JObject jObject);

    public override bool CanConvert(Type objectType)
    {
        return typeof(T) == objectType;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jObject = JObject.Load(reader);

        var target = Create(objectType, jObject);

        serializer.Populate(jObject.CreateReader(), target);

        return target;
    }
}

InformationBlockConverter

public class InformationBlockConverter : JsonCreationConverter<InformationBlockModel>
{
    private readonly Dictionary<InformationBlockType, Type> _types = new Dictionary<InformationBlockType, Type>
    {
        {InformationBlockType.Text, typeof(TextInformationBlockModel)},
        {InformationBlockType.Image, typeof(ImageInformationBlockModel)},
        {InformationBlockType.Video, typeof(VideoInformationBlockModel)}
    };

    protected override InformationBlockModel Create(Type objectType, JObject jObject)
    {
        return (InformationBlockModel) jObject.ToObject(_types[Enum.Parse<InformationBlockType>(
            jObject.GetValue("type", StringComparison.InvariantCultureIgnoreCase).Value<string>(), true)]);
    }
}

InformationBlockType

public enum InformationBlockType
{
    Text,
    Image,
    Video
}
like image 54
Vladyslav Kolodka Avatar answered Sep 28 '22 01:09

Vladyslav Kolodka