MVC - Sending array with javascript + ajax to Controller - asp.net-mvc

I'm trying to send an array with coordinates to an MVC controller
I'm doing it like this (not posting all the code, only the relevant):
var coords = [];
..for loop
coords.push({ X: x, Y: y});
..end of loop
then I just do an ajax call with the following object as data
var data = {
OtherData: "SomeString",
OtherData2: 1,
Coords: coords
};
When I debug the action on the controller the other data is parsed correctly
The model I expect looks something like this
public class Model
{
public int OtherData2 { get; set; }
public string OtherData { get; set; }
public Point[] Coords { get; set; }
}
What I already tried
- Using List
- Making a class Simple Point with X and Y as properties
- sending the X and Y values as string values
- sending the X and Y values concatenated as 1 string and receiving a list of string
In the cases of the point object as an array I get a list with the same amount of points but they're all empty (0,0) with the List object the list is just null
any idea?
Maybe an important note is that I'm using MVC4

looks like the DefaultModelBinder doesn't know how to bind to a List,
(it's missing the proper type converter)
what you can do is create your own point and a type converter to it:
[TypeConverter(typeof(PointTypeConverter))]
public class Point
{
public int X { get; set; }
public int Y { get; set; }
}
/// <summary>
/// we need this so we can use the DefaultModelBinder to bind to List<Point>
/// example at http://msdn.microsoft.com/en-us/library/ayybcxe5.aspx
/// </summary>
public class PointTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return true;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
JavaScriptSerializer serializer = new JavaScriptSerializer();
Point ret = serializer.Deserialize<Point>((string)value);
return ret;
}
}
the controller action will look like this:
[HttpPost]
public ActionResult testPoints(List<Point> cords)
{
//party
}
and the Ajax call like this:
$.ajax({
url: "/Home/testPoints/",
type: "POST",
data: {
//note that i stringify very value in the array by itself
cords: [JSON.stringify({'X':1,'Y':2}),JSON.stringify({'X':3,'Y':4})]
},
success: function (data)
{
//client party
}
});
tested all this in MVC3, and there is no reason it won't work in MVC4
hope this helps

Related

How to code a Polymorphic Model Binder and Provider in MVC 6

This question has been asked before on SO and elsewhere in the context of MVC3 and there are bits and bobs about it related to ASP.NET Core RC1 and RC2 but niot a single example that actually shows how to do it the right way in MVC 6.
There are the following classes
public abstract class BankAccountTransactionModel {
public long Id { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
public readonly string ModelType;
public BankAccountTransactionModel(string modelType) {
this.ModelType = modelType;
}
}
public class BankAccountTransactionModel1 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel1():
base(nameof(BankAccountTransactionModel1)) {}
}
public class BankAccountTransactionModel2 : BankAccountTransactionModel{
public bool IsPending { get; set; }
public BankAccountTransactionModel2():
base(nameof(BankAccountTransactionModel2)) {}
}
In my controller I have something like this
[Route(".../api/[controller]")]
public class BankAccountTransactionsController : ApiBaseController
{
[HttpPost]
public IActionResult Post(BankAccountTransactionModel model) {
try {
if (model == null || !ModelState.IsValid) {
// failed to bind the model
return BadRequest(ModelState);
}
this.bankAccountTransactionRepository.SaveTransaction(model);
return this.CreatedAtRoute(ROUTE_NAME_GET_ITEM, new { id = model.Id }, model);
} catch (Exception e) {
this.logger.LogError(LoggingEvents.POST_ITEM, e, string.Empty, null);
return StatusCode(500);
}
}
}
My client may post either BankAccountTransactionModel1 or BankAccountTransactionModel2 and I would like to use a custom model binder to determine which concrete model to bind based on the value in the property ModelType which is defined on the abstract base class BankAccountTransactionModel.
Thus I have done the following
1) Coded up a simple Model Binder Provider that checks that the type is BankAccountTransactionModel. If this is the case then an instance of BankAccountTransactionModelBinder is returned.
public class BankAccountTransactionModelBinderProvider : IModelBinderProvider {
public IModelBinder GetBinder(ModelBinderProviderContext context) {
if (context == null) throw new ArgumentNullException(nameof(context));
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) {
var type1 = context.Metadata.ModelType;
var type2 = typeof(BankAccountTransactionModel);
// some other code here?
// tried this but not sure what to do with it!
foreach (var property in context.Metadata.Properties) {
propertyBinders.Add(property, context.CreateBinder(property));
}
if (type1 == type2) {
return new BankAccountTransactionModelBinder(propertyBinders);
}
}
return null;
}
}
2) Coded up the BankAccountTransactionModel
public class BankAccountTransactionModelBinder : IModelBinder {
private readonly IDictionary<ModelMetadata, IModelBinder> _propertyBinders;
public BankAccountTransactionModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders){
this._propertyBinders = propertyBinders;
}
public Task BindModelAsync(ModelBindingContext bindingContext) {
if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext));
// I would like to be able to read the value of the property
// ModelType like this or in some way...
// This does not work and typeValue is...
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
// then once I know whether it is a Model1 or Model2 I would like to
// instantiate one and get the values from the body of the Http
// request into the properties of the instance
var model = Activator.CreateInstance(type);
// read the body of the request in some way and set the
// properties of model
var key = some key?
var result = ModelBindingResult.Success(key, model);
// Job done
return Task.FromResult(result);
}
}
3) Lastly I register the provider in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options => {
options.ModelBinderProviders.Insert(0, new BankAccountTransactionModelBinderProvider());
options.Filters.Add(typeof (SetUserContextAttribute));
});
The whole thing seems OK in that the provider is actually invoked and the same is the case for the model builder. However, I cannot seem to get anywhere with coding the logic in BindModelAsync of the model binder.
As already stated by the comments in the code, all that I'd like to do in my model binder is to read from the body of the http request and in particular the value of ModelType in my JSON. Then on the bases of that I'd like to instantiate either BankAccountTransactionModel1 or BankAccountTransactionModel and finally assign values to the property of this instance by reading them of the JSON in the body.
I know that this is a only a gross approximation of how it should be done but I would greatly appreciate some help and perhaps example of how this could or has been done.
I have come across examples where the line of code below in the ModelBinder
var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
is supposed to read the value. However, it does not work in my model binder and typeValue is always something like below
typeValue
{}
Culture: {}
FirstValue: null
Length: 0
Values: {}
Results View: Expanding the Results View will enumerate the IEnumerable
I have also noticed that
bindingContext.ValueProvider
Count = 2
[0]: {Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProvider}
[1]: {Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider}
Which probably means that as it is I do not stand a chance to read anything from the body.
Do I perhaps need a "formatter" in the mix in order to get desired result?
Does a reference implementation for a similar custom model binder already exist somewhere so that I can simply use it, perhaps with some simple mods?
Thank you.

