Web API 2 Dictionary binding from JSON - asp.net-web-api2

This seems like a duplicate, but I've tried every permutation of the other answers I can come up with, and WebAPI always fails to bind a parameter of type Dictionary<string, string>. The parameter values is never populated. It usually is instantiated as an empty dictionary, though some variation I tried (lost track which) it was just null. Have tried various data shapes (see javascript snip), and both GET and POST verbs, changing the parameter attribute between [FromUri] and [FromBody] as appropriate.
Controller Method
[Route("api/reports/generate/{reportid}")]
[HttpPost]
public object GenerateReport(Guid reportid, [FromBody] Dictionary<string, string> values) {
// logic snipped...
}
Angular Service (though I have tried the same requests from Postman)
runReport(reportid: string, values: any) {
const keys = Object.keys(values);
// shape: { "values": { "someKey": "some string value", "anotherKey": "value" } }
const params: any = { values: values};
// shape: {"values": [{key: "someKey", value: "some string value"}, ...]}
// params.values = keys.map(k => ({ key: k, value: values[k] }));
// shape: { "values[0].Key": "someKey", "values[0].Value": "some string value", ...}
// keys.forEach((k, i) => {
// params[`values[${i}].Key`] = k;
// params[`values[${i}].Value`] = values[k];
// });
// try as get request (with parameter as [FromUri])
// return this._http.get(`/api/reports/generate/${reportid}`, {
// params, responseType: 'blob'
// });
// try as POST request (with parameter as [FromBody])
return this._http.post(`/api/reports/generate/${reportid}`, params, { responseType: 'blob' });
}

Quite late, but try this -
[Route("api/reports/generate/{reportid}")]
[HttpPost]
public object GenerateReport(Guid reportid) {
var values = this.Url.Request.GetQueryNameValuePairs(); //This would return the values in Key-Value dictionary format.
}

Related

Cannot format or transform data before save, bound too tightly to the view

