MVC not parsing IEnumerable values in request - c#

MVC 4 seems to be having trouble parsing the object I am sending it.
Using jQuery's ajax function, I send the data using POST request. I receive it in the Request object, it appears like this in the Request.Form:
{Name=Test&Groups%5b0%5d%5bName%5d=GroupName1&Groups%5b0%5d%5bCount%5d=123Groups%5b1%5d%5bName%5d=GroupName2&Groups%5b1%5d%5bCount%5d=123ID=bee4c411-f06c-43c6-815f-8002df4f2779}
//formatted for readability
Name=Test &
Groups[0][Name]=GroupName1 &
Groups[0][Count]=123 &
Groups[1][Name]=GroupName2 &
Groups[1][Count]=123 &
ID=bee4c411-f06c-43c6-815f-8002df4f2779
The Name and ID values are parsed just fine, but the Groups array is not... I do get an IEnumerable, and it contains the correct number of groups but the values within are null.
I've read this and this and I can't seem to find what I did wrong...
What am I missing?
The MVC Action looks like this:
public ActionResult UpdateGroups(GroupsListRequest req)
{
[...] //handle the request
}
and GroupsListRequest looks like this:
public class GroupsListRequest
{
public string Name { get; set; }
public string ID { get; set; }
public IEnumerable<GroupRequest> Groups { get; set; }
}
Finally, GroupsRequest looks like this:
public class GroupsRequest
{
public string Name { get; set; }
public int Count { get; set; }
}

try this
remove the square bracket around name and count field in request.
Name:Test &
Groups[0].Name:GroupName1 &
Groups[0].Count:123 &
Groups[1].Name:GroupName2 &
Groups[1].Count:123 &
ID:bee4c411-f06c-43c6-815f-8002df4f2779
your input name should be like this
<input type="text" name="Groups[0].Name" value="George" />

So, as #sangramparmar and #stephenmuecke mentioned, the problem was with the format of the objects in the array - they were surrounded by an extra pair of brackets.
Several circumstances made this problematic to fix:
I am using jQuery to send the request to the server, and the param function is the one causing the problem by parsing the array incorrectly (at least as far as C#'s MVC is concerned
Each request to the server needs to have a (session-constant) key added to its data prior to sending the request. This is performed automatically through a global ajaxSetup
For some reason, MVC failed to read the ajax request's data when I set the contentType to application/json (at least, failed to read it in the way I was expecting... which, if I've learned anything ever, was probably misdirected in some way)
The solution I ended up using was to build my own stringify function to parse the data object to a string, like so:
stringify: function (obj, prefix) {
var objString = '';
prefix = prefix || '';
if(typeof(obj) === 'object') {
if (Array.isArray(obj)) {
_.each(obj, function (item, index) {
objString = objString + utilityMethods.stringify(item, prefix + '%5b' + index + '%5d');
});
} else {
if (prefix != undefined && prefix.length !== 0) {
prefix = prefix + '.';
}
_.each(_.keys(obj), function (key) {
objString = objString + utilityMethods.stringify(obj[key], prefix + key);
});
}
} else {
objString = objString + '&' + prefix + '=' + obj;
}
return objString;
}
Then, in global ajaxSetup, I added a beforeSend to add the needed key, like so:
beforeSend: function (jqXHR, settings) {
var userKeyString = 'userKey=' + [... fetch the user key...];
if (settings.type.toUpperCase() === 'GET') {
settings.url = settings.url + '&' + userKeyString;
} else {
if (settings.data != undefined) {
settings.data = userKeyString + '&' + settings.data;
}
}
},
Now, I do end up with an extra ampersand in each request. I'll fix that eventually... maybe.

Related

Error message is given when creating a CSV from a List of Lists which are based on a Datamodel

Background
Currently executing a stored procedures on a database using PetaPoco, the result is each call will give me a list of OfficeGroup,OfficeGroupID, which is subsequently bound to a data model.
Problem
I am having trouble trying to create this CSV file, I can opun a great StackOverflowExample example which I have started to use. The only difference is that I have a list of objects and not strings. So I am trying to alter that code to my needs but I am getting the following error.
Error
Error CS0173 Type of conditional expression cannot be determined because there is no implicit conversion between 'string' and 'TestScript.Models.MarketStoredProcedureDatamodel
DataModel
namespace TestScript.Models
{
public class MarketStoredProcedureDatamodel
{
public string OfficeGroupID { get; set; }
public string OfficeGroup { get; set; }
}
}
Code
List<List<MarketStoredProcedureDatamodel>> storedProcCollection = new List<List<MarketStoredProcedureDatamodel>>();
const char SEPARATOR = ',';
using (StreamWriter writer = new StreamWriter("file.csv"))
{
storedProcCollection.ForEach(line =>
{
var lineArray = line.Select(c =>
ERROR Line >> c.OfficeGroup.Contains(SEPARATOR) ? c.OfficeGroupID.Replace(SEPARATOR.ToString(), "\\" + SEPARATOR) : c).ToArray();
writer.WriteLine(string.Join(SEPARATOR, lineArray));
});
}
}
I think that your issue is that you want to return c.OfficeGroup here (scroll to the right to see the code on the end):
c.OfficeGroup.Contains(SEPARATOR) ? c.OfficeGroupID.Replace(SEPARATOR.ToString(), "\\" + SEPARATOR) : c)
Use this instead:
c.OfficeGroup.Contains(SEPARATOR) ? c.OfficeGroupID.Replace(SEPARATOR.ToString(), "\\" + SEPARATOR) : c.OfficeGroup)
Your code is returning the MarketStoredProcedureDatamodel object instead of the OfficeGroup that you want to get for your CSV.

