In ASP.NET Web API (4.6.2) why wrapping response inside Ok does not do content negotiation?
For example
Http Request has the following headers:
Content-Type:application/json and
Accept:application/xml
and i have two methods
[HttpGet]
public IHttpActionResult GetData()
{
return Ok(new { FirstName = "foo", LastName = "bar" });
}
[HttpGet]
public Person GetData2()
{
return new Person { FirstName = "foo", LastName = "bar" };
}
The first method always returns response in Json format. It does not obey the Accept header. The return type of the Ok method is OkNegotiatedContentResult. Which as per the documentation
Represents an action result that performs content negotiation and
returns an HttpStatusCode.OK response when it succeeds
Second method GetData2, however, returns the correct response and obey the Accept header
Update 1
looks like Ok does not do content-negotiation with anonymous types.
if i do
return Ok( new Person(){FirstName="foo", LastName="bar"});
it works
Related
i'm sending an array of Guids to an ASP.NET Core Web app using aurelia-fetch-client, however on the server side the model binder doesn't pick it up and the list of notificationIds is null. However when i make the request through Swagger, or CURL it binds just fine.
I changed the signature of my controller method to accept a list of strings just in case there was something wrong with the GUID formatting, but same issue.
JS
var body = {notificationIds : this.notifications.map(x => x.notificationId) };
console.log("Dismissing All notifications");
await this.httpClient.fetch('http://localhost:5000/api/notifications/clear',
{
method: 'POST',
body: json(body),
headers: {
'Authorization': `Bearer ${localStorage.getItem('access_token')}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Requested-With': 'Fetch'
},
mode: 'cors'
}).then(response => {
if(response.status == 204){
//Success! Remove Notifications from VM
}
else{
console.log(response.status)
}
})
Controller Method
// POST: api/Notifications
[HttpPost]
[Route("clear")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Post([FromBody]List<string> notificationIds)
{
if (notificationIds.IsNullOrEmpty())
{
return BadRequest("No notifications requested to be cleared");
}
var name = User.Claims.ElementAt(1);
await _notificationRepository.Acknowledge(notificationIds, name.Value);
return NoContent();
}
Interesting thing is that Chrome (V62) shows nothing posted.
But Fiddler does
The shape of the object you are passing from JavaScript isn't the same shape of an object you are telling the ASP.NET framework to expect.
There are two ways that you could fix this issue:
Option 1:
In your JavaScript, change your body to var body = this.notifications.map(x => x.notificationId);
Option 2:
Create an object in c# that reflects what you are passing from your JavaScript.
namespace Foo
{
public class Bar
{
public List<string> NotificationIds { get; set; }
}
}
and then update your controller method to the following:
// POST: api/Notifications
[HttpPost]
[Route("clear")]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> Post([FromBody]Bar bar)
{
if (bar.NotificationIds.IsNullOrEmpty())
{
return BadRequest("No notifications requested to be cleared");
}
var name = User.Claims.ElementAt(1);
await _notificationRepository.Acknowledge(bar.NotificationIds, name.Value);
return NoContent();
}
The problem here is that you're not sending a list of GUIDs you are sending an object with a property that contains a list of GUIDs. Either create and use a view model (as described by peinearydevelopment) or accept a dynamic parameter that refers to the json object.
public async Task<IActionResult> Post([FromBody] dynamic json)
{
var notificationIds = json.notifcationIds;
...
I want to know exactly why this is not working:
[HttpPost]
public IHttpActionResult Post(Slack_Webhook json)
{
return Ok(json.challenge);
}
public class Slack_Webhook
{
public string type { get; set; }
public string token { get; set; }
public string challenge { get; set; }
}
The Official Documentation says:
We’ll send HTTP POST requests to this URL when events occur. As soon
as you enter a URL, we’ll send a request with a challenge parameter,
and your endpoint must respond with the challenge value.
This is an example object (JSON) sent by Slack:
{
"token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
"challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
"type": "url_verification"
}
EDIT:
I could write a book on code that does not work in this issue... here's another example that did not work - still no idea what is wrong:
[HttpPost]
public IHttpActionResult Post()
{
var pairs = Request.GetQueryNameValuePairs();
bool isValidToken = false;
string c = "This does not work.";
foreach(var pair in pairs)
{
if (pair.Key == "token")
{
if (pair.Value == "<UNIQUETOKEN>")
{
isValidToken = true;
}
}
if (pair.Key == "challenge")
{
c = pair.Value;
}
}
if (isValidToken == true)
{
return Json(new {challenge = c });
}
else
{
return BadRequest();
}
}
EDIT2:
Very interesting that I get NULL as a response from below code - that means the body of the received POST is empty.. Could anyone with a working Slack-Integration try that out? So their site is wrong, stating the challenge is sent in the body - where else could it be?
// POST: api/Slack
[HttpPost]
public IHttpActionResult Post([FromBody]string json)
{
return Json(json);
}
EDIT3:
This function is used to get the raw request, but there is nothing inside the body - I am out of solutions.. the support of Slack said, they have no idea about ASP.NET and I should ask here on SO for a solution. Here we are again! ;-)
[HttpPost]
public async Task<IHttpActionResult> ReceivePostAsync()
{
string rawpostdata = await RawContentReader.Read(this.Request);
return Json(new StringContent( rawpostdata));
}
public class RawContentReader
{
public static async Task<string> Read(HttpRequestMessage req)
{
using (var contentStream = await req.Content.ReadAsStreamAsync())
{
contentStream.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(contentStream))
{
return sr.ReadToEnd();
}
}
}
}
The result ( as expected ) looks like this:
Our Request:
POST
"body": {
"type": "url_verification",
"token": "<token>",
"challenge": "<challenge>"
}
Your Response:
"code": 200
"error": "challenge_failed"
"body": {
{"Headers":[{"Key":"Content-Type","Value":["text/plain; charset=utf-8"]}]}
}
I think I'm missing something - is there another way to get the body of the POST-Request? I mean, I can get everything else - except the body ( or it says it is empty).
EDIT4:
I tried to read the body with another function I found - without success, returns empty string - but to let you know what I already tried, here it is:
[HttpPost]
public IHttpActionResult ReceivePost()
{
var bodyStream = new
StreamReader(HttpContext.Current.Request.InputStream);
bodyStream.BaseStream.Seek(0, SeekOrigin.Begin);
var bodyText = bodyStream.ReadToEnd();
return Json(bodyText);
}
While trying to solve this I learnt a lot - but this one seems to be so impossible, that I think I will never solve it alone. Thousands of tries with thousands of different functions - I have tried hundreds of parameters and functions in all of WebApi / ASP.NET / MVC / whatever - why is there no BODY? Does it exist? What's his/her name? Where does it live? I really wanna hang out with that parameter if I ever find it, must be hidden at the end of the rainbow under a pot of gold.
If you can use ASP.NET Core 2, this will do the trick:
public async Task<ActionResult> HandleEvent([FromBody] dynamic data)
=> new ContentResult {Content = data.challenge};
According to the official documentation linked to in the OP you have to format your response depending on the content type you return.
It is possible you are not returning the value (challenge) in one of the expected formats.
Once you receive the event, respond in plaintext with the challenge
attribute value. In this example, that might be:
HTTP 200 OK
Content-type: text/plain
3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P
To do the above you would have needed to return your request differently
[HttpPost]
public IHttpActionResult Post([FromBody]Slack_Webhook json) {
//Please verify that the token value found in the payload
//matches your application's configured Slack token.
if (ModelState.IsValid && json != null && ValidToken(json.token)) {
var response = Request.CreateResponse(HttpStatusCode.OK, json.challenge, "text/plain");
return ResponseMessage(response);
}
return BadRequest();
}
Documentation also shows
Or even JSON:
HTTP 200 OK
Content-type: application/json
{"challenge":"3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"}
Which again would have to be formatted a little differently
[HttpPost]
public IHttpActionResult Post([FromBody]Slack_Webhook json) {
//Please verify that the token value found in the payload
//matches your application's configured Slack token.
if (ModelState.IsValid && json != null && ValidToken(json.token)) {
var model = new { challenge = json.challenge };
return Ok(model);
}
return BadRequest();
}
Here's how you can access the data:
[HttpPost]
[Route("something")]
public JsonResult DoSomething()
{
var token = HttpContext.Request.Form["token"];
// Is the same as:
// var token = Request.Form["token"];
return new JsonResult(token);
}
I suggest using a Request Bin for further debugging.
I've read MSDN documentation of JsonRequestBehavior.AllowGet and many answers here at SO. I experimented and I am still confused.
I have the following action method. It works fine if I use POST method in my ajax call. It fails with status 404 (Resource not found) if I use GET method in my ajax call. So, the question is what exactly does the JsonRequestBehavior.AllowGet enum do in this Json method? The MSDN documentation says: AllowGet HTTP GET requests from the client are allowed. (https://msdn.microsoft.com/en-us/library/system.web.mvc.jsonrequestbehavior(v=vs.118).aspx), but then why does it fail when I use GET method in my ajax call? Changing the attribute from HttpPost to HttpGet does not help, it fails with either POST or GET method.
[HttpPost]
public JsonResult Create(Model m)
{
m.Ssn = "123-45-8999";
m.FirstName = "Aron";
m.LastName = "Henderson";
m.Id = 1000;
return Json(m, JsonRequestBehavior.AllowGet);
}
public class Model
{
public int Id { get; set; }
public string Ssn { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Here is my jQuery ajax call:
$(function () {
console.log("hola");
$("button").on("click", function () {
$.ajax({
method: "POST", //Try changing this to GET and see.
url: "Home/Create",
data: { Id: 123, Ssn: "585-78-9981", FirstName: "John", LastName: "Smith" }
})
.done(function (msg) {
alert("Data Saved: " + msg);
});
});
})
A 404 (Resource not found) means the method is not found (and has nothing to do with JsonRequestBehavior).
Change your ajax to use
$.ajax({
url: "/Home/Create", // note leading forward slash
....
or better, use url: '#Url.Action("Create", "Home")', to correctly generate your url.
404's are due to the attributes, nothing to do with "AllowGet" on the JSON.
You need one or the other [HttpVERB] attributes... not both attributes.
This would work if it is your scenario.
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
You should check out this well documented post
AllowGet will simply allow that JSON response to work over the GET scenario without exception. If you do not, you will see this message:
This request has been blocked because sensitive information could be
disclosed to third party web sites when this is used in a GET request.
To allow GET requests, set JsonRequestBehavior to AllowGet.
In ASP.NET WebApi 2, what is the difference between the following:
public <IHttpActionResult> GetItem(Guid id)
{
// ... code ..., Item result = ....
return result;
}
public <IHttpActionResult> GetItem(Guid id)
{
// ... code ..., Item result = ....
return Json(result);
}
public <IHttpActionResult> GetItem(Guid id)
{
// ... code ..., Item result = ....
return Ok(result);
}
This code returning result won't compile, as result doesn't implement IHttpActionResult...
public <IHttpActionResult> GetItem(Guid id)
{
// ... code ..., Item result = ....
return result;
}
Returning Json() always returns HTTP 200 and the result in JSON format, no matter what format is in the Accept header of the incoming request.
public <IHttpActionResult> GetItem(Guid id)
{
// ... code ..., Item result = ....
return Json(result);
}
Returning Ok() returns HTTP 200, but the result will be formatted based on what was specified in the Accept request header.
public <IHttpActionResult> GetItem(Guid id)
{
// ... code ..., Item result = ....
return Ok(result);
}
Just an addition to previous explanations:
The return types for your fuctions are: IHttpActionResult
Therefore the expectation is for the method to return a IHttpActionResult which is an interface for HttpResponseMessage. The HttpResponseMessage has useful properties such as Headers, Content, and Status code.
Therefore, Ok(result) returns a HttpResponseMessage with Ok status code and the contents, which in this case is the result. Meanwhile, Json(result) converts the object to json format, aka serialization, and that gets placed as the content in the HttpResponseMessage.
The best thing about a web api with ASP.NET is that it creates simple ways to pass the Http Responses through abstraction. The worst thing, is it takes a bit of understanding before actually using the relatively simple methods.
Here is more info about serilization and json
Here is more about info about IHttpActionResult
I have a REST API that until now always returned JSONP (JSON data wrapped in whatever function call client wanted):
static final String JAVASCRIPT = "application/javascript;charset=UTF-8";
#RequestMapping(value = "/matches", produces = JAVASCRIPT)
#ResponseBody
public String matches(#RequestParam String callback) {
String json = jsonService.getCachedJson("/matches");
return toJsonp(callback, json);
}
Now, things have changed so that I need to return either JSON or JSONP: if client provides a callback function name, we return JSONP and otherwise pure JSON.
With regards to content type, I'd like to be as correct as possible and use application/json for JSON and application/javascript for JSONP.
So, something like this:
#RequestMapping(value = "/matches")
#ResponseBody
public String matches(#RequestParam(required = false) String callback) {
String json = jsonService.getCachedJson("/matches");
// TODO: if callback == null, set content type to "application/json",
// otherwise to "application/javascript"
return jsonOrJsonp(callback, json);
}
String jsonOrJsonp(String callback, String json) {
return Strings.isNullOrEmpty(callback) ? json : toJsonP(callback, json);
}
Looks like I can no longer use produces attribute of #RequestMapping. What's the simplest way to set content type with Spring MVC in the scenario above?
I'd like to avoid defining HttpMessageConverters (or other Spring hassle) or changing the method return type, if at all possible! And obviously I wouldn't like duplicated method declarations where produces value is the only significant difference. What I'm looking for is minimal changes to the above code.
Latest Spring (3.2.3).
Have you tried just using two request handler methods?
#RequestMapping(value = "/matches", produces = JAVASCRIPT, params="callback")
#ResponseBody
public String Jsonp(#RequestParam String callback) {
return toJsonp(callback, jsonService.getCachedJson("/matches"));
}
#RequestMapping(value = "/matches", produces = JSON)
#ResponseBody
public String json() {
return toJson(jsonService.getCachedJson("/matches"));
}
The first method with the params parameter will only be mapped to requests where the callback param is present.