Reputation: 162
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; }
}
Upvotes: 5
Views: 4920
Reputation: 162
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
}
Upvotes: 10
Reputation: 3352
Asp.Net binding does not work like this by default. If you want to this sort of behaviour you will have to write your own custom model binding, which isn't too difficult.
Or, use a view model:
public class InformationBlockViewModel
{
public string Type { get; set; }
public string Text { get; set; }
public string Name { get; set; }
}
Then handle the block type in the controller:
public async Task<IActionResult> CreateArticle(List<InformationBlockViewModel> informationBlocks)
{
foreach (var block in informationBlocks) {
switch (block.Type)
{
case "Text":
// Handle text
break;
case "Image":
// Handle image
break;
case default:
throw new Exception("Unknown information block type.");
}
}
}
Upvotes: 2