Model binding with inheritance in Asp.Core - asp.net

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; }
}

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
}

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.");
}
}
}

Related

I can not create instace of DBContext

I have model:
public class Department
{
public int DepartmentID { get; set; }
[Required]
[UniqueDepartmentName]
public string Name { get; set; }
public List<Person> Persons { get; set; }
}
And DBcontext:
public class InstituteContext : DbContext
{
public InstituteContext (DbContextOptions<InstituteContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>().HasIndex(p => p.Name).IsUnique();
}
public DbSet<Institute.Models.Department> Department { get; set; }
}
As you see property "NAME" i make unique.
For validation i create My validation Attribute:
public class UniqueDepartmentName : ValidationAttribute
{
public override bool IsValid(object value)
{
var db = new InstituteContext();
}
}
But i can not create instance of InstituteContext, because constructor need parameters.
How i can create instance of InstituteContext? Or what should i pass to constructor in parameters?
Try this:
public class UniqueDepartmentName : ValidationAttribute
{
public override bool IsValid(object value)
{
var connectionString = "Your ConnectionString"
var options = new DbContextOptionsBuilder<InstituteContext>()
.UseSqlServer(new SqlConnection(connectionString)).Options;
using (var dbContext = new BloggingContext(options))
{
// Do necessary staffs here with dbContext
}
}
}
Your DbContextOptions method is in the wrong place, your constructor can be empty, and you need to add the method OnConfiguring, which receives the DbContextOptions.
Something like:
public DbSet<Department> Department { get; private set; }
protected override void OnConfiguring(DbContextOptionsBuilder options) {
// In my case I'm passing the connection string in this method below
options.UseSqlServer("Data Source=DATABASEIP;Initial Catalog=DATABASETABLE;" +
"User ID=USER;Password=PASSWORD");
}

Clean way for updating object in a collection of abstract objects

As I'm developping an asp net core + ef core 2.0 with localized objects in my model, I adapted the solution provided in the following link to localize my objects link.
I'm now trying to find a clean way to update my collection of translation when updated object are received in the controller.
For the moment I have a step model class defined this way :
public class Step
{
//Native properties
public Guid ID { get; set; }
public string Name { get; set; }
public int Order { get; set; }
public string ScriptBlock { get; set; }
//Parent Step Navigation property
public Nullable<Guid> ParentStepID { get; set; }
public virtual Step ParentStep { get; set; }
//Collection of sub steps
public virtual ICollection<Step> SubSteps { get; set; }
//MUI Properties
public TranslationCollection<StepTranslation> Translations { get; set; }
public string Description { get; set; }
//{
// get { return Translations[CultureInfo.CurrentCulture].Description; }
// set { Translations[CultureInfo.CurrentCulture].Description = value; }
//}
public Step()
{
//ID = Guid.NewGuid();
Translations = new TranslationCollection<StepTranslation>();
}
}
public class StepTranslation : Translation<StepTranslation>
{
public Guid StepTranslationId { get; set; }
public string Description { get; set; }
public StepTranslation()
{
StepTranslationId = Guid.NewGuid();
}
}
Translation and translationCollection are the same as in the link
public class TranslationCollection<T> : Collection<T> where T : Translation<T>, new()
{
public T this[CultureInfo culture]
{
// indexer
}
public T this[string culture]
{
//indexer
}
public bool HasCulture(string culture)
{
return this.Any(x => x.CultureName == culture);
}
public bool HasCulture(CultureInfo culture)
{
return this.Any(x => x.CultureName == culture.Name);
}
}
public abstract class Translation<T> where T : Translation<T>, new()
{
public Guid Id { get; set; }
public string CultureName { get; set; }
protected Translation()
{
Id = Guid.NewGuid();
}
public bool HasProperty(string name)
{
return this.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Any(p => p.Name == name);
}
}
My issue in this sample is how to deal correctly with the PUT method and the Description property of my step controller. When it receive a Step object to update (which is done through a native c# client) only the string Description property of Step might have been created/updated/unchanged. So I have to update/create/do Nothing on the Description of the translation in the correct culture.
My first guess is to add in the TranslationCollection class a method in which I could pass the culture, the name of the property to update or not (Description in this case) and the value of the Description.
But as the TranslationCollection is a collection of abstract objects I don't even if this is a good idea and if it's possible.
If someone would have any advice on it (hoping I was clear enough) it would be great !
Finally answered my own question, and it was quite simple.
Just had to use the indexer like :
myobject.Translations[userLang].Name = value;

Skip property name and only use the value in json

I'm trying to parse to the following json string with NewtonSoft json 4.5:
{
"Name":"Henrik",
"Children":[
{
"Name":"Adam",
"Grandchildren":[
"Jessica", //How can i only show the value and not the property name?
"Michael"
]
}
]
}
My objects that i will serialize to json looks like this:
public class Parent
{
public string Name { get; set; }
[JsonProperty(PropertyName = "Children")]
public List<Child> Childs { get; set; }
}
public class Child {
public string Name { get; set; }
[JsonProperty(PropertyName = "Grandchildren")]
public List<GrandChild> GrandChilds { get; set; }
}
public class GrandChild {
[JsonProperty(PropertyName = "")]
public string Name { get; set; }
}
I've tried to set the property name to empty but it doesnt solve the issue.
You should replace List<GrandChild> with List<string> or else add a custom JsonConverter for GrandChild like so.
[JsonConverter(typeof(GrandChildConverter))]
public class GrandChild
{
public string Name { get; set; }
}
public class GrandChildConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(GrandChild);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(((GrandChild)value).Name);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new GrandChild { Name = reader.Value.ToString() };
}
}

Bind specified property of the WebAPI controller's model

