Why FromBody Behave like this ? - asp.net-web-api

I want to understand why FromBody behave like this for common request.
Case 1:
Controller
public void Post([FromBody]string value)
{
}
POSTMAN Request
Problem
In this case value is my post method will not bind to value parameter to test.
Case 2:
Model
public class Test
{
public string value { get; set; }
}
Controller
public void Post([FromBody]Test model)
{
}
POSTMAN Request
Same as Case 1
Result :
Now model value property successfully bind to "test".
Why it is behave like this ?

Because that's the way the API was designed. Note that in your second example you could have elided [FromBody] and WebApi would have searched the body for the data anyways because Test is a complex type.
This article explains the rules by which WebApi binds data:
http://blogs.msdn.com/b/jmstall/archive/2012/04/16/how-webapi-does-parameter-binding.aspx
EDIT: As commented, the article linked above clearly states you should be able to do
void Get([FromBody]string value)
This is true, but it means you can read the value of the body as plain text into the parameter value. This means if you go this route, you MUST use the raw option (from postman) and either clear Content-Type or make sure it's set to text/plain.
In other words, on Postman, if you set the send method to raw, and type in "this is a sentence" as is in the text area provided, your value, when the issue below is fixed, will be "this is a sentence".
Now, if you tried doing this from a fresh template, you will run into this error:
{
"Message": "The request entity's media type 'text/plain' is not supported for this resource.",
"ExceptionMessage": "No MediaTypeFormatter is available to read an object of type 'String' from content with media type 'text/plain'.",
"ExceptionType": "System.Net.Http.UnsupportedMediaTypeException",
"StackTrace": " at System.Net.Http.HttpContentExtensions.ReadAsAsync[T](HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n at System.Net.Http.HttpContentExtensions.ReadAsAsync(HttpContent content, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)\r\n at System.Web.Http.ModelBinding.FormatterParameterBinding.ReadContentAsync(HttpRequestMessage request, Type type, IEnumerable`1 formatters, IFormatterLogger formatterLogger, CancellationToken cancellationToken)"
}
This article explains how to allow WebApi to accept text/plain data from the body:
https://myadventuresincoding.wordpress.com/2012/06/19/c-supporting-textplain-in-an-mvc-4-rc-web-api-application/
It's kind of stupid that the default ValuesController exposes a method with that signature, but does not provide a MediaTypeFormatter for text/plain, but I guess that's Microsoft for you.