Null Reference Error when directly assigning a List object to an PagedList

KeyMaster Model
public class KeyMasterModel
{
public int KeyId { get; set; }
public int TypeId { get; set; }
public string TypeName { get; set;}
}
PagedKeyModel
public class PagedKeyModel
{
// Collection of our KeyMasterModel , trying to populate this and send it to ViewModel to display a Grid//
public IPagedList<Security.Models.KeyMasterModel> pagedkeymaster;
}
//ViewModel//
public class KeyMasterViewModel
{
//tell me how to initialise the KeyMasterViewModel.pagedkeymodel.pagedkeymaster in a constructor here so that I dont get a null
public PagedKeyModel pagedkeymodel;
public KeyMasterModel keymastermodel;
}
//Controller//
[HttpGet]
public ActionResult ListOfKey(string sortOrder, string CurrentSort, int? page)
{
// View Model Object //
KeyMasterViewModel keyMasterViewModel = new KeyMasterViewModel();
// At the gollowing step keyMasterModelObject has values retrieved from db //
//Next aim is to place it into ViewModel Object by assigning the retrieved PagedList of KeyMasterModel to IPagedList<Security.Models.KeyMasterModel> pagedkeymaster
IPagedList<KeyMasterModel> KeyMasterModelObject= datalayercall.GetAll(sortOrder, CurrentSort, page);
// Here is where error is thrown , all of a sudden I get the error , KeyMasterModelObject becomes null .
// I am trying finally to populate everything into ViewModel object
keyMasterViewModel .pagedkeymodel.pagedkeymaster = KeyMasterModelObject;
return View(keyMasterViewModel);
}
// Business Logic Layer
public IPagedList<KeyMasterModel> GetAll(string sortOrder, string CurrentSort, int? page)
{
var retrieveddatalayerobject = datalayerobject.KeyMasters;
// Retrieving Data from db and forming a list according to my model in App//
List<KeyMasterModel> keymastermodellist = new List<KeyMasterModel>();
KeyMasterModel keymastermodelobject=new KeyMasterModel();
foreach(var retrieveditems in retrieveddatalayerobject)
{
keymastermodelobject.KeyId = retrieveditems.KeyId;
keymastermodelobject.TypeName = retrieveditems.TypeMaster.TypeName;
// Create a New List Of KeyMasterModel //
keymastermodellist.Add(keymastermodelobject);
}
// Paged List of KeyMaster Model //
IPagedList<KeyMasterModel> IPagedListKeyMasterModel = null;
switch (sortOrder)
{
case "KeyId":
if (sortOrder.Equals(CurrentSort))
IPagedListKeyMasterModel = keymastermodellist.OrderByDescending
(m => m.KeyId).ToPagedList(pageIndex, pageSize);
}
return IPagedListKeyMasterModel;
}
Your code as I understand it:
KeyMasterViewModel keyMasterViewModel = new KeyMasterViewModel();
[...]
keyMasterViewModel.pagedkeymodel.pagedkeymaster = KeyMasterModelObject;
So here you are using keyMasterViewModel.pagedkeymodel and assuming it is not null (because you are trying to assign to a property on it). However KeyMasterViewModel doesn't have a constructor and doesn't set pagedkeymodel to anything at declaration so it will default to being null. Hence your problem.