How to rename element ids to conform to model sequence collection

C# MVC web application
I have a button that adds input fields dynamically to my web page on a form.
I specifically give the dynamically added elements an id and name to conform to my Model so that they get passed back as an collection of like items to match a “Artists” collection property in my model.
So the input elements name and ids are for example
Artists[0].artist
Artists[1].artist
Artists[2].artist
In my model I have:
public Collection<Artist> Artists { get; set; }
public class Artist
{
public string artist { get; set; }
}
And here is my script to add remove an element:
var artists = new Array();
var AddArtist = function (e) {
artistname = $("#artistinput").val();
artists.push($("#artistinput").val());
var addDiv, artistVal;
addDiv = $("#artist");
artistVal = $("#artistinput").val();
var input_append = $('<div />'),
label = $('<input />', {
style: 'background-color:#e0ffff',
Value: artistVal,
id: 'artists[' + (artists.length - 1) + '].artist',
name: 'artists[' + (artists.length - 1) + '].artist',
readonly: true
}),
image = $('<img />', {
id: 'removeartist',
src: '/Content/bootstrap/img/cross-button.png',
on: {
click: function () {
input_append.remove();
artists.splice(artists.length - 1, 1);
var test = (artists.length - 1);
alert(test);
}
}
}
);
addDiv.append(input_append.append(label, image));
};
I can add/remove the elements on the actual page. The problem is, if I remove an element from the middle or beginning, the sequence of the name ids are broken and when the collection get passed back to my model the Artists collection is now empty.
So instead of
Artists[0].artist
Artists[1].artist
Artists[2].artist
This may be passed back:
Artists[0].artist
Artists[2].artist
Which is no longer a collection based on how the view is mapped to the model.
I need to rename all the name/ids in a ordered sequence once an item has been removed.
What’s the easiest solution for this problem.
So that this gets passed back
Artists[0].artist
Artists[1].artist
Okay here's what I used in a previous project to revise the IDs and names of inputs to allow model binding when posted to an MVC controller.
The first function takes an object and searches for all inputs, selects and textareas contained within it. The object you pass would probably be a row or div that contains all related inputs per Artist.
// Applies new id and name with correct number sequence to set fields
function reviseFieldNameAndId(obj, newNumber) {
obj.find('input,select,textarea').each(function () {
var parts = this.id.split(/_[\d+]__/); // Everything can be obtained from id only
this.id = parts[0] + '_' + newNumber+ '__' + parts[1]; // Payments_0__PaymentReasonId
this.name = parts[0] + '[' + newNumber+ '].' + parts[1]; // eg. Payments[0].PaymentReasonId
});
}
function reviseAllFieldNamesAndIds() {
$('#artists .row').each(function (index) {
reviseFieldNameAndId($(this), index);
});
}
Use the second function to go through all rows and perform the apply the new sequence.
PS - your answer is near enough the same as this but only works with artists whereas mine will work with different names and ids
I gave each dynamically added input item the same class name then used JavaScript to update and reorder every element id and name in an ordered sequence:
$('.divArtist').each(function (i, obj) {
this.id = 'artists[' + i + '].artist';
this.name = 'artists[' + i + '].artist';
});

Azure Mobile Services - Passing arrays through Custom Parameters

