MVC3 / JSON: How do I use model binding when my property names are renamed via a DataContract? - json

I use a DataContractJsonSerializer to create a JsonResult for my model data when sending data to the client. My model represents data to be displayed in a data table, and I wished to change the name of the model's properties in the JSON only so that less verbose property names are sent over the wire for each data table row. Now, I'm attempting to send the data table cell values via JSON to the server's controller action method. The names of the fields being sent back are still the short names, and the model binding doesn't seem to like that. What can I do to get model binding working and preserve the ability to sent alternate property names via JSON?
Model:
[DataContract()]
public class UsageListModel {
[DataMember(Name = "results")]
public IEnumerable<UsageModel> Usages { get; set; }
}
[DataContract()]
public class UsageModel {
[DataMember(Name = "job")]
public string JobId { get; set; }
[DataMember(Name = "dt")]
public DateTime UsageDate { get; set; }
[DataMember(Name = "qty")]
public int Quantity { get; set; }
[DataMember(Name = "uom")]
public string UnitOfMeasure { get; set; }
[DataMember(Name = "nts")]
public string Notes { get; set; }
}

It's not as elegant but I usually do this by just making an intermediary class (I refer to it as a ViewModel) that has those shortname properties and can be translated back and forth between it and the actual Model. Although it seems like busy work, the ViewModel can be useful beyond this stint - for example you can use it to easily cache client-side info if the need arises, or serialize/deserialize exactly what's going to/from the client in tests.

I'm still in disbelief that MVC doesn't offer some easier method to bind using custom attributes (or even the .NET data-contract attributes). Given that it doesn't... your best bet is to implement your own IModelBinder. Use reflection to get the DataMember names of the properties, and look for those values in the binding context.
Here's a great reference on model binding: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx
A good general approach to maintaining custom binders: http://lostechies.com/jimmybogard/2009/03/18/a-better-model-binder/
EDIT
Generic model binder that handles a defined type. To add this to your application, add this line in global.asax:
ModelBinders.Binders.Add(typeof(UsageModel), new CustomModelBinder<UsageModel>());
And the binder:
public class CustomModelBinder<T> : IModelBinder
{
public override bool IsMatch(Type t)
{
return t == typeof(T);
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Type t = typeof(T);
var entity = (bindingContext.Model ?? Activator.CreateInstance(t));
// Cycle through the properties and assign values.
foreach (PropertyInfo p in t.GetProperties())
{
string sourceKey;
// this is what you'd do if you wanted to bind to the property name
// string sourceKey = p.Name;
// TODO bind sourceKey to the name in attribute DataMember
Type propertyType = p.PropertyType;
// now try to get the value from the context ...
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(sourceKey);
if (valueResult != null)
{
bindingContext.ModelState.SetModelValue(sourceKey, valueResult);
p.SetValue(entity, valueResult.ConvertTo(propertyType), null);
}
}
return entity;
}
}

I stumbled across a potential answer to this question randomly while browsing this other question.
I never realized this until now, but apparently you can add attributes to method parameters. Let's take a simple example:
public ActionResult SomeMethod(string val) {
return View(val);
}
If you call this URL -- /MyController/SomeMethod?val=mytestval -- then you'll get back "mytestval" in the model, right? So now you can write this:
public ActionResult SomeMethod([Bind(Prefix="alias")] string val) {
return View(val);
}
Now this URL will produce the same result: /MyController/SomeMethod?alias=mytestval.
Anyway, I'm still not sure if that will answer your question, but I thought it was very interesting.

Related

Dealing with external namespaces for a POCO data model implemented through NJsonSchema auto-generated classes