Passing JSON to WEB API Controller

I have this model for an MVC WEB API controller.
What will be the corresponding JSON to match this model structure?
namespace CarEvaluator.Models
{
[DataContract]
public class Record
{
[DataMember]
public List<Boolean> CHits { get; set; }
[DataMember]
public List<Boolean> MHits { get; set; }
}
}
public void Post(Record record)
{
}
Structure:
{"CHits":[true,false],"MHits":[true,false]}
Example:
var postObject = new Object();
// Initialize CHits and MHits as arrays
postObject.CHits = [];
postObject.MHits = [];
// push some items into the arrays
postObject.CHits.push(true);
postObject.CHits.push(false);
postObject.MHits.push(true);
postObject.MHits.push(false);
// serialize data to post
// this is what you set as data property in for instance jquery ajax
$.ajax({
//other params, content type etc
type: 'POST',
data: JSON.stringify(postObject),
...
});
if your parameter is null, you should try to add the [FromBody] attribute and decorate the method with httppost
[HttpPost]
public void Post([FromBody]Record record)
{
}

How to pass a ko.observablearray via JSON to an MVC controller

I'm using Knockout JS to build a model to pass to an MVC controller. The ko.observable() items are passed to the controller no problem, however, the ko.observableArray([]) data is appearing as "count=0" at the controller.
Below is the object I am building in my View:
var AddViewModel = function () {
self.ModelRequest = {
Object: {
VarArray: ko.observableArray([]),
Var1: ko.observable(""),
Var2: ko.observable("")
}
};
....
The ModelRequest.Object.VarArray is an ko.observableArray contains a few attributes in the object: Name, Id, Code, Type.
Below is how I'm sending the data via JSON:
p = ko.toJSON(AddViewModel.ModelRequest);
debugger;
$.ajax({
url: url,
type: 'POST',
contentType: 'application/json; charset=utf-8',
data: ko.toJSON(AddViewModel.ModelRequest),
success: function (data) {
...something...
}
});
When I am debugging the code, I examine the p variable described above and I see the below:
{"Object":{"VarArray":[{"Name":"Name 1", "Id":2, "Code":"50.1", "Type":"A"}],
"Var1":"abc", "Var2":"def"}}
When I examine the object being passed into the controller, Var1 and Var2 have the correct values, however, the VarArray is "Count=0".
Any thoughts? Thanks for taking the time to look at this. I'll try any ideas at this point!!
EDIT 10/6/13:
This is my controller action:
[HttpPost]
public CRUDResponse AddItem(AddRequest ModelRequest)
{
... something here ...
}
At this point when I examine the ModelRequest I see that VarArray is "Count = 0".
Edit 10/8/13:
This is the details of the AddRequest:
#region Public Members
public ObjectType Object { get; set; }
#endregion Public Members
Where the ObjectType is:
#region Public Members
public int Var1 { get; set; }
public int Var2 { get; set; }
public List<SpecType> VarArray { get; set; }
#endregion Public Members
Where the SpecType is
public string Name { get; set; }
public int Id { get; set; }
public string Code { get; set; }
public FieldType Type { get; protected set; }
And the FieldType is a Enum.
UPDATE: I had just found the problem. It looks like the property is not getting serialized properly through JSON when I make a call to my Web API from the UI. The above-mentioned property is of type TypaA which inherits from TypeB. TypeB contains all of the fields needed by TypeA. When I change the property failing to serialize to be of type TypeB, instead of TypeA, it serializes just fine and I get all of the values I need reflected in Web API.
So, basically, JSON fails to serialize a value if it's type is derived from another type. Removing the inheritance by declaring a value to be of base type fixes the issue.
So, is there a way to serialize a property whose type inherits from another class?
Eric
I think the problem is that either A: you are never populating the observableArray, or B: you are not receiving the proper object type back on the controller, either because you are sending it incorrectly or receiving it improperly.
Try doing this -
function testData(name) {
var self = this;
self.Name = ko.observable(name);
}
inside of your view model
var AddViewModel = function () {
self.ModelRequest = {
Object: {
varArray: ko.observableArray([
new testData('Your my boy blue'),
new testData('Frank the tank')
]),
var1: ko.observable(""),
var2: ko.observable("")
}
};
}
And see if your controller action is actually getting your data back.
If not then you are most likely not matching the object you are sending to the controller with an object the controller recognizes.

Knockout and MVC3: Posting JSON to action, serializing twice? Can't convert to C# objects?

I've got a Knockout Model that gets posted via a save method:
self.save = function(form) {
ko.utils.postJson($("form")[0], self);
};
I check the request to make sure all the data is properly being posted (it is):
However, when I get to my action:
[HttpPost]
public ActionResult Create(EquipmentCreateModel equipmentCreateModel)
{
/stuff here
}
BuildingCode and Room contain escaped quotes, and identifiers is totally not null but has a count of 0:
And my ModelState is not valid, there is one error, for the Identifiers property which has an attempted value of :
and the Exception message is:
"The parameter conversion from type 'System.String' to type 'System.Collections.Generic.KeyValuePair`2[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]' failed because no type converter can convert between these types."
My Model:
public class EquipmentCreateModel
{
//used to populate form drop downs
public ICollection<Building> Buildings { get; set; }
public ICollection<IdentifierType> IdentifierTypes { get; set; }
[Required]
[Display(Name = "Building")]
public string BuildingCode { get; set; }
[Required]
public string Room { get; set; }
[Required]
[Range(1, 100, ErrorMessage = "You must add at least one identifier.")]
public int IdentifiersCount { get; set; } //used as a hidden field to validate the list
public string IdentifierValue { get; set; } //used only for knockout viewmodel binding
public IDictionary<Guid, string> Identifiers { get; set; }
}
Now first I thought it was a problem with knockout, but then I found out the data wasn't being posted in the request correctly. I fixed that and still had the same problem. I thought MVC3 automatically converts Json now? Why are my simple properties appearing in escaped quotes and why can't my identities collection properly populate from the posted data?
Try this:
[HttpPost]
public ActionResult Create([FromJson] EquipmentCreateModel equipmentCreateModel)
{
//stuff here
}
where FromJson is:
public class FromJsonAttribute : CustomModelBinderAttribute
{
private readonly static JavaScriptSerializer serializer = new JavaScriptSerializer();
public override IModelBinder GetBinder()
{
return new JsonModelBinder();
}
private class JsonModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var stringified = controllerContext.HttpContext.Request[bindingContext.ModelName];
if (string.IsNullOrEmpty(stringified))
return null;
return serializer.Deserialize(stringified, bindingContext.ModelType);
}
}
}
This is taken from:
http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/
you should check the comments to as there are some modification for the FromJsonAttribute.
If you are using MVC3 you don't need to add JsonValueProviderFactory. For those of us who are still on MVC2 you can add JsonValueProviderFactory manually
http://haacked.com/archive/2010/04/15/sending-json-to-an-asp-net-mvc-action-method-argument.aspx
However JsonValueProvider only works with AJAX post. For the full postback it needs the extra processing. There's a thread describing how to handle full postback: groups.google.com/d/topic/knockoutjs/3FEpocpApA4/discussion
The easiest solution would be to use AJAX post. Change
ko.utils.postJson($("form")[0], self);
to
$.ajax({
url: $("form").action,
type: 'post',
data: ko.toJSON(self),
contentType: 'application/json',
success: function (result) {
alert(result);
}
});
You could try:
[HttpPost]
public ActionResult Create(string equipmentCreateModelString)
{
var equipmentCreateModel = JsonConvert.DeserializeObject<EquipmentCreateModel> equipmentCreateModelString, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore });
}
Otherwise you need to use a JsonValueProviderFactory. Here's an example
#DOTang, i have another approach. First, you need a clean js object from your view model. You can get it calling: ko.mapping.toJS(self), then pass your view model to postJson function as a property. Finally add [FromJson] attribute to your controller. Your controller argument name, must be equal to your js property name, in this case: model.
I hope it works for you as works for me.
server
[HttpPost]
public ActionResult RegisterUser([FromJson] EquipmentCreateModel model)
{
//...
}
client
self.save = function() {
var jsModel = ko.mapping.toJS(self);
ko.utils.postJson('#Url.Action("Create", "Equipment")', { model : jsModel });
}

Resources