Binding MVC model in controller which has a complex array - javascript

Most of my model is populated using $('form').serializeArray(), however an array of objects that form a paged grid need to be populated from its manager.
For example:
public JsonResult SubmitForm(MyViewModel input)
{
...
public class MyViewModel
{
[Display(Name = "Name")]
public string GridName { get; set; }
[Display(Name = "Description")]
public string GridDescription { get; set; }
public GridRow[] GridRows { get; set; }
The name and description would be picked up by serializeArray(), no issues there. If the GridRow is a string[], then it accepts me simply pushing multiple instances to it into the serialized array that jquery made:
var data = $('form').serializeArray();
for (var i in gridData) {
data.push({ name: 'GridRows', value: gridData[i].id });
}
$.ajax({
type: "POST",
url: '/Central/Results/SubmitForm',
dataType: "json",
data: data,
This way I can at least get an array of the IDs. However, it does not allow me to push the entire object into it (gridData[i]) when I want to populate the proper data type. I always get a null value when it reaches the controller.
Any idea how I need to handle the data in order for MVC to populate the model correctly? Thanks.

I'm pretty sure this is related to having to set the traditional option to true in your Ajax post. jQuery handles arrays a little differently than you'd expect, in terms of when they are posted to MVC controller actions.
So do this:
$.ajax({
type: "POST",
url: '/Central/Results/SubmitForm',
dataType: "json",
traditional: true,
data: data,
...
See this answer for more details.

Turns out just need to add a line and property reference, and add each variable separately.
for (var i in gridData) {
for (var j in gridData[i]) {
data.push({ name: 'GridRows[' + i + '].' + j, value: gridData[i][j] });
}
}
Edit: Just thought I'd post my updated helper method I wrote a while ago for this.
function serializeArray(obj, data, name) {
/// <summary>Turns an object into an MVC standard array of values </summary>
/// <param name="obj" type="Object">Object to serialise (required)</param>
/// <param name="data" type="Array">Array to append data to end of (optional)</param>
/// <param name="name" type="String">Name prefix for all entries (optional/ignore)</param>
if (!data || !(data instanceof Array)) {
data = [];
}
if (obj instanceof Array) {
if (!name) {
throw "Need an object name when converting an array";
}
for (var index = 0; index < obj.length; index++) {
data = serializeArray(obj[index], data, name + '[' + index + ']');
}
} else if (obj instanceof Object) {
for (var property in obj) {
if (obj.hasOwnProperty(property)) {
data = serializeArray(obj[property], data, name ? (name + '.' + property) : property);
}
}
} else {
data.push({ name: name, value: obj });
}
return data;
}

Related

passing an array from ajax call to controller, array empty in controller

Can anyone suggest what I need to change here?
I'm getting classes from elements that contain the class 'changed', the classes I get contain id's that I want to pass to the controller method.
When I look in the network tab I can see the data I want in the payload, it looks like this:
{"list":["GroupId-1","SubGroupId-2","changed"]}
but when I put a breakpoint on the controller the list is null.
This is the class I'm expecting in the controller method:
public class MemberGroups
{
public string GroupId { get; set; }
public string SubGrouId { get; set; }
public string Status { get; set; }
}
javascript for the save
function Savechanges() {
var spans = document.getElementsByClassName("changed");
var list = [];
$.each(spans,
function (key, value) {
$.each(value.classList,
function (key, value) {
list.push(value);
});
});
var dataToPost = JSON.stringify({ list: list });
$.ajax({
url: "/umbraco/Api/OrganisationMemberGroupsDashboardApi/UpdateMemberToGroup",
data: JSON.stringify({ list }),
type: "POST",
contentType: "application/json; charset=utf-8", // specify the content type
})
.done(function (data) {
});
}
controller
public string UpdateMemberToGroup( List<MemberGroups> list)
{
// save records
}
The spans are created dynamically and added to a treeview. When they are dragged and dropped all classes are removed then the 'changed' class is added along with the id classes so I only pass the ones I need to to the controller
var s = document.createElement('span');
s.classList.add('node-facility');
s.classList.add('ui-droppable');
s.classList.add('GroupId-' + value.GroupId);
s.classList.add('SubGroupId-0');
s.id=('GroupId-' + value.GroupId);
s.appendChild(document.createTextNode(value.GroupName));
This variant was tested using postman body json -
["GroupId-1","SubGroupId-2","changed"]
Change your ajax data to this:
data: list,
and your controller action:
public string UpdateMemberToGroup([FromBody] []string list)
{
var memberGroups = new MemberGroups
{
GroupId =list[0],
SubGrouId =list[1],
Status =list[2]
};
// save records
}
This variant was tested in postman using
{"GroupId":"GroupId-1","SubGroupId": "SubGroupId-2", "Status":"changed"}
you can put the code in javascript:
var data={GroupId:list[0],SubGroupId:list[1], Status:list[2]}
......
....
data:data,
.....
your controler action in this case:
public string UpdateMemberToGroup([FromBody] MemberGroups memberGroups)
{
// save records
}
And I don't know what version MVC you use , but for some versions instead of [FromBody] better to use [FromForm] or don't use anything at all.

Get all json key and value from an external json file in MVC with javascript

I am creating a web app in which I have a Json file in which I have many keys with values, like the following,
{
"Login_Header_Text": "Login",
"Login_Header_Recent_Updates": "Recent Updates",
"Login_TextBox_UserName": "User Name",
"Login_TextBox_Password": "Password",
"Login_Button_Login": "Log In",
"Login_ErrorMessage_Usernamerequired": "User name required",
"Login_ErrorMessage_Passwordrequired": "Password required.",
"Login_ErrorMessage_Invalid_Credentials": "Invalid user name/password",
}
and I can retrieve the values like the following
<script>
console.log('#HttpContext.GetGlobalResourceObject("", "Login_TextBox_UserName")');
</script>
now, how can I retrieve whole json file data and print that into my console,
Like, if I have 55 Records in the json file, whole data should be printed in console.log
what is the proper way to do this?
here is how i would do it. in C# and also in javascript
assum we have this js
var json = [{Name: "test", Passowrd:"test" }]
in C# i would convert this to class
public class myjson{
public string Name { get; set;}
public string Password { get; set;}
}
then with reflection call on the property
public GetValue(this myjson o, string propertyName){
return o.GetType().GetProperty(propertyName).GetValue(o);
}
in Jsvascript i would just call this
var value = json[0][property]
I hop this could help you
hello I think this can help you, I made a small example by picking up an answer written in Ajax and writing it in the console.
Look at the function in success, I think that's what you're looking for
function formToJSON(form) {
var obj = {};
var elements = form.querySelectorAll("input, select, textarea");
for (var i = 0; i < elements.length; ++i) {
var element = elements[i];
var name = element.name;
var value = element.value;
if (name) {
obj[name] = value;
}
}
return obj;
}
function test(id, url, method) {
var data = formToJSON(document.getElementById(id));
$.ajax({
type: method,
url: url,
data: data,
success: function (output, status, xhr) {
var response = JSON.parse(xhr.responseText);//<--- here is your JSON
for (var item in response) { // and set for to print indivual
console.log(item+' '+response[item]);
}
console.log(response);
},
cache: false
});
}

Kendo UI hidecolumn/showcolumn performance with many columns

I am trying to hide and show columns in my grid using Kendo UI hidecolumn and showcolumn methods, it works however it looks like the grid lags in showing and hiding column when there are many columns in grid. I have about 50 columns in my grid. Also after hiding the columns, the column doesn't realign in the grid even after using refresh method of the grid.
Does anyone know what's an alternative way to hide/show column without loosing the performance in the grid?
Thanks.
Sanjeev
Well, first of all, you just need to know, that columns in grid is nothing more than usual array. So you can create this array separately and assign into grid configuration.
I done it for configuration of whole grid but I try show only columns part, because example will be relative long because I will try show all important methods. Anyway I hope it helps you.
So process can be (but don't have to) be something like this:
Prepare database table. If you need save only visible/hid value, you can be satisfied with basic table like this:
CREATE TABLE dbo.TableSettings(
Id INT PRIMARY KEY IDENTITY,
[Key] VARCHAR(MAX) NOT NULL,
Name VARCHAR(MAX) NULL,
Value BIT NULL );
In my case table has more columns but for save this information this is enough. [Key] is table name in my case (dbo.[User] for example), Name is name of column (FirstName for example) and Value is Visible/hid (1/0).
Now you need to load columns settings from database and save it into array witch will be assigned into grid config.
RunSynchronousAjax(address, null, function (e) {
var options = JSON.parse(e.Config);
var columns = options.Columns;
//This part is needed only if you have some special cases which are prepared on server. It is because on server you cannot assign function like in javascript but you have to assign only name of function which need to be eval in javascript.
for (var i = 0; i < options.columns.length; i++) {
options.columns[i].filterable.ui = eval(options.columns[i].filterable.ui);
if (options.columns[i].filterable.itemTemplate != undefined) {
options.columns[i].filterable.itemTemplate = eval(options.columns[i].filterable.itemTemplate);
}
}
}, defaultErrorCallBack);
Note: RunSynchronousAjax is my helper method for processign ajax request because I didn't want write it everztime I need it. It looks like this:
function RunSynchronousAjax(url, data, successCallback, errorCallback) {
jQuery.ajax({
contentType: 'application/json; charset=utf-8',
url: url,
data: data,
type: "POST",
cache: false,
success: function (json) {
successCallback(json);
},
error: function (data) {
errorCallback(data);
},
async: false
});
}
function defaultErrorCallback(data) {
alert("Unexpected error. Please, try press CTRL+F5.");
}
This function is way how I load config for table, now let's look on method in controller.
public JsonResult GetSetting()
{
KendoGrid grid = new KendoGrid();
grid.PrepareColumns();
string json = JsonConvert.SerializeObject(grid);
return Json(new
{
Config = json
});
}
Objects looks like this
public class KendoGrid
{
...
private List<Column> _Columns = new List<Column>();
[JsonProperty("columns")]
public List<Column> Columns
{
get { return this._Columns; }
set { this._Columns = value; }
}
...
}
public class Column
{
[JsonProperty("field")]
public string Field { get; set; }
[JsonProperty("title")]
public string Title { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
[JsonProperty("width")]
public string Width { get; set; }
[JsonProperty("filterable")]
public object Filterable { get; set; }
[JsonProperty("template")]
public string Template { get; set; }
[JsonProperty("order")]
public int Order { get; set; }
[JsonProperty("format")]
public string Format { get; set; }
.... and more ... just what you need
public Column(
string field,
string title = null,
string type = "string",
string width = null,
string template = null,
object filterable = null,
string format = null,
...)
{
this.Field = field;
this.Title = title ?? field;
this.Type = type;
this.Width = width;
this.Template = template;
this.Order = order;
this.Format = format;
....
}
In point 3 there is method PrepareColumns, it looks like this
public void PrepareColumns()
{
var listOfColumns = TableSettings.GetColumns("dbo.TableName");
this.Columns.AddNotNull(Column.GetColumn<ObjectWhichIsDisplayedInGrid>(x => x.Id, listOfColumns,
type: "number",
width: "140px",
...));
this.Columns.AddNotNull(Column.GetColumn<ObjectWhichIsDisplayedInGrid>(x => x.Name, listOfColumns,
width: "250px",
...));
.....
}
//This method is based on you, It just load records from db...
public static List<TableSettings> GetColumns(string key)
{
List<TableSettings> result = new List<TableSettings>();
var query = Sql.Builder
.Select("*")
.From("dbo.TableSettings")
.Where("[Key] = #0", key)
.OrderBy("OrderInTable");
using (IDatabase db = DbConnection.Connect())
{
result = db.Fetch<TableSettings>(query);
}
return result;
}
public static Column GetColumn<T>(
Expression<Func<T, object>> expression,
List<TableSettings> settings,
string type = "string",
string width = null,
string template = null,
...)
{
var fieldName = PropertyNameHelper.PropertyName<T>(expression);
var fieldDescription = PropertyNameHelper.PropertyDescription<T>(expression);
var column = settings.Where(c => c.Name.Replace(" ", string.Empty).ToLower() == fieldName.Replace(" ", string.Empty).ToLower()).FirstOrDefault();
Column col = new Column(
field: fieldName,
title: fieldDescription,
type: type,
....);
return col;
}
public static string PropertyName<T>(Expression<Func<T, object>> expression)
{
return GetPropertyName(expression);
}
public static string PropertyDescription<T>(Expression<Func<T, object>> expression)
{
var propertyName = GetPropertyName(expression);
PropertyInfo prop = typeof(T).GetProperty(propertyName);
if (prop.IsDefined(typeof(DisplayAttribute), false))
{
return (prop.GetCustomAttribute(typeof(DisplayAttribute), false) as DisplayAttribute).Name;
}
return propertyName;
}
That's it. First 5 points is about load columns configuration from database. What is saved as not visible, will not be loaded so kendo grid will know only about columns which are visible.
Now, I have talked about my own columnMenu. It is only usual popup window with options. Important is that grid has set columnMenu to false, so default functionality is off. I have added button into toolbar and click event has opened kendoWindow where I had checkboxes with columns names. I can check what I want to display and after popup is closed/confirmed, some ajax request is called - it is saved into database and grid is refreshed.
Anyway mainly it can gives you idea, I believe there is much more solutions for this. If you find way how to create columns array dynamically you will win.

How can I prevent passing in multiple parameters to controller?

Right now I have a form that contains 6 security questions and 6 security answers. I've been trying to refactor my code and I'm running into an interesting situation that I'm not sure on how to proceed.
Here's my view:
var RequestSecurityQuestions_Submit = function () {
ValidationAttribute.BlankValue(true);
var form = $('form#RequestSecurityQuestions');
$.validator.unobtrusive.parse(form);
var d = $('form#RequestSecurityQuestions').serialize();
SecurityQuestionsValid = true;
var inputs = $('form#RequestSecurityQuestions').find('input[data-val]');
$.each(inputs, function (index) {
var input = inputs[index];
if (!$(input).valid()) {
SecurityQuestionsValid = false;
}
});
var dataObject = {}, dropdowns = $("input.customdropdownlist");
for (var i = 0; i < 6; i++) {
dataObject['question' + i] = $(dropdowns[i]).data("kendoDropDownList").value()
}
for (var i = 1; i < 7; i++) {
dataObject['answer' + i] = $('#idAnswer' + i).val();
}
var dataToPass = JSON.stringify(dataObject);
if (SecurityQuestionsValid) {
$.ajax({
url: Url.getFullUrl('Account/RequestSecurityQuestions_Submit'),
type: 'Post',
data: { securityInfo: dataToPass },
dataType: 'json',
cache: false,
success: function (data) {
//Next Dialog
},
error: AjaxLog.HandleAjaxCallFail
});
}
return SecurityQuestionsValid;
}
I get a dataObject which contains all the values from my view and I want to pass it to the controller.
Currently this works:
[AllowAnonymous]
[HttpPost]
public ActionResult RequestSecurityQuestions_Submit(string answer1, string answer2, string answer3, string answer4, string answer5, string answer6, string question1, string question2, string question3, string question4, string question5, string question6)
{
SecurityQuestions securityQuestions = new SecurityQuestions();
if (!string.IsNullOrEmpty(answer1))
{
securityQuestions.ChallengeA1 = answer1;
}
if (!string.IsNullOrEmpty(answer2))
{
securityQuestions.ChallengeA2 = answer2;
}
if (!string.IsNullOrEmpty(answer3))
{
securityQuestions.ChallengeA3 = answer3;
}
//etc....
}
However, I am passing in 12 parameters to my controller which sounds like a no-no to me. Is there another way to pass in my data from my view to my controller without having to pass in 12 parameters?
EDIT:
New controller attempt:
/*Problem: securityInfo array looks like: ""{\"question0\":\"2\",\"question1\":\"3\",\"question2\":\"4\",\"question3\":\"5‌​\",\"question4\":\"7\",\"question5\":\"1\",\"answer1\":\"fgfg\",\"answer2\":\"fgf‌​gf\",\"answer3\":\"fgfg\",\"answer4\":\"fgfgfg\",\"answer5\":\"fgfg\",\"answer6\"‌​:\"fggf\"}"" */
[AllowAnonymous]
[HttpPost]
public ActionResult RequestSecurityQuestions_Submit(string[] securityInfo)
{
SecurityQuestions securityQuestions = new SecurityQuestions();
}
There are multiple ways to achieve this of course, the following is one of them. A better one would be to restructure your code as per other answers/comments.
// Creating your array
var dataObject = [],
dropdowns = $("input.customdropdownlist");
//Populate your array. [0-5] will be questions and [6-11] will be answers .
for (var i = 0; i < 6; i++) {
if (i < 6){
dataObject[i] = $(dropdowns[i]).data("kendoDropDownList").value()
}
else {
var d = i - 5;
dataObject[i] = $('#idAnswer' + d).val();
}
}
// Your AJAX call with contentType
$.ajax({
url: Url.getFullUrl('Account/RequestSecurityQuestions_Submit'),
type: 'Post',
data: JSON.Stringify(dataObject), //Change data format to JSON array, will be received as array in backend
contentType: "application/json",
cache: false,
success: function (data) {
//Next Dialog
},
error: AjaxLog.HandleAjaxCallFail
});
And simply receive the array in your backend
public ActionResult RequestSecurityQuestions_Submit(List<String> data)
{
//Your code here
}
Mate, Well, I don't understand why you have N parameters which has the same type of data--- that's really where the class should come for.
Short Answer will be you can create a Model which contains these parameters, then on your controller, just RequestSecurityQuestions_Submit(Model postedModel)
then access your parameters inside like postedModel.parameter1 ... In you ajax call , it looks like
$.ajax({
url: Url.getFullUrl('Account/RequestSecurityQuestions_Submit'),
type: 'Post',
data: {parameter1:'',parameter2:''...},
cache: false,
success: function (data) {
//Next Dialog
},
Multiple parameter(n>3) on any function is bad in theory and practice,
You can simplify all this by correctly using a model, binding to a model and posting back you model.
View models
public class SecurityAnswerVM
{
[Required(ErrorMessage="Please select a question")]
[Display(Name = "Question")]
public int? QuestionID { get; set; }
[Required(ErrorMessage = "Please enter an answer")]
public string Answer { get; set; }
}
public class SecurityLoginVM
{
public SelectList QuestionList { get; set; }
public List<SecurityAnswerVM> SelectedQuestions { get; set; }
}
Controller
[HttpGet]
public ActionResult Index()
{
SecurityLoginVM model = new SecurityLoginVM();
// Populate SelectedQuestions and QuestionList from the database
return View(model);
}
[HttpPost]
public ActionResult Index(SecurityLoginVM model)
{
// model.SelectedQuestions contains the 6 objects containing the QuestionID and the users Answer
....
}
EditorTemplate (/Views/Shared/EditorTemplates/SecurityAnswerVM.cshtml)
#model yourAssembly.SecurityAnswerVM
#Html.LabelFor(m => m.QuestionID)
#Html.DropDownListFor(m => m.QuestionID, (SelectList)ViewData["options"], "Please select", new { #class = "question" })
#Html.ValidationMessageFor(m => m.QuestionID)
#Html.LabelFor(m => m.Answer)
#Html.TextAreaFor(m => m.Answer)
#Html.ValidationMessageFor(m => m.Answer)
Main view
#model yourAssembly.SecurityLoginVM
#using(Html.BeginForm())
{
#Html.EditorFor(m => m.SelectedQuestions, new { options = Model.QuestionList })
<button id="save type="submit">Save</button> // or type="button" is posting via ajax
}
And if you want to use ajax to post the data
var url = '#Url.Action("Index")';
$('#save').click(function() {
$.post(url, $('form').serialize(), function(data) {
// Next Dialog
});
});
Far less code, strongly typed model binding, client and server side validation and all the other beneficial features of using MVC!
Why not doing something like this -
public class QA {
public int QId {get;set;}
public string Answer {get;set;}
}
[AllowAnonymous]
[HttpPost]
public ActionResult RequestSecurityQuestions_Submit(QA[] answers)
{
for (var qa in answers){
//do what ever you like
}
}
and then in js -
// Creating your array
var dataObject = [],
var dropdowns = $("input.customdropdownlist");
for (var i = 0; i < 6; i++) {
dataObject[i] = {
'QId': $(dropdowns[i]).data("kendoDropDownList").value(),
'Answer': dataObject[i] = $('#idAnswer' + i).val();
};
}
$.ajax({
url: Url.getFullUrl('Account/RequestSecurityQuestions_Submit'),
type: 'Post',
data: JSON.stringify(dataObject),
contentType: "application/json",
cache: false,
success: function (data) {
},
error: function(data){
}
});

How to read serialized data (sent through Jquery POST) in MVC collection

I have a form and one submit button click, i serialize my form and send it to MVC action.
Jquery code
var formCollection = $('form').serialize();
var path = $(this).attr('data-content-url');
// check if no more error
$.ajax({
type: 'POST',
url: path,
cache: false,
data: { collection: formCollection },
datatype: 'json',
success: function (data) {
// do stuff
}
});
MVC side
[HttpPost]
public ActionResult MethodName( FormCollection collection )
{
}
i do get serialize data in my collection variable as
name=test&id=1 as collection[0]
How can i break this collection[0] so that this name and id can be assigned directly to class parameter, something like
Person p = new person { collection["name"], collection["id"] }
Many thanks
this worked, create a new helper class which convert your serialized data in formcollection as you would normally expect
private FormCollection DeSerialize(FormCollection form)
{
FormCollection collection = new FormCollection();
//un-encode, and add spaces back in
string querystring = Uri.UnescapeDataString(form[0]).Replace("+", " ");
var split = querystring.Split(new [] {'&'}, StringSplitOptions.RemoveEmptyEntries);
Dictionary<string, string> items = new Dictionary<string, string>();
foreach (string s in split)
{
string text = s.Substring(0, s.IndexOf("="));
string value = s.Substring(s.IndexOf("=")+1);
if (items.Keys.Contains(text))
items[text] = items[text] + "," + value;
else
items.Add(text, value);
}
foreach (var i in items)
{
collection.Add(i.Key, i.Value);
}
return collection;
}
you need to Deserialize using your model
try this
var response = JsonConvert.DeserializeObject<YourModel>(collection);
var name = response.name ;
var id = response.id;
use foreach loop , to get more records from list

Categories

Resources