In a dotnet microservice architecture, to avoid exposing my data model and its logic, I am willing to add a POCO data model layer following a mechanism: fullDataModel>json>pocoDataModel.
Here is my reduced fullDataModel
public class MyDto
{
public string Name { get; set; }
public AnExternalNamespace.MyExternalType ExternalObject { get; set; }
}
To implement the above mechanism, I am using NJSonSchema CSharpGenerator as follow:
// generate Json schema from ConsoleApp1
JsonSchema schema = await JsonSchema.FromFileAsync("MyDto.txt");
CSharpGeneratorSettings settings = new CSharpGeneratorSettings
{
ClassStyle = CSharpClassStyle.Poco,
Namespace = "MS1Namespace",
GenerateDataAnnotations = true
};
var generator = new CSharpGenerator(schema, settings);
var modelFile = generator.GenerateFile();
// generate the C# file in a another ConsoleApp2 for testing purpose
using (StreamWriter writer = new StreamWriter("..\\ConsoleApp2\\MyDtoFromJson.cs"))
{
writer.WriteLine(modelFile);
}
Problem, the MyDto class refers to a type which comes from an external library (AnExternalNamespace.MyExternalType). But the mechanism I implemented does not preserve this type of reference, rather it generates a type bounded to each namespace associated with each microservice I use that implement this type. Have a look at the C# generated file which is bounded to MS1Namespace
namespace MS1Namespace
{
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.8.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class MyExternalType
{
[Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
[Newtonsoft.Json.JsonProperty("Description", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Description { get; set; }
}
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.8.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class MyDto
{
[Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Name { get; set; }
[Newtonsoft.Json.JsonProperty("ExternalId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public MyExternalType ExternalObject { get; set; }
}
}
In other words, with this mechanism, MyExternalType is kind of redefined relative to each MSnNamespace for every microservice (1 to n) where it is embedded. On the contrary, I would like to be able to receive objects that refer to the type defined in the AnExternalNamespace each time I send a request to these microservices. That would allow me treat the data of same type coming from these different microservices in a unified way with the unique AnExternalNamespace.MyExternalType
I have tried to play with Json annotations in MyDto class. For example by doing this:
[JsonProperty(TypeNameHandling = TypeNameHandling.Auto)]
public MyPublicLibrary.MyExternalType ExternalId { get; set; }
This does not allow to get out of the MS1Namespace scope to which all the auto-generated classes are bounded.
So, the only solution I see to overcome this for now is to implement an explicit&implicit cast in each microservice embedding this type from MSnNamespace.MyExternalType to AnExternalNamespace.MyExternalType.
But I was thinking NJSonSchema settings could offer me a more elegant way to deal with this situation. Am I missing something here? Do you see any other options or better practice while continuing keeping passing through the Json serialization/deserialization process?
Thanks in advance for your help!

Model Validation is not working in ASP.NET Core 2.0 [duplicate]

I have developed a custom validator Attribute class for checking Integer values in my model classes. But the problem is this class is not working. I have debugged my code but the breakpoint is not hit during debugging the code. Here is my code:
public class ValidateIntegerValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
int output;
var isInteger = int.TryParse(value.ToString(), out output);
if (!isInteger)
{
return new ValidationResult("Must be a Integer number");
}
}
return ValidationResult.Success;
}
}
I have also an Filter class for model validation globally in application request pipeline. Here is my code:
public class MyModelValidatorFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
return;
var errors = new Dictionary<string, string[]>();
foreach (var err in actionContext.ModelState)
{
var itemErrors = new List<string>();
foreach (var error in err.Value.Errors){
itemErrors.Add(error.Exception.Message);
}
errors.Add(err.Key, itemErrors.ToArray());
}
actionContext.Result = new OkObjectResult(new MyResponse
{
Errors = errors
});
}
}
The model class with validation is below:
public class MyModelClass
{
[ValidateIntegerValue(ErrorMessage = "{0} must be a Integer Value")]
[Required(ErrorMessage = "{0} is required")]
public int Level { get; set; }
}
Can anyone please let me know why the attribute integer validation class is not working.
Model validation comes into play after the model is deserialized from the request. If the model contains integer field Level and you send value that could not be deserialized as integer (e.g. "abc"), then model will not be even deserialized. As result, validation attribute will also not be called - there is just no model for validation.
Taking this, there is no much sense in implementing such ValidateIntegerValueAttribute. Such validation is already performed by deserializer, JSON.Net in this case. You could verify this by checking model state in controller action. ModelState.IsValid will be set to false and ModelState errors bag will contain following error:
Newtonsoft.Json.JsonReaderException: Could not convert string to
integer: abc. Path 'Level', ...
One more thing to add: for correct work of Required validation attribute, you should make the underlying property nullable. Without this, the property will be left at its default value (0) after model deserializer. Model validation has no ability to distinguish between missed value and value equal to default one. So for correct work of Required attribute make the property nullable:
public class MyModelClass
{
[Required(ErrorMessage = "{0} is required")]
public int? Level { get; set; }
}

Generic Singleton and share data between pages

To share data (complexe data ) between pages in my windows phone 8 application I want to implement a singleton, but I want it to be generic, is it possible? I suppose that it creates a new instance for each type isn't it?
public sealed class NavigationContextService<T>
{
private static readonly NavigationContextService<T> instance = new NavigationContextService<T>();
private NavigationContextService()
{
}
public static NavigationContextService<T> Instance
{
get
{
return instance;
}
}
public List<T> ShareList { get; set; }
public T ShareData { get; set; }
}
It is creating a new instance for every type, because it is generic - you want it to be like this (if you start with generics, take a look at some tutorials, blogs or MSDN - you will easily find many in the internet).
It is still a singleton. When you use
NavigationContextService<string>.Instance.ShareList.Add("Text");
then you have one Instance for type string. Generics helps a lot when you want to create same methods/classes that differ in type.
On the other hand if you want to create Singleton that will hold different types then you can for example modify your class to be non Generic like this:
public sealed class NavigationContextServiceNonGeneric
{
private static readonly NavigationContextServiceNonGeneric instance = new NavigationContextServiceNonGeneric();
private NavigationContextServiceNonGeneric() { ShareList = new List<object>(); }
public static NavigationContextServiceNonGeneric Instance
{ get { return instance; } }
public List<object> ShareList { get; set; }
public object ShareData { get; set; }
}
As you can see in the code above I haven't defined the 'exact' type of shared data - it is object type. Then you can easily hold most of data with it:
NavigationContextServiceNonGeneric.Instance.ShareList.Add("Text");
NavigationContextServiceNonGeneric.Instance.ShareList.Add(3);
NavigationContextServiceNonGeneric.Instance.ShareList.Add(3.0f);
It is singleton, which can hold different types of shared data. BUT it has also disavantages - the main is that you have to remember what type of data you hold and in what order. In my opinion Generic version is better because of that fact.
Everything depends on the purpose of your code. There may be easier and better ways that those two approaches.
As for the Page Navigation, you can for example try to use a method from this article - you extend Navigation service to pass the object:
public static class Extensions
{
private static object Data;
public static void Navigate(this NavigationService navigationService, Uri source, object data)
{
Data = data;
navigationService.Navigate(source);
}
public static object GetNavigationData(this NavigationService service) { return Data; }
}
Then you use it:
NavigationService.Navigate(yourUri, DataToPass);
After Navigation you can get your data:
string myTextData = NavigationService.GetNavigationData() as string;
This method has to disadvantages: it is not type-safe and your data won't be preserved in Tombstone mode.
As for the second disadvantage you can easily use PhoneApplicationService.State Property for the purpose of Page Navigation - it is a dictionary (which is preserved while tombstoning):
PhoneApplicationService.Current.State.Add("data", yourData);
Then when you want to get your data:
yourDataType yourData = PhoneApplicationService.Current.State["data"] as yourDataType;
There are also more ways in which you can pass the data.

WP7 ArrayAdapter equivalent

I have a JSON object returned from a WCF Service, it returns two “Article” objects and looks like this:
{"GetArticlesResult":[{"ArticleName":"Mobile Application Development","ArticleText":"Lots of text here."},{"ArticleName":"Super Fast Development Cycle","ArticleText":"Lots more text here."}]}
And I can display it in a message box with this code:
MessageBox.Show(e.Result);
What I want to do is display a list of ArticleNames in a ListBox, I will then want to link them to another page to display the ArticleText but that’s later for now.
My research has shown me that I need to use the DataContractJsonSerializer but this is where I’m stuck, all the examples seem to be a little complicated. In and Android app I would simply use an ArrayAdapter to populate something. Can I do something similar in a WP7 app?
Cheers,
Mike.
EDIT
I now have the following, how do I display the results in a ListBox?
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(e.Result));
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(Article));
//put ms into a listBox here????....
[DataContract]
public class Article
{
[DataMember()]
public string Title { get; set; }
[DataMember()]
public string Text { get; set; }
}
You need to set the ItemsSource property of the listbox to an array/list (or ObservableCollection if you're going to add more items while it's displaying). If you are using a ViewModel, you can bind the ItemsSource property to an ObservableCollection property on your VM and it will update automatically.
You can use JSON.NET (it's faster than DataContractJsonSerializer) to either deserialize the JSON to a strongly typed object model and bind that or you can use the same library to deserialize the JSON to a dynamic and then access the JSON properties from that.
If you use a strongly typed object model, you'll need to implement the changes xyzzer made to Article/ArticleList in order for it to map properly.
Maybe a List would work for you to deserialize/represent an array from a JSON structure. Otherwise - the question is too vague. Are you asking how to display a collection in a ListBox? Using an ItemsSource binding...
*EDIT
Try this:
var jsonString = "{\"GetArticlesResult\":[{\"ArticleName\":\"Mobile Application Development\",\"ArticleText\":\"Lots of text here.\"},{\"ArticleName\":\"Super Fast Development Cycle\",\"ArticleText\":\"Lots more text here.\"}]}";
var articles = Deserialize<ArticleList>(jsonString);
Debug.WriteLine(articles.GetArticlesResult.Count);
[DataContract]
public class ArticleList
{
[DataMember]
public List<Article> GetArticlesResult { get; set; }
}
[DataContract]
public class Article
{
[DataMember(Name = "ArticleName")]
public string ArticleName { get; set; }
[DataMember(Name = "ArticleText")]
public string ArticleText { get; set; }
}
public static T Deserialize<T>(string strData) where T : class
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(T));
byte[] byteArray = Encoding.UTF8.GetBytes(strData);
MemoryStream memoryStream = new MemoryStream(byteArray);
T tRet = serializer.ReadObject(memoryStream) as T;
memoryStream.Dispose();
return tRet;
}

