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());
}
Related
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");
}
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.");
}
}
}
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;
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() };
}
}
I have the following JSON url. I am trying to populate my list view using the subarrays hair[] and math[] after getting them from the JSON url.
Here is the url http://pastebin.com/raw.php?i=2fzqLYXj
Inside the array "Students" there is a subarray called "Properties" and another sub array inside "Properties" called "Grades". "hair" is inside "Properties" and "math" is inside "Grades".
Here is my WebService class
public class WebService
{
public WebService ()
{
}
public async Task<Rootobject> GetStudentInfoAsync (string apiurl) {
var client = new HttpClient ();
var response = await client.GetStringAsync(string.Format(apiurl));
return JsonConvert.DeserializeObject<Rootobject>(response.ToString());
}
}
And here is my View Model
public class Property
{
public int iq { get; set; }
public string hair { get; set; }
public string glasses { get; set; }
public Grade[] Grades { get; set; }
}
public class StudentInfo
{
public string name { get; set; }
public string lastname { get; set; }
public Property[] Properties { get; set; }
public string StudentName {
get{ return String.Format ("{0}", name); }
}
//I am accessing the JSON sub arrays with the following to get the "hair" and "math" properties.
public string[] HairColors
{
get
{
return (Properties ?? Enumerable.Empty<Property>()).Select(p => p.hair).ToArray();
}
}
public string[] MathGrades
{
get
{
return (Properties ?? Enumerable.Empty<Property>()).SelectMany(p => p.Grades ?? Enumerable.Empty<Grade>()).Select(g => g.Math).ToArray();
}
}
}
public class Rootobject
{
public StudentInfo[] students { get; set; }
}
}
And here is how I populate my list view with student names which works. Notice "StudentName" was not in a subarray.
var sv = new WebService();
var es = await sv.GetStudentInfoAsync(apiurl);
Xamarin.Forms.Device.BeginInvokeOnMainThread( () => {
listView.ItemsSource = es.students;
});
var cell = new DataTemplate (typeof(TextCell));
listView.ItemTemplate = cell;
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "StudentName");
But I need to populate my list view with HairColors and MathGrades as follows but the following code does not work.
var sv = new WebService();
var es = await sv.GetStudentInfoAsync(apiurl);
Xamarin.Forms.Device.BeginInvokeOnMainThread( () => {
listView.ItemsSource = es.students;
});
var cell = new DataTemplate (typeof(TextCell));
listView.ItemTemplate = cell;
listView.ItemTemplate.SetBinding(TextCell.TextProperty, "HairColors");
listView.ItemTemplate.SetBinding (TextCell.DetailProperty, "MathGrades");
What doesn't this code work? How do I make it work?
You could make a custom converter for this:
public class HairColorsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var hairColors = (string[])value;
return string.Join(", ", hairColors);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then bind this like:
SetBinding(TextCell.TextProperty, "HairColors", BindingMode.Default, new HairColorsConverter());