Reputation: 4036
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; }
}
Upvotes: 4
Views: 2893
Reputation: 102743
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.
Upvotes: 0
Reputation: 37947
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.
Upvotes: 2
Reputation: 102743
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;
}
}
Upvotes: 0