I'm using Azure Mobile Services with a C# client. I have a table of "Scores" that have a Facebook Id for each score. What I need to do is pass in an array of friends for a user and return all scores in that list of friends.
So I tried this on the client:
return _client.GetTable<Score>().WithParameters(new Dictionary<string, string>
{
{ "Level", level.ToString() },
//aggregate our string[] to a comma-delimited string
{ "Friends", friends.Aggregate(new StringBuilder(), (b, s) => b.Append(s).Append(',')).ToString() }
}).ToListAsync();
Which is weird I only have the option to pass in strings for custom parameters.
So I did this on the server's read:
function read(query, user, request) {
query.where({ Level: request.parameters.Level, UserId: request.parameters.Friends.split(',') });
request.execute();
}
It doesn't seem like a comma-delimited list is going to work. I get this error on the server:
Error in script '/table/Score.read.js'. Error: Unsupported literal value chucknorris,bobloblaw,
NOTE: I passed chucknorris and bobloblaw as Facebook ids for a test.
Is there another way to make this work? The "Level" value filters just fine if I take out the string delimited stuff.
Your usage of the mssql object definitely works, but you can also use a function in the query.where call, with the function returning the condition you want:
function read(query, user, request) {
query.where(function(level, userIds) {
return this.Level === level && this.UserId in userIds;
}, request.parameters.Level, request.parameters.Friends.split(','));
request.execute();
}
Notice that the in operator in this function doesn't behave quite exactly like the in operator in JavaScript. Instead, it's used exactly to have an 'or' grouping of equality statements, which you were creating "by hand" to pass to the SQL object.
Got it working with this server-side script:
function read(query, user, request) {
var innerSql = '';
var friends = request.parameters.Friends.split(',');
var parameters = new Array(friends.length + 1);
parameters[0] = request.parameters.Level;
for (var i = 0; i < friends.length; i++) {
if (i !== 0) {
innerSql += ' or ';
}
innerSql += 'UserId = ?';
parameters[i + 1] = friends[i];
}
mssql.query('select * from Score where Level=? and (' + innerSql + ')', parameters, {
success: function (results) {
request.respond(statusCodes.OK, results);
}
});
}
I'll keep this answer open for a few days if someone has a cleaner solution.

Json Result From MVC With Ajax Is Not Showing

My Ajax block looks like so:
$('#btnTest').click(function(){
$.getJSON('/User/ViewMessages',function(result) {
// TODO: update the DOM with the items
$("table#tblMessages").append("<tr><td>"+result.MESSAGETEXT+"</td><td>"+result.DATESENT+"</td>");
});
});
My Action in my Controller looks like such:
public ActionResult ViewMessages()
{
var recipient = Convert.ToInt32(HttpContext.Session["User_Id"]);
var query = (from m in context.Messages
from rec in context.Recipients
where rec.RECIPIENT == recipient
where rec.MESSAGEID == m.MESSAGEID
select new
{
m.MESSAGETEXT,
m.DATESENT
}).ToList();
return Json(query.ToList());
}
When Debugging, my query variable returns:
{ MESSAGETEXT = "seresr", DATESENT = {9/15/2011 11:06:45 AM} }
The thing is, my table is added with "undefined" for both my values. Where have i gone wrong? I have already added the maproute as well, but I'm still out of luck.
It looks like you're returning a list, which will be represented in JSON as an array. So your result object is a JavaScript array rather than a single object. To loop through all the items and add a table row for each, try something like this:
$.getJSON('/User/ViewMessages', function(result) {
for (var i in result) {
var row = result[i];
$("table#tblMessages").append("<tr><td>" + row.MESSAGETEXT
+ "</td><td>" + row.DATESENT + "</td>");
}
});

javascript Request.QueryString

How do I request querystring using javascript from URL
e.g : http://localhost:1247/portal/alias__MySite/lang__en/tabid__3381/default.aspx
I want to get tabid...
var tabid = '<%= Request.QueryString["tabid"] %> ';
Above code works only in aspx page
but i dont need it, any ideas? thanks
There is now a new api URLSearchParams. Use that in conjunction with window.location.search
var urlParams = new URLSearchParams(window.location.search);
console.log(urlParams.get('tabid'));
If your browser does not support URLSearchParams, you can create a custom fallback function:
function getParam(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
};
console.log(getParam('tabid'));
Don't know why but I've always found the javascript for querystring data fetching a bit hacky. if you don't need this value on the initial page load then perhaps you could use Request.QueryString in the code and set the value to a hidden field, which your javascript will read from?
Try this, It is working perfectly for me.
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
var tabId=getParameterByName("tabid");
I bet there is a server-side rewrite (DotNetNuke?), so the aspx.cs "sees" the redirection target which contains the correct QueryString.
For the client, you have to use another mechanism because the browser only "sees" the public URL. In this case, a Regex that picks the number behind 'tabid_' and before the next slash should work. This would be the same number (page id?) that the aspx page "sees".
This is what I used:
<script type="text/javascript">
function QueryString(key) {
//Get the full querystring
fullQs = window.location.search.substring(1);
//Break it down into an array of name-value pairs
qsParamsArray = fullQs.split("&");
//Loop through each name-value pair and
//return value in there is a match for the given key
for (i=0;i<qsParamsArray.length;i++) {
strKey = qsParamsArray[i].split("=");
if (strKey[0] == key) {
return strKey[1];
}
}
}
//Test the output (Add ?fname=Cheese&lname=Pizza to your URL)
//You can change the variable to whatever it is you need to do for example, you could
//change firstname to id and lastname to userid and just change the reference in the
//document.write/alert box
var firstname = QueryString("fname");
var lastname = QueryString("lname");
document.write("You are now logged in as " + firstname + " " + lastname + "!");
</script>
You can replace document.write with alert and it would give you an alert box instead!
I used this on my website. Its not done yet but when it is it will be at zducttapestuff.com
The output will look like this: You are now logged in as Cheese Pizza!
This is very unsecure for Passwords though since the password will be shown in the url.

Categories

Resources