Firstly, I apologise in advance for any incorrect terminology! I've gone snow blind reading so many blog posts and articles on how I might do this and have come to an impasse.
I've been successfully passing parameters via AJAX to an MVC action in a controller, which returns the SQL datatable in JSON format for me to consume in my page. An example set of parameters I send looks like this;
var parameters = {
pSQL: "SELECT * FROM v_MyTable",
pSQLServer: "SERVER01",
};
My Client Side Script looks like this;
$.ajax({
type: 'POST',
url: '/ControllerName/GiveMeJSON',
data: JSON.stringify(parameters),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
...
});
My Controller Action simply works like this;
public string GiveMeJSON(string pSQL, string pSQLServer)
{
var SQL = pSQL
var SQLSERVER = pSQLServer
// do stuff and return JSON
}
This has been working great for me. I send JSON request data as POST and receive JSON data in return. Brilliant.
Now, I'd like to extend the functionality of my action so that I can pass it an additional set of variables as an array, something like this;
var parameters = {
pSQL: "SELECT * FROM v_MyTable",
pSQLServer: "SERVER01",
pMoreParams: {
pParameter1: "ABC",
pParameter2: "XYZ",
}
};
...where the parameter names in MoreParams could be called anything, with any type of value. That's important, as I think that rules out using models?
This results in the POST request BODY looking like this Valid JSON;
{"pSQL":"SELECT * FROM v_MyTable","pSQLSERVER":"SERVER01","pMoreParams":{"pParameter1":"ABC","pParameter2":"XYZ"}}
So the data I need is clearly going to the server, but yet I am really struggling with enumerating/accessing those additional items in the pMoreParams array. I seem to be getting close, I've played around with using...
public string GiveMeJSON(string pSQL, string pSQLServer, List<string> pMoreParams)
...and I've had a foreach loop in the controller run for however many items are in the pMoreParams list (2, in the above example) but just can't seem to work out how to access/read those values in that list in the controller action.
I'm at a loss now as to how to loop through and read the extra data I am sending. I sense I'm close, with using the List<string> or IEnumerable - but the solution evades me.
I have had more success getting the controller reading those extra parameters when I send them as a simple valid JSON string, like so;
var parameters = {
pSQL: "SELECT * FROM v_MyTable",
pSQLServer: "SERVER01",
pMoreParams: '[{"name": "pParameter1", "value": "ABC" },{"name": "pParameter2", "value": "XYZ"}]'
};
...and using a controller actions which look something like this;
public class SQLParam
{
public string name { get; set; }
public string value { get; set; }
}
if (string.IsNullOrEmpty(pMoreParams) == false)
{
var sqlparameters = JsonConvert.DeserializeObject<SQLParam[]>(pMoreParams);
foreach (var sqlparameter in sqlparameters)
{
Debug.WriteLine(sqlparameter.name, sqlparameter.value);
}
};
...BUT I then lose the ability to easily read and modify those values in client-side jquery/javascript, and I'd MUCH prefer/desire to keep the way I define the parameters as it is, so that I can easily read and modify the values using a one-liner, something like;
parameters.pMoreParams[0].pParameter1 = "NEW VALUE";
...as I can then simply trigger the AJAX call again and it POSTS with the new parameter values.
Any help and advice is much appreciated, thanks!
When you send in:
"pMoreParams":{"pParameter1":"ABC","pParameter2":"XYZ"}
You are sending in an object called pMoreParams that has two properties.
If you want to get these from the controller, you would need to create a C# object which mirrors the JSON you are sending in.
public class MoreParams
{
public string pParameter1 {get;set;}
public string pParameter2 {get;set;}
}
And then update your action method:
public string GiveMeJSON(string pSQL, string pSQLServer, MoreParams pMoreParams)
{
var param1 = pMoreParams.pParameter1;
var param2 = pMoreParams.pParameter2;
}
SIDE NOTE
You are sending in SQL for the the server to execute. I know it is convinient, but this is a HUGE security issue that will lead to SQL Injections that will allow an attacker to control your SQL server.
OK, after a good sleep, I think I have come up with a solution which meets my requirements to not have to define all my possible and future parameters in a class and also satisfies some of the SQL injection concerns, having read up on using parameterisation;
Here's the data I JSON.stringify and pass via AJAX to the controller;
var chart01Params = {
pSQLCon: "ConnectionString",
pSQL: "MySQLFile.sql", // accessible only by the server from MapPath
pMoreParams :
[
{
"name" : "pParameterXYZ",
"value": "12345ABC"
},
],
};
...which looks like this in POST request body;
{"pSQLCon":"ConnectionString","pSQL":"MySQLFile.sql","pMoreParams":[{"name":"pParameterXYZ","value":"12345ABC"}]}
Here's my class;
public class SQLParam
{
public string name { get; set; }
public string value { get; set; }
}
Here's my controller head;
public string GETMYJSON(string pSQLCon, string pSQL, List<SQLParam> pMoreParams)
...before my controller executes the SQL command it will add any parameters and values to the SQL query, which I hope will make it more secure from SQL Injection attack;
foreach (SQLParam parameter in pMoreParams)
{
cmd.Parameters.Add(parameter.name, SqlDbType.Char);
cmd.Parameters[parameter.name].Value = parameter.value;
}
And it works great! If I want to send up parameters that a query expects, I can do so without having to include them in a class and I can also, still, use local client-side scripting to take choices/input from the DOM and set parameter values as long as I know the Index in the object of the parameter value I want to change.
Related
I am very new to MVC and making api calls server side and need a little guidance. I have created a simple method to call an api to retrieve results in a JSON object:
apiController.cs (normal controller.cs file)
[HttpGet]
public JsonResult getDefaultStuff(string a = "abc") {
var url = "https://myapiurl";
var client = new HttpClient();
client.DefaultRequestHeaders.UserAgent.ParseAdd("Blah");
var response = client.GetStringAsync(url);
return Json(response, JsonRequestBehavior.AllowGet);
}
The results return in an array like this:
{Result: {examples: [[0000,6.121],[0000,1.122],[0000,9.172]]},"Id":81,"Exception":null,"Status":5,"IsCanceled":false,"IsCompleted":true,"CreationOptions":0,"AsyncState":null,"IsFaulted":false}
I need it to return with keynames like this :
{
"examples": [
{
"Keyname1": "45678",
"Keyname2": "1234"
},
{
"Keyname1": "14789",
"Keyname2": "1234"
},
{
"Keyname1": "12358",
"Keyname2": "4569"
}
]
}
Do I need to use IDictonary? I am unsure of the approach. Do I create a new object and then loop through each result adding keynames? an example would be much appreciated or just the approach will be very helpful.
You can do the following:
By using the Json.Net nuget package first deserialize the response into, for example, an anonymous object:
var deserialized = JsonConvert.DeserializeAnonymousType(response, new
{
examples = new[] { new decimal[] { } }
});
Then transform this object into the new one that has the property structure you need:
var result = new
{
examples = deserialized.Result.examples.Select(x => new
{
Keyname1 = x[0],
Keyname2 = x[1]
})
};
And return it to the client:
return Json(result, JsonRequestBehavior.AllowGet);
This is what your solution could roughly look like but you do have to keep several things in mind though:
Exception handling for deserialization
Checks for possible nulls in the deserialized object
Maybe also the safer way of retrieving values from the array to avoid possible out of range exceptions.
Also the GetStringAsync method is asynchronous and you should put an await keyword in front of it, but in order to do so you need to make your method async as well:
public async Task<JsonResult> getDefaultStuff(...)
If you don't have enough knowledge of asynchronous programming, here is the most advanced, in-depth and comprehensive video explaining it from top to bottom I have ever seen, so check it out whenever you find time...
I have a WebApi service that calculates a price of a customized product. The controller function is:
public double Get([FromUri]Specifications specifications)
Specifications is a class that allows to customize the product:
public class Specifications
{
public string Currency;
public int DesktopLicenses;
public Product Product;
public int Licenses;
}
Now, how can I consume this service from C#. I want to avoid to codify manually the URI query with all Specifications variables, I would like to able to use directly an instance of Specificationsto call the service.
If the service is a POST, I could do it doing:
Specifications product = new Specifications( ...);
HttpResponseMessage reponse = httpClient.PostAsJsonAsync("api/pricecalculator", product).Result;
but I cannot find the way to do the same when I use GET.
The example is showing that the GET is passing it a complex object in the call. Normally, that's just a simple request, and returning the complex object -- that's the "best practice". If you need to request something by giving it a complex object - it should still be a POST call. I know the pundits like to think POST/PUT as your change/add for the REST world -- but in the end, frankly there's zero difference between a POST and a GET besides the request body. If you need to give the server complex data, use the request body (aka POST). If it's a simple request -- /api/listofvendors/zone1 - then use a GET.
Web API Get Method with Complex Object as Parameter
example:
[HttpGet]
[Route("~/services/mrf/{mrfnumber}")] // GET specific MRF
public Mrf GetMrfRecord(string mrfnumber) {
using (var ddc = new MRFDataContext(ConnectionString)) {
var options = new DataLoadOptions();
options.LoadWith((Mrf c) => c.MRFParts); //immediate load related MRFParts
ddc.LoadOptions = options;
var mrf = (from u in ddc.Mrfs
where u.MrfNum == mrfnumber
select u).FirstOrDefault();
return mrf ?? null;
}
}
I created a Web-API and i would like to get all routes with parameters BeginAddress (string), EndAddress(string), BegineDate (Datetime). I created a new Class SearchRoute with these properties.
I can do a normal Getwith an id or a string but how to do a Get by giving an object? Is this possible?
Would it be possible to do a post/put with an object and than ask for a return?
using (HttpClient client = new HttpClient())
{
HttpResponseMessage response = await client.GetAsync(url + userid);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
List<Route> list = await SerializeService.Deserialize<List<Route>>(content);
return list;
}
return null;
}
Web API Function
public List<Route> GetAllByCity(SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}
Update:
If i do this, the Post doesn't work but if i create a new controller it works.
[HttpPost]
// POST api/route
public void Post([FromBody]Route route)
{
RouteDAO.Create(route);
}
// POST api/route
[HttpPost]
public List<Route> Post([FromBody]SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}
I prefer sticking with GET even when using a complex object as a parameter. If you are concerned about the length of the URI then remember that:
Prefixing the property names for simple like complex objects is not necessary because the Web API object binding can auto resolve based on property names alone.
The maximum allowed URL length is 2083 characters which is more than sufficient in most cases.
If you we take your example
public class SearchRoute {
public string BeginAddress {get;set;}
public string EndAddress {get;set;}
public DateTime BeginDate {get;set;}
}
[HttpGet]
public List<Route> Get([FromUri]SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}
Uri when searching on
BeginAddress = "Some beginning";
EndAddress = "Some ending"
BeginDate = "2016-01-01T16:40:00"
Resulting query string:
?BeginAddress=Some beginning&EndAddress=Some ending&BeginDate=2016-01-01T16:40:00
Again, the properties will auto resolve even without the object prefix/qualifier and populate the object instance.
Add a domain info to the URL maybe another 50 or so characters
Add a controller name maybe another 30 or so characters
Add the query string = 82 characters
Note that I am not taking into account resolving the special characters like spaces to Url escaped character sequence
Total ≈ 162 characters give or take
Not bad considering that the maximum allowed URL length is 2083 characters, so you have used up only 7% of what is possible in this simple example.
This would probably be the preferred way of doing it because it conforms to the RESTful API standard where GET calls/verbs do not alter data and POST calls/verbs do.
You can pass an object by using a complex type in the URI. You need to help Web API by using the correctly formatted Query String. This would be an example:
?SearchRoute.BeginAddress=TheAddressValue&SearchRoute.EndAddress=TheAddressValue
However, if your Query String starts to become too big, you might be modeling the interaction incorrectly.
Then, in the server you should let Web API know that it should look in the URI for the values:
public List<Route> GetAllByCity([FromUri]SearchRoute sr)
{
return RouteDAO.GetAllByCity(sr);
}
I didn't think this would be that hard, but I am trying to pass a class that has a dynamic for a property (which is currently being set as an expando object in the c#) into the View. In this view, a good bit is getting rendered w/ some jQuery Templating and I thought that if I had this in the c# code:
public dynamic SomeProperty {get;set;}
...
SomeProperty = new ExpandoObject();
SomeProperty.SomeValue = "5";
return View(TheClassThatContainsSomeProperty);
Such that in the jquery templating I could do:
${SomeProperty.SomeValue}
...and that it would hopefully work. It doesn't... If you inspect the response, you can see that it essentially gets sent over as a dictionary:
SomeProperty: [{SomeValue, Value:5}]
0: {Key:SomeValue, Value:5}
which leads to (I guess) my next question: Is there an easy way to access dictionaries in jquery templating? I did try this:
${SomeProperty["SomeValue"]}
to no avail either. At this point the only thing I know to do is to leverage the ability to put a function in the template (as copied here from the jquery website):
Template:
<tr><td>${getLanguages(Languages, " - ")}</td></tr>
Code:
function getLanguages( data, separator ) {
return data.join( separator );
}
So am I over complicating this? Can I easily either 1) access a dynamic value from jquery template or 2) Easily lookup a value from a dictionary in jquery template?
ExpandoObject derives from IEnumerable<KeyValuePair<string, Object>>, and most serializers will recognize a dynamic as this type when you assign an ExpandoObject. This is why you see an array type on the javascript side with named Key::Value pairs.
ExpandoObject Class (System.Dynamic) # MSDN
One alternative to using ExpandoObject is to use C# anonymous types. When serialized to json, these map field by field as you expect.
Anonymous Types (C# Programming Guide) # MSDN
It is possible to access values declared with dynamic from jQuery, but most likely you won't be returning a MVC View() with the model to be consumed with jQuery, as any server-side view template engine (razor, etc.) can already perform the same template activities with less overhead. Instead, jQuery templates are better used with Ajax calls.
Here are code examples demonstrating three cases where a variable declared dynamic on the server is consumed with jQuery templates in the browser.
The first example uses an anonymous type for the member field SomeValue, and has a jQuery template that treats it as a member object.
The second example uses an array of anonymous types for the member field SomeValue and has a template that uses {{each}} syntax to enumerate the items. Note that this is a scenario where things can go badly with dynamic, as you get no strongly-typed support and must either know the correct type or discover it at the time you access it.
The third example uses an ExpandoObject for the member field SomeValue, and has a jQuery template like the first example (single member object). Note that in this case, we need to use a helper function pivotDictionaryMap() to pivot Key::Value pairs into object members.
Starting with a blank C# MVC3 Web Project, we need to modify three files to demonstrate these examples.
Inside _Layout.cshtml, add script includes for jQuery templates and a proper version of jQuery in your <head> element.
<script src="#Url.Content("~/Scripts/jquery-1.8.2.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.tmpl.js")" type="text/javascript"></script>
Inside HomeController.cs, we'll add some methods that return json ActionResults. Also, for brevity we'll just declare a class SomeModelType here; and note that a proper application would probably have this class declared in its Models.
using System.Dynamic; // up top...
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC!";
return View();
}
public ActionResult SomeDataSource(int id)
{
dynamic d = new { innerId = 99, innerLabel = "inside object" };
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = d;
return Json(obj, "text/plain");
}
public ActionResult SomeDataSourceWithArray(int id)
{
dynamic d1 = new { innerId = 99, innerLabel = "inside object (first array member)" };
dynamic d2 = new { innerId = 100, innerLabel = "inside object (second array member)" };
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = new object[] {d1, d2};
return Json(obj, "text/plain");
}
public ActionResult SomeDataSourceWithExpando(int id)
{
dynamic d = new ExpandoObject();
d.innerId = 99;
d.innerLabel = "inside object";
SomeModelType obj = new SomeModelType(id, "new object");
obj.SomeValue = d;
return Json(obj, "text/plain");
}
}
public class SomeModelType
{
public SomeModelType(int initId, string initLabel)
{
Id = initId;
Label = initLabel;
}
public int Id { get; set; }
public string Label { get; set; }
public dynamic SomeValue { get; set; }
}
Finally, in the default view, we will add script tags for the templates and the javascript necessary to consume them. Note the use of $.post() and not $.get(), as a JsonResult in MVC disallows GET requests by default (you can turn these on with an attribute).
#{
ViewBag.Title = "Home Page";
}
<h2>#ViewBag.Message</h2>
<script id="someDataTemplate" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has an inner item with id <b>${SomeValue.innerId}</b> whose label is "<i>${SomeValue.innerLabel}</i>".
</script>
<h3>SomeDataSource Example #1 (Single Item)</h3>
<div id="someData">
</div>
<script id="someDataArrayTemplate" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has these inner items:
<ul>
{{each SomeValue}}
<li><b>${innerId}</b> has a label "<i>${innerLabel}</i>".</li>
{{/each}}
</ul>
</script>
<h3>SomeDataSource Example #2 (Array)</h3>
<div id="someArrayData">
</div>
<script id="someDataTemplateFromExpandoObject" type="text/x-jquery-tmpl">
Item <b>${Id}</b> is labeled "<i>${Label}</i>" and has an inner item with id <b>${SomeValue.innerId}</b> whose label is "<i>${SomeValue.innerLabel}</i>".
</script>
<h3>SomeDataSource Example #3 (Single Item, Expando Object)</h3>
<div id="someDataFromExpandoObject">
</div>
<script type="text/javascript">
function pivotDictionaryMap(src)
{
var retval = {};
$.each(src, function(index, item){
retval[item.Key] = item.Value;
});
return retval;
}
</script>
<script type="text/javascript">
$(document).ready(function() {
// Ajax Round-Trip to fill example #1
$.post("/Home/SomeDataSource/5", function(data) {
$("#someDataTemplate").tmpl(data).appendTo("#someData");
}, "json");
// Ajax Round-Trip to fill example #2
$.post("/Home/SomeDataSourceWithArray/67", function(data) {
$("#someDataArrayTemplate").tmpl(data).appendTo("#someArrayData");
}, "json");
// Ajax Round-Trip to fill example #3
$.post("/Home/SomeDataSourceWithExpando/33", function(data) {
data.SomeValue = pivotDictionaryMap(data.SomeValue);
$("#someDataTemplateFromExpandoObject").tmpl(data).appendTo("#someDataFromExpandoObject");
}, "json");
});
</script>
I won't mark my own answer as "correct" just on premise that I don't like this answer. If someone figures out what I was trying to do above, I'll gladly give you the points.
function getDisplayValue(data, toMatchOn) {
var _return = $.grep(data, function (n, i) { return (n.Key == toMatchOn); })[0];
if (_return != null)
return _return.Value;
return "";
}
and in the jquery template:
${getDisplayValue(DisplayFields, 'Something')}
So I was able to get this to work with the method described above so here it is as a possible solution but I don't know javascript well enough to know how bad of a performance hit this could be creating (and as I research it, I'll update this answer) but I thought javascript dictionaries were optimized against their key value, so the fact that MVC doesn't serialize expando as a true javascript dictionary seems to make this answer very inefficient. And the fact that I originally took this tack with the dynamic c# object was that I originally thought this would serialize down into a cleaner form. Anyway, this works but Occam's Razor is just making this feel way too complicated.
Not sure if this will help or not, but have a look at this gist. It is hard to tell from your code snippets but if you are turning that ExpandoObject into JSON, then try wrapping it on a DynamicJsonObject first, as done in the gist.
Code from the Gist copy/pasted for those who don't want to click the link:
// By default, Json.Encode will turn an ExpandoObject into an array of items,
// because internally an ExpandoObject extends IEnumerable<KeyValuePair<string, object>>.
// You can turn it into a non-list JSON string by first wrapping it in a DynamicJsonObject.
[TestMethod]
public void JsonEncodeAnExpandoObjectByWrappingItInADynamicJsonObject()
{
dynamic expando = new ExpandoObject();
expando.Value = 10;
expando.Product = "Apples";
var expandoResult = System.Web.Helpers.Json.Encode(expando);
var dictionaryResult = System.Web.Helpers.Json.Encode(new DynamicJsonObject(expando));
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", expandoResult); // FAILS (encodes as an array instead)
Assert.AreEqual("{\"Value\":10,\"Product\":\"Apples\"}", dictionaryResult); // PASSES
}
You're right that performance is going to be sketchy if you're iterating over an array to find a key. But you should be able to comfortably convert the key-value-pair array (that the server sends back) into a "true" Javascript dictionary/map. Eg:
var kvps = [ {key: "test", value: "expando"}, {key: "hello", value: "world" } ];
var map = {};
kvps.forEach(function(kvp) { map[kvp.key] = kvp.value; } );
console.log( JSON.stringify(map) );
{"test":"expando","hello":"world"}
Of course, if you're nested objects, then you'd have to apply recursion to the above approach to make it work.
View
#using (Html.BeginForm("UpdateClient", "Client")) {
Controller
[HttpPost]
public ActionResult UpdateClient(Client client)
{
if (ModelState.IsValid) {
bool ret = _clientRepository.UpdateClient(client);
return RedirectToAction("Index", "Home");
}
return View(client);
}
Repository
public bool UpdateClient(Client client)
{
using (var context = new Entities()) {
context.AttachTo("Clients",client);
context.ObjectStateManager.ChangeObjectState(client, EntityState.Modified);
context.SaveChanges();
}
return true;
}
When I call the UpdateClient in the controller, the client ID is 0. How do I pass the ID I have updated?
When you call your UpdateClient method from the client-side code (i.e, the browser), you need to make sure that the fields you want set are posted back to the receiving page.
For example, I often use jQuery and a simple ajax call to post data back to the server. The post needs to contain matching values for the Client object:
$.post("/controller/UpdateClient", {
"clientId": 1,
"otherProp1": "some value",
"otherProp2": "some value 2"
}, function (data) {
}
};
In your case, you probably need to make sure you've got an input field that contains a field named after ClientId so that it gets posted to the server that way:
<input type="hidden" id="clientId" name="clientId" value="1" />
Additionally, you may find it useful to installer Fiddler2 and use it to watch the data actually being posted to the server. It's invaluable when dealing with this type of thing.
There is not much info u gave us but your id can be 0 in 3 cases.
1. If you generate or input id on client side u just didn't post it properly. Missed hidden input, wrong variable name.
2. If you generate ur I'd in model constructor you should take in mind that there LL be default constructor called. Then it ll overwrite its properties with new values. Look at your id property is there something that can block or overwrite incoming value.
3. If u generate Id on database level then you should know that data base don't return id when creating new object.
Its a common problem, but I'm not very experienced in it(we generate id in business logic of application).
I'm sure ull track problem if u will have another look at your code.