I want to bind only specified property of the WebAPI controller's model and skip all another properties to the DefaultModelBinder.
For example I have json, which should be passed to the WebAPI controller:
{
prop1: 1,
prop2: "b",
prop3: "c",
currentTime: "Fri Feb 27 2015",
references:
[
{ name: "Name 1", type: "Info" },
{ name: "Name 2", type: "Warn" }
]
}
On the server-side there is a HttpPost action with the following signature:
[HttpPost]
public void SaveObject(ReferenceModel model)
{
...
}
Where ReferenceModel is:
public class ReferenceModel
{
public int Prop1 {get; set;}
public string Prop2 {get; set;}
public string Prop3 {get; set;}
public DateTime CurrentTime {get; set;}
public List<BaseReferenceItem> References {get; set;}
}
I need for custom bind of References property, because I want to initialize this property at the runtime by objects of derived types.
For example if I will get Prop1 with value 1 so I need to initialize this collection by objects of type DerivedReferenceType1:
Model.References = new List<BaseReferenceItem>()
{
new DerivedReferenceType1(){...},
new DerivedReferenceType1(){...}
}
If I will get Prop1 with value 2 - of type DerivedReferenceType2 and so on.
I've found potential solution in the ModelBinder, but I don't want to bind all properties at the model.
I know that it's can be easy implemented in the MVC model binders with the call of
base.BindModel(controllerContext, bindingContext);
But how to do it in WebAPI?
Thanks!
One possibility would be to write a custom JsonConverter to handle this situation:
public class MyConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(ReferenceModel);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var result = new ReferenceModel();
serializer.Populate(jsonObject.CreateReader(), result);
result.References = new List<BaseReferenceItem>();
foreach (var obj in jsonObject.GetValue("references", StringComparison.InvariantCultureIgnoreCase))
{
if (result.Prop1 == 1)
{
result.References.Add(obj.ToObject<DerivedReferenceType1>(serializer));
}
else if (result.Prop1 == 2)
{
result.References.Add(obj.ToObject<DerivedReferenceType2>(serializer));
}
else
{
throw new NotSupportedException(result.Prop1 + " is not supported value for Prop1");
}
}
return result;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
and then you could decorate your main view model with this custom converter:
[JsonConverter(typeof(MyConverter))]
public class ReferenceModel
{
public int Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
public DateTime CurrentTime { get; set; }
public List<BaseReferenceItem> References { get; set; }
}
or if you prefer to do this without modifying your view models you could always register your custom converter during the bootstrap process:
public static void Register(HttpConfiguration config)
{
// Web API routes
...
var jsonFormatter = config.Formatters.JsonFormatter;
jsonFormatter.SerializerSettings.Converters.Add(new MyConverter());
}

json.net deserialize an object that has a dictionary property as well as other properties

I have a JSON string representing the following object type:
public partial class Address
{
public Address()
public string Country { get; set; }
public string StreetNo { get; set; }
public string City { get; set; }
public string Zip { get; set; }
public string Complement { get; set; }
public Nullable<int> Latitude { get; set; }
public Nullable<int> Longitude { get; set; }
public Nullable<int> Altitude { get; set; }
public string Url { get; set; }
public System.DateTime LastModified { get; set; }
public byte[] Version { get; set; }
public string StateCode { get; set; }
public string Street { get; set; }
public string RecordStatus { get; set; }
public int UserId { get; set; }
public int AddressId { get; set; }
public Dictionary<string, object> OriginalValues { get; set; }
}
I am trying to create a JsonConverter to deserialize the JSON string to the above object. I am stuck on what to add to my converter to create the dictionary within my code. The code for the converter thus far looks like this:
public class AddressConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
var temp = objectType == typeof(Address);
return temp;
}
public override object ReadJson(JsonReader reader, Type objectType
, object existingValue, JsonSerializer serializer)
{
var obj = JObject.Load(reader);
var model = new Address();
model.Country = Convert.ToString(((JValue)obj["Country"]).Value);
model.StreetNo = Convert.ToString(((JValue)obj["StreetNo"]).Value);
model.City = Convert.ToString(((JValue)obj["City"]).Value);
model.Zip = Convert.ToString(((JValue)obj["Zip"]).Value);
model.Complement = Convert.ToString(((JValue)obj["Complement"]).Value);
model.Latitude = Convert.ToInt32(((JValue)obj["Latitude"]).Value);
model.Longitude = Convert.ToInt32(((JValue)obj["Longitude"]).Value);
model.Altitude = Convert.ToInt32(((JValue)obj["Altitude"]).Value);
model.Url = Convert.ToString(((JValue)obj["Url"]).Value);
model.LastModified = Convert.ToDateTime(((JValue)obj["LastModified"]).Value);
model.StateCode = Convert.ToString((((JValue)obj["Country"]).Value));
model.Street = Convert.ToString(((JValue)obj["StateCode"]).Value);
model.RecordStatus = Convert.ToString(((JValue)obj["RecordStatus"]).Value);
model.UserId = Convert.ToInt32(((JValue)obj["UserId"]).Value);
model.AddressId = Convert.ToInt32(((JValue)obj["AddressId"]).Value);
var encodedString = ((JValue) obj["Version"]).Value.ToString();
model.Version = Convert.FromBase64String(encodedString);
// add Dictionary code here
return model;
}
public override bool CanWrite
{
get
{
return false;
}
}
public override void WriteJson(JsonWriter writer, object value
, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
throw new NotImplementedException();
}
}
I dont know what you are trying to acheive with this Custom convertor. But here is what you want.
// Dictionary code here
model.OriginalValues = (Dictionary<string, object>)JsonConvert.DeserializeObject(obj["OriginalValues"].ToString(), typeof(Dictionary<string, object>));

Resources