Entity Framework 4.1 Code-First and Inserting New One-to-Many Relationships

I am having trouble peristing a new object graph to the context with a one-to-many relationship. I am using the Entity Framework 4.1 release, and implementing a Code-First approach. I am using an existing SQL 2008 database and implemented a context derived from DbContext. I have two classes, Person and Address. A person can contain 0 or more Addresses, defined as such.
public class Person
{
public Person()
{
Addresses = new List<Address>();
}
public int PersonId { get; set; }
***Additional Primitive Properties***
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int AddressTypeId { get; set; }
***Additional Primitive Properties***
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
I am trying to create a new instance of Person with two addresses. However, when I add this structure to the context and save, only the first Address in the collection is persisted. The second has the Person navigation property set to null, and is not associated with the Person object, however, the first one in the list is associated.
var person = new Person();
var mailingAddress = new Address() { AddressTypeId = 1 };
person.Addresses.Add(mailingAddress);
var billingAddress = new Address() { AddressTypeId = 2 };
person.Addresses.Add(billingAddress);
context.People.Add(entity);
context.SaveChanges();
It does not throw an exception, but the second item in the Address collection is just not saved.
Does anybody have any good ideas on why only the first would be saved? Thank you.
After hours of troubleshooting/trial and error, I've solved my problem.
My POCO classes are also used in a disconnected environment, where
the objects are detached from the context, modified, and then re-attached.
In order to determine which navigation property collection items were affected, I overrode
the Equals and GetHashCode methods in the Address class to determine equality. Apparently this affects the ability for EF 4.1 to insert a complete collection of navigation property objects???
Here are the original equality methods which caused the issue:
public override bool Equals(object obj)
{
Address address = obj as Address;
if (address == null) return false;
return address.AddressId == this.AddressId;
}
public override int GetHashCode()
{
return this.AddressId.GetHashCode();
}
In order to correct the problem, I created a custom equality comparer
for the navigation object rather than including it directly in the address class.
public class AddressEqualityComparer : IEqualityComparer<Address>
{
public bool Equals(Address address1, Address address2)
{
if (address1.AddressId == address2.AddressId)
return true;
else
return false;
}
public int GetHashCode(Address address)
{
return address.AddressId.GetHashCode();
}
}
My context.People.Add method call worked as expected after I made this change.
If anyone knows why overriding the equality methods in the class causes
EF 4.1 to only insert the first item in the collection, that would be
great information.
As hinted at already, it's because the GetHashCode method is using the ID of all the siblings, which will be 0 at point of comparison by Entity Framework. Comment just that out and you will good to go.
I had the same exact issue and this piece let me to that. I didn't even bother to look at my EntityBase code...it's so old and hasn't changed in forever until now.
So a big thank you for your research!
Here is another way to attempt to add the code. Worth a shot. This code may not be exact, I typed freehand.
var person = new Person();
person.Addresses.Add(new Address()
{
AddressTypeId = 1
}),
new Address()
{
AddressTypeId = 2
});
context.People.Add(entity);
context.SaveChanges();