I have the following code:
[HttpPost]
public async Task<ReturnStatus> Delete([FromBody]int id)
{
await new BusinessLogic.Templates().DeleteTemplate(id);
return ReturnStatus.ReturnStatusSuccess();
}
When I run this as an AJAX request, the id is null. I've inspected the data coming in through Fiddler and the body is:
{"id":"11"}
The header has Content-Type: application/json; charset=UTF-8.
If I modify the code slightly to
[HttpPost]
public async Task<ReturnStatus> Delete([FromBody]string id)
{
await new BusinessLogic.Templates().DeleteTemplate(Convert.ToInt64(id));
return ReturnStatus.ReturnStatusSuccess();
}
it works just fine.
What am I doing wrong here?
Please read this part, number 3 in particular:
http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/
3. [FromBody] parameters must be encoded as =value
(quoting the section for future reference:)
There are two ways to make jQuery satisfy Web API’s encoding requirement. First, you can hard code the = in front of your value, like this:
$.post('api/values', "=" + value);
Personally, I’m not a fan of that approach. Aside from just plain looking kludgy, playing fast and loose with JavaScript’s type coercsion is a good way to find yourself debugging a “wat” situation.
Instead, you can take advantage of how jQuery encodes object parameters to $.ajax, by using this syntax:
$.post('api/values', { '': value });
Related
I am trying to understand how to short-circuit my controller method in case content negotiation is going to fail in ASP.NET 6.
Suppose I have this method:
[Consumes("application/json")]
[Produces("application/json")]
public async Task<IActionResult> Something()
{
var result = await do_a_bunch_of_expensive_work();
return Ok(result);
}
And I call it with "Accept: application/xml". This returns, as expected, 406/Not Acceptable.
The problem is that the mismatch between Accept and Produces attribute is determined when the result is prepared (last line, after the expensive work), but I don't want to do the "do_a_bunch_of_expensive_work" in cases when it's going to end up in a 406 anyway.
Do I need to read the value of the Produces attribute and match it against the Accept header myself before doing the expensive work, or is there a more elegant way I'm missing?
This is an example of what I want to achieve, however I want to do my own custom attribute that also feeds itself from something other than the request url. In the case of HttpGet/HttpPost these built-in attributes obviously have to look at the http request method, but is there truly no way to make Url.Action() resolve the correct url then?
[HttpGet("mygeturl")]
[HttpPost("myposturl")]
public ActionResult IndexAsync()
{
// correct result: I get '/mygeturl' back
var getUrl = Url.Action("Index");
// wrong result: It adds a ?method=POST query param instead of returning '/myposturl'
var postUrl = Url.Action("Index", new { method = "POST" });
return View();
}
I've looked at the aspnet core source code and I truly can't find a feature that would work here. All the LinkGenerator source code seems to require routedata values but routedata always seems to require to be in the url somewhere, either in the path or in the query string. But even if I add the routedata value programmatically, it won't be in time for the action selection or the linkgenerator doesn't care.
In theory what I need is to pass something to the UrlHelper/LinkGenerator and have it understand that I want the url back out that I defined in my custom attribute, in this case the HttpPost (but I'll make my own attribute).
In ASP.Net Core you have multiple ways to generate an URL for controller action, the newest being tag helpers.
Using tag-helpers for GET-requests asp-route is used to specify route parameters. It is from what I understand not supported to use complex objects in route request. And sometimes a page could have many different links pointing to itself, possible with minor addition to the URL for each link.
To me it seems wrong that any modification to controller action signature requires changing all tag-helpers using that action. I.e. if one adds string query to controller, one must add query to model and add asp-route-query="#Model.Query" 20 different places spread across cshtml-files. Using this approach is setting the code up for future bugs.
Is there a more elegant way of handling this? For example some way of having a Request object? (I.e. request object from controller can be put into Model and fed back into action URL.)
In my other answer I found a way to provide request object through Model.
From the SO article #tseng provided I found a smaller solution. This one does not use a request object in Model, but retains all route parameters unless explicitly overridden. It won't allow you to specify route through an request object, which is most often not what you want anyway. But it solved problem in OP.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Context.GetQueryParameters()" asp-route-somestring="optional override">Link</a>
This requires an extension method to convert query parameters into a dictionary.
public static Dictionary GetQueryParameters(this HttpContext context)
{
return context.Request.Query.ToDictionary(d => d.Key, d => d.Value.ToString());
}
There's a rationale here that I don't think you're getting. GET requests are intentionally simplistic. They are supposed to describe a specific resource. They do no have bodies, because you're not supposed to be passing complex data objects in the first place. That's not how the HTTP protocol is designed.
Additionally, query string params should generally be optional. If some bit of data is required in order to identify the resource, it should be part of the main URI (i.e. the path). As such, neglecting to add something like a query param, should simply result in the full data set being returned instead of some subset defined by the query. Or in the case of something like a search page, it generally will result in a form being presented to the user to collect the query. In other words, you action should account for that param being missing and handle that situation accordingly.
Long and short, no, there is no way "elegant" way to handle this, I suppose, but the reason for that is that there doesn't need to be. If you're designing your routes and actions correctly, it's generally not an issue.
To solve this I'd like to have a request object used as route parameters for anchor TagHelper. This means that all route links are defined in only one location, not throughout solution. Changes made to request object model automatically propagates to URL for <a asp-action>-tags.
The benefit of this is reducing number of places in the code we need to change when changing method signature for a controller action. We localize change to model and action only.
I thought writing a tag-helper for a custom asp-object-route could help. I looked into chaining Taghelpers so mine could run before AnchorTagHelper, but that does not work. Creating instance and nesting them requires me to hardcode all properties of ASP.Net Cores AnchorTagHelper, which may require maintenance in the future. Also considered using a custom method with UrlHelper to build URL, but then TagHelper would not work.
The solution I landed on is to use asp-all-route-data as suggested by #kirk-larkin along with an extension method for serializing to Dictionary. Any asp-all-route-* will override values in asp-all-route-data.
<a asp-controller="Test" asp-action="HelloWorld" asp-all-route-data="#Model.RouteParameters.ToDictionary()" asp-route-somestring="optional override">Link</a>
ASP.Net Core can deserialize complex objects (including lists and child objects).
public IActionResult HelloWorld(HelloWorldRequest request) { }
In the request object (when used) would typically have only a few simple properties. But I thought it would be nice if it supported child objects as well. Serializing object into a Dictionary is usually done using reflection, which can be slow. I figured Newtonsoft.Json would be more optimized than writing simple reflection code myself, and found this implementation ready to go:
public static class ExtensionMethods
{
public static IDictionary ToDictionary(this object metaToken)
{
// From https://geeklearning.io/serialize-an-object-to-an-url-encoded-string-in-csharp/
if (metaToken == null)
{
return null;
}
JToken token = metaToken as JToken;
if (token == null)
{
return ToDictionary(JObject.FromObject(metaToken));
}
if (token.HasValues)
{
var contentData = new Dictionary();
foreach (var child in token.Children().ToList())
{
var childContent = child.ToDictionary();
if (childContent != null)
{
contentData = contentData.Concat(childContent)
.ToDictionary(k => k.Key, v => v.Value);
}
}
return contentData;
}
var jValue = token as JValue;
if (jValue?.Value == null)
{
return null;
}
var value = jValue?.Type == JTokenType.Date ?
jValue?.ToString("o", CultureInfo.InvariantCulture) :
jValue?.ToString(CultureInfo.InvariantCulture);
return new Dictionary { { token.Path, value } };
}
}
I have a WebAPI method which takes in 3 parameters, two of them primitive data types and the 3rd one is a complex data type:
public HttpResponseMessage validateUser(string elementName, string checkPermission, List<AccessElement> accessGroups)
I'm making call to this WebAPI using Angular $http:
return $http({
method: 'get',
url: serviceUrlPrefix + '/api/v1/validateUser',
params: { 'elementName': CONSTANTS.UNAUTH_DATA_UI, 'checkPermission': CONSTANTS.CAN_READ , 'accessGroups': accessGroups }
})
Problem:
When $http request is made, the query string values are truncated as the complex datatype parameter accessGroups is relatively long. I read in one of the blogs that query string limit in IE is 2083 characters
Question 1:
In my scenario, I won't say that acessGroups object is too big as it is a collection of 10 records (having 4 columns). With the query string limit on each browser, its quite understandable that I would face this truncation very easily when we pass the complex data type. So if it is the case, I would like to understand what is the primary use of [FromUri].
Question 2:
I was able to get around this issue by making the controller method as
public HttpResponseMessage validateUser(string elementName, string checkPermission, [FroimUri]List<AccessElement> accessGroups)
And made a POST call to this method with "data" parameter
return $http({
method: 'post',
url: serviceUrlPrefix + '/api/v1/validateUser',
params: { 'elementName': CONSTANTS.UNAUTH_DATA_UI, 'checkPermission': CONSTANTS.CAN_READ },
data: { 'accessGroups': accessGroups }
})
What is the drawback in this approach, since I'm going for "POST" method from a normal "Get". Would it cause any additional overhead?
I believe the only drawback is not adhering to the REST design principles (POST should be used to create a resource according to REST paradigm).
In practical terms, I would use [FromBody] tag. That way, you can be certain you won't run into any length restrictions.
I'm learning aspnet mvc 4 web api, and find it very easy to implement by simply returning the object in the apicontrollers.
However, when I try to return value types such as bool, int, string - it does not return in JSON format at all. (in Fiddler it showed 'true/false' result in raw and webview but no content in JSON at all.
Anyone can help me on this?
Thanks.
Some sample code for the TestApiController:
public bool IsAuthenticated(string username)
{
return false;
}
Some sample code for the jQuery usage:
function isAuthenticated(string username){
$.getJSON(OEliteAPIs.ApiUrl + "/api/membership/isauthenticated?username="+username,
function (data) {
alert(data);
if (data)
return true;
else
return false;
});
}
NOTE: the jquery above returns nothing because EMPTY content was returned - however if you check it in fiddler you can actually see "false" being returned in the webview.
cheers.
Before your callback function is called, the return data is passed to the jquery parseJSON method, which expects the data to be in the JSON format. jQuery will ignore the response data and return null if the response is not formatted correctly. You have two options, wrap you return boolean in a class or anonymous type so that web api will return a JSON object:
return new { isAuthentication = result }
or don't use getJSON from jQuery since you're not returning a properly formatted JSON response. Maybe just use $.get instead.
Below is a quote for the jQuery documentation:
Important: As of jQuery 1.4, if the JSON file contains a syntax error,
the request will usually fail silently. Avoid frequent hand-editing of
JSON data for this reason. JSON is a data-interchange format with
syntax rules that are stricter than those of JavaScript's object
literal notation. For example, all strings represented in JSON,
whether they are properties or values, must be enclosed in
double-quotes. For details on the JSON format, see http://json.org/.