I have some data in vuejs that I want to format before sending it off through an ajax call but it changes the view its bound to. For example I have a birthday field that is formatted like this on the view 01/11/1981 but I need to format that to YYYY-MM-DD HH:mm:ss for the db and I don't want to do this on the backend.
Where and when would I do this on the frontend? I have tried doing this before the ajax request and it changes the view, so I made a copy of the data and modified it and that also changed the view. It seems no matter what I do it affects the view.
Here is my methods block:
methods: {
/**
* Update the user's contact information.
*/
update() {
/*Attempt to copy and format*/
var formattedForm = this.form;
formattedForm.birthday = moment(formattedForm.birthday).format('YYYY-MM-DD HH:mm:ss');```
Spark.put('/settings/contact', formattedForm)
.then(() => {
Bus.$emit('updateUser');
});
},
}
Here is my data block as well:
data() {
return {
form: $.extend(true, new SparkForm({
gender: '',
height: '',
weight: '',
birthday: '',
age: '',
}), Spark.forms.updateContactInformation),
};
},
The easiest way is to make a clone using Object.assign, like so:
let form = Object.assign({}, this.form);
form.age = 21;
Here's the JSFiddle: https://jsfiddle.net/y51yuf05/
Objects are passed by reference in javascript, which means:
let a = {
"apple": 6
}
let b = a
then, b and a are pointing to the same location in the memory, it is essentially copying the address of the object in a to the variable b.
You need to therefore clone the object, there are many ways to do it like:
b = Object.assign({}, a)
MDN: Object.assign()
this would not be deeply cloned, which means if your object is nested then the nested objects would still be linked between the original and the copy.
for which I use:
function isObject(obj) {
return typeof obj === 'object' && !Array.isArray(obj)
}
function clone(obj) {
let result = {}
for (let key in obj) {
if (isObject(obj[key])) {
result[key] = clone(obj[key])
} else {
result[key] = obj[key]
}
}
return result
}
function logger () {
console.log("p.a.b.c: ", p.a.b.c)
console.log("q.a.b.c:", q.a.b.c)
console.log("r.a.b.c:", r.a.b.c)
}
let p = {a: {b: {c: 5}}}
let q = clone(p)
let r = Object.assign({}, p)
logger()
p.a.b.c = 11
logger()

In Processing web service data, Why m.prop is not required?

Note that this getter-setter holds an undefined value until the AJAX
request completes.
var users = m.prop([]); //default value
var doSomething = function() { /*...*/ }
m.request({method: "GET", url: "/user"}).then(users).then(doSomething)
But following code is not used m.prop. Why?
Are you set the default value in a different way?
//model
var User = {}
User.listEven = function() {
return m.request({method: "GET", url: "/user"}).then(function(list) {
return list.filter(function(user) {return user.id % 2 == 0});
});
}
//controller
var controller = function() {
return {users: User.listEven()}
}
If ok in the above code, and useless in the following?
var doSomething = function() { /*...*/ }
m.request({method: "GET", url: "/user"}).then(doSomething)
https://lhorie.github.io/mithril/mithril.request.html
The listEven code works because both m.prop and m.request returns a GetterSetter, but when using m.request the GetterSetter will be populated with the value returned from the promise. It's quite convenient.
And in the last example there is no GetterSetter involved, it's a simple promise usage. So all three examples works fine. To decide which one is best, you have to look at your specific case.

How do I operate the m.withAttr tutorials code?

A contrived example of bi-directional data binding
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
m.render("body", [
m("input", {onchange: m.withAttr("value", controller.user.name), value: controller.user.name()})
]);
}
};
https://lhorie.github.io/mithril/mithril.withAttr.html
I tried the above code does not work nothing.
It was the first to try to append the following.
m.mount(document.body, user);
Uncaught SyntaxError: Unexpected token n
Then I tried to append the following.
var users = m.prop([]);
var error = m.prop("");
m.request({method: "GET", url: "/users/index.php"})
.then(users, error);
▼/users/index.php
<?php
echo '[{name: "John"}, {name: "Mary"}]';
Uncaught SyntaxError: Unexpected token n
How do I operate the m.withAttr tutorials code?
Try returning m('body', [...]) from your controller.
view: function (ctrl) {
return m("body", [
...
]);
}
render should not be used inside of Mithril components (render is only used to mount Mithril components on existing DOM nodes).
The example is difficult to operate because it's contrived, it's not meant to be working out-of-the-box. Here's a slightly modified, working version:
http://jsfiddle.net/ciscoheat/8dwenn02/2/
var user = {
model: function(name) {
this.name = m.prop(name);
},
controller: function() {
return {user: new user.model("John Doe")};
},
view: function(controller) {
return [
m("input", {
oninput: m.withAttr("value", controller.user.name),
value: controller.user.name()
}),
m("h1", controller.user.name())
];
}
};
m.mount(document.body, user);
Changes made:
m.mount injects html inside the element specified as first parameter, so rendering a body element in view will make a body inside a body.
Changed the input field event to oninput for instant feedback, and added a h1 to display the model, so you can see it changing when the input field changes.
Using m.request
Another example how to make an ajax request that displays the retrieved data, as per your modifications:
http://jsfiddle.net/ciscoheat/3senfh9c/
var userList = {
controller: function() {
var users = m.prop([]);
var error = m.prop("");
m.request({
method: "GET",
url: "http://jsonplaceholder.typicode.com/users",
}).then(users, error);
return { users: users, error: error };
},
view: function(controller) {
return [
controller.users().map(function(u) {
return m("div", u.name)
}),
controller.error() ? m(".error", {style: "color:red"}, "Error: " + controller.error()) : null
];
}
};
m.mount(document.body, userList);
The Unexpected token n error can happen if the requested url doesn't return valid JSON, so you need to fix the JSON data in /users/index.php to make it work with your own code. There are no quotes around the name field.

Updating MVC Model List using Knockout.js

I am working on an app which connects to XSockets via WCF and am able to get the data on the client side. I want to display this data using Grid.Mvc and have seen samples of using knockout.js, but I am not sure how to push this into my IEnumerable model so that I can see the View updated.
I have tried using the following code
#{
var initialData = new JavaScriptSerializer().Serialize(Model); }
$(function() {
ws = new XSockets.WebSocket("ws://127.0.0.1:4502/Book");
var vm = ko.mapping.fromJSON('#Html.Raw(initialData)');
ko.applyBindings(vm);
//Just write to the console on open
ws.bind(XSockets.Events.open, function (client) {
console.log('OPEN', client);
ws.bind('SendBook', function (books) {
jQuery.ajax({
type: "POST",
url: "#Url.Action("BooksRead", "Home")",
data: JSON.stringify(books),
dataType: "json",
contentType: "application/json",
success: function (result) {
//This doesnt work
/vm.push({Name:'Treasure Island',Author:'Robert Louis Stevenson'});
//vm.pushAll(result)
},
error: function (result){},
async: false
});
});
});
I am always receiving a null value for the parameter in the the BooksRead JsonResult method.
The model is a simple one
public class BookModel
{
public string Name {get; set;}
public string Author {get; set;}
}
I am returning a BookModel IEnumerable as my Model from the home controller on load and would want to insert new books into it as I receive them in the socket bind. This is because I am using it to generate the grid.
#Html.Grid(Model).Columns(c =>
{
c.Add(b => b.Name).Titled("Title");
c.Add(b => b.Author);
})
I would appreciate any pointers and guidance as to how I can go about achieving this.Many thanks
UPDATE
I am now able to get values in the controller action method after removing the dataType & contentType parameters from the ajax call. The controller method is as follows
public JsonResult BooksRead(string books)
{
BookModel data = JsonConvert.DeserializeObject<BookModel>(books);
List<BookModel> bookList = (List<BookModel>) TempData["books"];
if (bookList != null && data != null)
{
bookList.Add(data);
var bookString = JsonConvert.SerializeObject(bookList);
return Json(bookString);
}
return Json("");
}
I have added a vm.push call in the success handler and am passing the result value to it, but it still doesnt seem to add the new book in the Model. It seems I am doing it the wrong way as I am new to knockout js, jquery & ajax and trying to learn as I go along so please pardon my ignorance
UPDATE 2
I have made a few more changes.Like Uffe said, I have removed the Ajax call. I am adapting the StockViewModel from the StockTicker example to my BookViewModel and have added a parameter to the ctor to take in my IEnumerable model. This works & the item is added. The AddOrUpdate is working fine too & the objects are added to the collection but how can I get my model to be updated in the grid.
#{
var initialData = #Html.Raw(JsonConvert.SerializeObject(Model));
}
$(function() {
vm = new BookViewModel(#Html.Raw(initialData));
ko.applyBindings(vm);
ws = new XSockets.WebSocket("ws://127.0.0.1:4502/Book");
//Just write to the console on open
ws.bind(XSockets.Events.open, function(client) {
console.log('OPEN', client);
ws.bind('SendBook', function (msg) {
vm.AddOrUpdate(msg.book);
});
ws.bind(XSockets.Events.onError, function (err) {
console.log('ERROR', err);
});
});
});
The ViewModel is as follows
var BookViewModel = function(data) {
//this.Books = ko.observableArray(data);
this.Books = ko.observableArray(ko.utils.arrayMap(data, function(book) {
return new BookItem(book);
}));
this.AddOrUpdate = function(book) {
this.Books.push(new BookItem(book));
};
};

Delete method with array type as parameter showing null value

I am calling web-api method delete all with array type parameter, showing the value null. why?
I am passing data like : data: "ArrMenuId"+ JsArrayMenuId,
function performalldeletemenu()
{
if (confirm('Are you sure you want to delete this menu?'))
{
var JsArrayMenuId = new Array();
$("input:checked").each(function ()
{
//console.log($(this).val()); //works fine
JsArrayMenuId.push($(this).val());
});
alert(JsArrayMenuId);
$.ajax({
url: '/api/MenuWebApi/DeleteAllMenu/',
type: 'DELETE',
contentType: 'application/json; charset=utf-8',
data: "ArrMenuId"+ JsArrayMenuId,
success: function (data)
{
if (data.Success == true)
{
//GetMenuList();
}
},
error: function (xhr, textStatus, errorThrown)
{
//window.location = JsErrorAction;
},
headers:
{
'RequestVerificationToken': JsTokenHeaderValue
}
});
}
return false;
}
public HttpResponseMessage DeleteAllMenu(Array ArrMenuId)
{
}
Here ArrMenuId is showing null values.
if any one have solution, please let me know.
Try changing
data: "ArrMenuId"+ JsArrayMenuId,
to
data: {ArrMenuId : JsArrayMenuId.join()}
and changing
public HttpResponseMessage DeleteAllMenu(Array ArrMenuId)
to
public HttpResponseMessage DeleteAllMenu(string ArrMenuId)
I don't think javascript array will translate easily into a c# array and by changing it to this you are instead passing a string. Once you have this comma delimited string you can make it into an array in your c#