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);
}
Related
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.
The following two links access the same action method:
http://stage.bullydog.com/Products/accessories/podmount and http://stage.sctflash.com/Products/accessories/podmount
I use Request.Url.Host to determine the brand of products I want to return from a database. When I access http://stage.bullydog.com/Products/accessories/podmount first, Request.Url.Host contains the value stage.bullydog.com, but if I then go to http://stage.sctflash.com/Products/accessories/podmount, Request.Url.Host may contain stage.sctflash.com or it may contain stage.bullydog.com.
The action method that is called is:
public ActionResult GetAccessoriesByType(RenderModel model, string id)
{
Common _common = new Common();
string brand = Request.Url != null ? _common.GetProductBrand() : BrandType.SCT;
var productSearchResultsModel = new ProductSearchResultsModel
{
Accessories = _accessoryRepository.GetAccessoriesByType(id, brand)
};
return View("~/Views/accessories.cshtml", productSearchResultsModel);
}
The code that gets the brand is:
public class Common
{
public string GetProductBrand()
{
var host = HttpContext.Current.Request.Url.Host;
if (host.Contains("sctflash"))
return BrandType.SCT;
if (host.Contains("bigrig") || host.Contains("bigrigs"))
return BrandType.BigRig;
if (host.Contains("bullydog"))
return BrandType.BullyDog;
return BrandType.SCT;
}
}
How can I ensure the Request.Url.Host contains the proper host when the same action method is accessed from two different hosts?
You can see this in action if you go to http://stage.bullydog.com/Products/accessories/podmount and then go to http://stage.sctflash.com/Products/accessories/podmount and refresh either one, the logo should change which means that the value returned by Request.Url.Host was incorrect.
Also, can GetProductBrand be static and still be thread-safe in this case?
The reason why accessing another website was bringing back the one websites content and the Request.Url.Host had the other websites information was because I had declared OutPutCache[Duration=60] at the top of my controller. If I waited 60 seconds and then refreshed, the correct data for each website would return. Once I removed this from the controller, everything worked as expected.
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 am currently using attribute routings such as this:
[Route("api/InventoryItems/{ID}/{packSize:int}/{CountToFetch:int}")]
...and am using even longer ones (with more "pieces," or arguments), and in practice they work fine.
However, I need to refactor this (args masquerading as part of the path considered bad practice) so that the URI passed by the client is not like so:
//http://<machineName>:<portNum>/api/InventoryItems/<IDVal>/<packSizeVal>/<CountToFetchVal>
http://platypus:8675309/api/InventoryItems/42/24/50
...but rather like this:
//http://<machineName>:<portNum>/api/InventoryItems/?<argName>=<argVal>?<argName>=<argVal>?<argName>=<argVal>
http://platypus:8675309/api/InventoryItems?ID=42?packSize=24?CountToFetch=50
Currently I can grab the args passed within the "path" info and pass them from the Controller (where they arrive) to the Repository (which uses them to get the precise data required).
For example, this Controller method:
[System.Web.Http.Route("api/Departments/{ID:int}/{CountToFetch:int}/{dbContext=03}")]
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch, string dbContext)
{
return _deptsRepository.Get(ID, CountToFetch, dbContext);
}
...has the values passed via a URI from the client assigned to the method parameters. What, if anything, do I need to change in this code for args passed in the URI via the "?=" method to also be assigned to the method parameters?
Can I do this, with those args simply stripped out of the Attribute Routing annotation, like so:
[System.Web.Http.Route("api/Departments")]
public IEnumerable<Department> GetBatchOfDepartmentsByStartingID(int ID, int CountToFetch, string dbContext)
{
return _deptsRepository.Get(ID, CountToFetch, dbContext);
}
?
...or possibly leave it as-is, with just the format of the URI changing (but nothing in the Controller)?
UPDATE
I wasn't expecting it to work, but I can definitely verify that leaving the server code as is, and replacing the URI with this jazz:
"?<argName>=<argVal>"
...does not work - it returns null without even hitting my Controller method!
UPDATE 2
With an URI like this:
http://localhost:28642/api/InventoryItems/PostInventoryItem?id=42?pack_size=12?description=ValuableDesc?vendor_id=venderado?department=42?subdepartment=85?unit_cost=2.50?unit_list=3.75?open_qty25.25?UPC_code=12345?UPC_pack_size=24?vendor_item=someVendorItem?crv_id=9898987?dbContext=03
...I can reach the Controller if I remove all args from the routing attribute and the method signature:
[Route("api/InventoryItems/PostInventoryItem")]
public void PostInventoryItem()
{
HandheldServerGlobals.SaveTypes st = HandheldServerGlobals.SaveTypes.CSV; //Works (C:\Program Files (x86)\IIS Express\SiteQuery3.csv created) // <-- I reach the breakpoint on this line, but...:
//commented out for now:
//_inventoryItemRepository.PostInventoryItem(id, pack_size, description, vendor_id, department, subdepartment, unit_cost, unit_list, open_qty, UPC_code, UPC_pack_size, vendor_item, crv_id, dbContext, st);
}
...but where/how do I get the args passed in the URI now?
By annotating the Controller method with "[FromURI]":
[Route("api/InventoryItems/PostInventoryItem")]
public HttpResponseMessage PostInventoryItem([FromUri] InventoryItem ii)
{
_inventoryItemRepository.PostInventoryItem(ii.ID, ii.pksize, ii.Description, ii.vendor_id, ii.dept,
ii.subdept, ii.UnitCost, ii.UnitList, ii.OpenQty, ii.UPC, ii.upc_pack_size, ii.vendor_item, ii.crv_id);
var response = Request.CreateResponse<InventoryItem>(HttpStatusCode.Created, ii);
string uri = Url.Link("DefaultApi", new { id = ii.ID });
response.Headers.Location = new Uri(uri);
return response;
}
...and by replacing all but the first "?" in the URI passed in with "&"
In my WebAPI project I have some problems with redirects. This is because the Uri.ToString() method behaves in a "defensive" way, in oter words, once the mentione method is called he decodes the safe parts of the query string.
Consider this failing unit test:
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace UriTest
{
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
// Arrange
const string expectedUrlRaw =
"http://localhost/abc?proxy=http%3A%2F%2Ftarget.nl%3Fparam1%3Dvalue1%26param2%3Dvalue2";
const string expectedUrlInHttpsRaw =
"https://localhost/abc?proxy=http%3A%2F%2Ftarget.nl%3Fparam1%3Dvalue1%26param2%3Dvalue2";
Uri expectedUri = new Uri(expectedUrlRaw);
Uri expectedUriInHttps = new Uri(expectedUrlInHttpsRaw);
// Act
string returnsUriInHttpsRaw = expectedUri.ToHttps().ToString();
// Assert
Assert.AreEqual(expectedUrlInHttpsRaw, returnsUriInHttpsRaw);
}
}
public static class StringExtensions
{
public static Uri ToHttps(this Uri uri)
{
UriBuilder uriBuilder = new UriBuilder(uri);
uriBuilder.Scheme = Uri.UriSchemeHttps;
uriBuilder.Port = 443;
return uriBuilder.Uri;
}
}
}
Now, I can't modify this behavior by constructing my own link from Uri properties as I have no control over it.
In my controller I do respond in the following way to a get message in order to redirect the call:
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.Found);
response.Headers.Location = // my Uri object
This works fine until a certain point. If my redirect Uri contains a query that contains an encoded link, it will return the wrong result. (and this is probably because the Headers.Location is read by calling a ToString on that property.
Does anyone have an idea on how to overcome this problem?
Thanks
Uri.ToString() does decode URL encoded sequences. (like %20=> a white space).
The behavior also changes between different versions of the .net framework.
In short, don't use Uri.ToString(), use Uri.AbsoluteUri or Uri.OriginalString.
See the following article for an in-depth investigation
https://dhvik.blogspot.com/2019/12/uritostring-automatically-decodes-url.html