Given this method:
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
Values.Add(value);
}
Postman must be configured correctly in order for our value to bind to its C# string object...one way to ensure this works in our case is to send our server a JSON request. Follow these steps:
Make sure you've selected POST.
Make sure you've entered an appropriate URL, i.e. http://localhost:1337/api/values.
Go to Body, tick the option for raw.
In the drop-down to the right, select JSON (application/json).
In the text area of Postman, insert only "Hello, world." (with the quotes as well, otherwise it won't be valid JSON).
It should now work once you hit Send.

For JSON you can do this:
public HttpResponseMessage Webhook(JToken json)
{
}
Where JToken is Newtonsoft.Json.Linq.JToken.
JObject works if the data is an actual JSON object (not an array)

Related

AWS Lex receives Invalid Response from lambda function - Can not construct instance of IntentResponse

Using Java8 in eclipse AWS SDK, I've created and uploaded a lambda function that is hooked in upon fulfillment of my lex intent.
Lambda has not problem receiving JSON request and parsing.
Then, I format a simple "Close" dialogAction response and send back to lex and receive the following error from the Test Bot page in the lex console:
An error has occurred: Received invalid response from Lambda:
Can not construct instance of IntentResponse:
no String-argument constructor/factory method to deserialize
from String value
('{"dialogAction
{"type":"Close","fulfillmentState":"Fulfilled","message":
{"contentType":"PlainText","content":"Thanks I got your info"}}}')
at [Source: "{\"dialogAction\":
{\"type\":\"Close\",\"fulfillmentState\":\"Fulfilled\",\"message\":
{\"contentType\":\"PlainText\",\"content\":\"Thanks I got your
info\"}}}";line: 1, column: 1]
It seems to have a problem right away with the format (line 1, column 1), but my JSON string looks ok to me. Before returning the output string in the handleRequest java function, I am writing the it to the Cloudwatch log and it writes as follows:
{
"dialogAction": {
"type": "Close",
"fulfillmentState": "Fulfilled",
"message": {
"contentType": "PlainText",
"content": "Thanks I got your info"
}
}
}
Things I've tried:
Removing the message element as it's not required
Adding in non-required properties like sessionAttributes,
responseCard, etc
removing the double quotes
replacing double quotes with single quotes
hardcoding json from sample response format message in documentation
Is there something hidden at the http headers level or is java8 doing something to the JSON that is not visible?
Not sure if this is because I'm using Java8 or not, but a return value of "String" from the RequestHandler class handleRequest method will not work.
Yes, String is an object, but the constructors on the Lex side are expecting an "Object". I was converting my lex response POJO to a String before returning it in the handleRequest method. That was my mistake.
I fixed it by changing the return type of the handleRequest method to be "Object" instead of "String".
public Object handleRequest(Object input, Context context)
instead of
public String handleRequest(Object input, Context context)
You also have to implement the
public class LambdaFunctionHandler implements RequestHandler<Object, Object>
not
public class LambdaFunctionHandler implements RequestHandler<Object, String>
This solved my issue.
In my case I was facing exactly the same issue and was able to fix it by creating specific response POJO type and using this POJO as the return type for 'handleRequest' method. E.g. BotResponse.java as follow:
public class BotResponse implements Serializable{
private static final long serialVersionUID = 1L;
public DialogAction dialogAction = new DialogAction();
public DialogAction getDialogAction() {
return dialogAction;
}
public void setDialogAction(DialogAction dialogAction) {
this.dialogAction = dialogAction;
}
}
Note, I have also added the 'implements Serializable' just to be on safer side. Probably it is an overkill.
Not sure why but for me returning a well formatted JSON String object did not worked even after changing the return type of 'handleRequest' method to 'Object'.
I know this is an old question however thought this might help some else
#Mattbob Solution dint fix my issue, However he is in the right path. Best approach is to use a Response object, a custom response object and make the lambda return the custom response object. So i went to the Documentation and created a custom object that looks Response format here
http://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html
At the time of answering question i couldnt find an object in SDK that matched the response Object so i had to recreate but if some one knows please comment below
Class xxxxx implements RequestHandler<Object, AccountResponse> {
#Override
public AccountResponse handleRequest(Object input, Context context) {
}
}
Lambda will look somewhat like this and just populate and return the object to match response structure and error goes away. Hope this helps.
Whenever we are returning the object to the bot from the backend make sure we need to pass content type along with content. But here we are passing wrong. So wE need to pass as like below. It is in Node.js
let message = {
contentType: "PlainText",
content: 'Testing bot'
};

405 when using AttributeRouting.PUTAttribute unless I also include HttpPutAttribute

We have an MVC project that I am attempting to update to include WebApi. In order to get the required routes we are using AttributeRouting. All the calls seem to be routing correctly except for [PUT] which returns a 405. I have simplified the controller and actions and still receive the error with the [PUT] unless I include [HttpPut] also. Not sure what I am missing.
[RoutePrefix("api/Sites")]
public class SitesController : BaseApiController
{
[POST("")]
public bool CreateSite(SiteSignupArgs args)
{
...
}
[GET("Statuses")]
public IList<SiteAuditViewModel> GetStatuses()
{
...
}
[PUT("Statuses/{siteId}")]
[HttpPut] // This is required or 405 is returned
public HttpResponseMessage UpdateStatus(string siteId, UpdateStatusArgs args)
{
...
}
[DELETE("Statuses/{siteId}")]
public HttpResponseMessage Delete(string siteId)
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Version 3.5.6 of AttributeRouting.Core, AttributeRouting.Core.Http, AttributeRouting.Core.Web, AttributeRouting.WebApi
MVC4
WebDAV is not installed.
What you are seeing is an expected behavior. Action Selector in Web API by default assumes the action to be of verb POST if the action name does not have a prefix with verbs like "Get", "Post", "Put", "Delete" etc.
Now it isn't working even if you have specified [PUT("Statuses/{siteId}")] attribute explicitly because, Action selector looks for attributes from System.Web.Http namespace like HttpGetAttribute, HttpPostAttribute, HttpPutAttribute etc.
Since AttributeRouting's PUTAttribute isn't of the above types, Action selector doesn't consider it and still thinks it to be the default one, which is POST. So your workaround of having HttpPut attribute is correct.

MediaTypeFormatter WriteToStreamAsync not called unless I add to Accept headers

I have a MediaTypeFormatter that converts an internal rep of an image to a png/jpeg/etc. if someone asks for it. However, my WriteToStreamAsync never gets called unless I add an image/png or similar to the accept headers.
First, here is my webapi method, with some key bits removed for brevity:
public ImageFormatter.BinaryImage GetImage(int cId, int iId)
{
....
using (var input = iFIO.OpenRead())
{
input.Read(b.data, 0, (int)iFIO.Length);
}
// With this next line my mediatypeformatter is correctly called.
Request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("image/png"));
return b;
}
And here is the write portion of my MediaTypeFormatter (there is also a read portion, and that works great, actually).
namespace PivotWebsite.MediaFormatters
{
public class ImageFormatter : MediaTypeFormatter
{
public class BinaryImage
{
public byte[] data;
public string metaData;
}
public ImageFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpg"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/jpeg"));
SupportedMediaTypes.Add(new MediaTypeHeaderValue("image/png"));
}
public override bool CanWriteType(Type type)
{
return true;
}
public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext)
{
var b = value as BinaryImage;
if (b == null)
throw new InvalidOperationException("Can only work with BinaryImage types!");
await writeStream.WriteAsync(b.data, 0, b.data.Length);
}
}
}
What I expected to be able to do was, in WriteToStreamAsync, to alter the outgoing headers to include Content-Type as "image/png" (or whatever, depending on the data type).
However, when I call this from a web browser with a URL like "http://my.testdomain.net:57441/api/Images?cID=1&iID=1", the WriteToStreamAsync never gets called (accepted headers are listed as {text/html, application/xhtml+xml, */*}). If I add the line above that adds the proper image type, then everything is called as I would expect.
What am I missing here? The accepted header of "*/*" should have triggered my media formatter, right? Or... am I missing something basic about the plumbing in Web API.
Do you want the image formatter to always get used if the Accept header is "/"? If that's the case, then you should insert your formatter first in the Formatters collection like this:
config.Formatters.Insert(0, new ImageFormatter());
What happens when there isn't an exact Accept header match like in your case is that the first formatter that can write the type gets selected to serialize the object. So if you register your formatter first, it would get used.
This could have unintended side-effects because it would affect all your controllers though. I would suggest changing the CanWriteType implementation to only return true if it's a BinaryImage. That should make the formatter only get used when that's your return type.
Another thing you could do is select the formatter directly in your action by returning an HttpResponseMessage:
return Request.CreateResponse(HttpStatusCode.OK, image, new ImageFormatter());
That's basically saying "this action should always use the image formatter, regardless of content-type, accept headers etc". That might be reasonable in your case if you're always just returning an image and you need it serialized with your formatter.
I'm writing a CsvFormatter and I want to be able to call the API from the browser to trigger a file download. Since I didn't have control over the Accept header, I wanted to use an extension to trigger my CSV formatter, but the XML formatter kept getting the request. I found that by adding a "text/html" media type, I could handle the CSV extension. Hopefully this doesn't cause other problems down the line :).
public CsvFormatter()
{
var header = new MediaTypeHeaderValue("text/csv");
SupportedMediaTypes.Add(header);
MediaTypeMappings.Add(new UriPathExtensionMapping("csv", header));
// From Chrome: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
// Allow the formatter to run from a standard browser request.
header = new MediaTypeHeaderValue("text/html");
SupportedMediaTypes.Add(header);
MediaTypeMappings.Add(new UriPathExtensionMapping("csv", header));
}

AJAC MVC3 Request object and raw Ajax data, where the heck is it?

If this was a regular post of a form I could go to Request.Form['somevalue'] and get the value. If this was a get with a query string I could go to Request.QueryString["somevalue"] and get the value.
Where is the raw data when you post an ajax request. I need a value out of the raw data string in a filter method.
Any help will be appreciated!!
Edits below:
public class ValidateAntiForgeryId : FilterAttribute, IAuthorizationFilter {
public void OnAuthorization(AuthorizationContext filterContext) {
if (filterContext == null) {
throw new ArgumentNullException("filterContext");
}
Guid filterGuid;
Guid.TryParse(filterContext.RequestContext.HttpContext.Request.Form["__sessionId"], out filterGuid);
if (filterGuid == Guid.Empty)
throw new AuthenticationException("Authentication failure");
try {
var cookieGuid = (Guid)filterContext.RequestContext.HttpContext.Items["SessionId"];
} catch {
throw new AuthenticationException("Authentication failure");
}
}
The posted data looks like this:
{"SsnLastFour":"2222","AccountNumber":"B112233","__sessionId":"dca0a504-3c40-4118-ae19-afefb9bfc8bd"}
I need access to the __sessionId chunk inside the filter.
There's nothing magic about AJAX posts. They're just plain old HTTP. That means you have plain old HTTP post values, and/or plainold HTTP Get values.
If you're not seeing them, it probably means you're not actually submitting them.
EDIT:
Two issues you did not include in your original question: 1) That this is JSON, and 2) That this is in an AuthorizationFilter (rather than an action method).
Both change the answers. Since ASP.NET does not natively understand JSON post values, you will have to parse them, via Request.InputStream. MVC3 by default has a JSON model binder, but AuthorizationFilters execute before model binders do, so you will be accessing things prior to the model binders being executed, and as such FormsCollection won't be populated (Request.Form[] won't work either, because as I said, asp.net doesn't natively understand JSON).
You may find that installing JSON.net via nuget may help with this task. Or you might just write a simple parse routine, since you know exactly what you're looking for.
You can accept the parameter values the same way you accept in normal form post.
Ex :
$.get("User/Get", { userId : "24"} ,function(data){
alert(data);
});
or
$("#yourDivId").load("User/Get?userId=23");
Your action method should look like
public ActionResult Get(int userId)
{
// you have value present in userId
if(Request.IsAjax())
{
return View("Partial/MyPartialView");
}
return View();
}
One thing you have to remember is, the parameter name of your action method should be same as of what your parameter/querystring name.
The fitlerContext has an ActionParameters collection which should have the parsed JSON properties (in case that helps). This may be easier than parsing the InputStream.
var sessionId = filterContext.ActionParameters["__sessionId"];

Asp.Net MVC : Execution of the child request failed. Please examine the InnerException for more information

I'm recieving the following error message,
A public action method 'RenderMenu'
was not found on controller
'Web.Controllers.SiteController'.
However this action DOES exist and the controller does exist (As it work everywhere on the site) I looked at the inner exception.
Execution of the child request failed.
Please examine the InnerException for
more information.
(This is the inner exception...)
Stack Trace
at
System.Web.Mvc.HttpHandlerUtil.ServerExecuteHttpHandlerWrapper.Wrap[TResult](Func`1
func) at
System.Web.HttpServerUtility.ExecuteInternal(IHttpHandler
handler, TextWriter writer, Boolean
preserveForm, Boolean setPreviousPage,
VirtualPath path, VirtualPath
filePath, String physPath, Exception
error, String queryStringOverride)
Now, we have a website set-up with a dynamic menu system so we are using RenderAction() on a generic controller to build this menu system up.
<% Html.RenderAction("RenderMenu", "Site"); %>
This call is made from the MasterPage and it works fine until there was a validation error like so,
[HttpPost]
public ActionResult Register(UserModel UserToAdd)
{
if(!ModelState.IsValid)
{
return View(UserToAdd);
}
//Run some validation
if (_UserService.DoesEmailExist(UserToAdd.EMail))
{
TempData["error"] = "Email Address Already in use!";
return View(UserToAdd);
}
//Add the user
TempData["info"] = "User Added - " + UserO.ID;
return View("Success");
}
It works fine when there this is a new user, but if someone enters an email that already exist we get the above error. THis RenderAction Method works all over the site (This is the first form we have added)
Any suggestions?
Fixed:
The RenderAction() Method is below
[HttpGet]
public ActionResult RenderMenu()
{
//Do Stuff
}
Removing the HttpGet Attribute has resolved the issue.
public ActionResult RenderMenu()
{
//Do Stuff
}
Would love to know why?
This is because your parent request is an [HttpPost], and the child request operates in the same verb as the parent. If your method is marked as [HttpGet], it will not respond to [HttpPost] requests. Hitting the action directly through your browser works because that is a GET. Hitting the action as a child action in the context of a POST will not work.

Resources