I wrote the following method to try to diagnose a problem I'm experiencing in my application:
[Route("/api/flow/test"), HttpPost]
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Test(string id, [FromBody] JToken input)
{
var result = input == null ? "Not OK" : "OK";
return Ok(result);
}
I post a large (6.5MB+) JSON body to it, and in one instance it works fine. When I post a similar JSON with some added properties, it does not - the input parameter comes in as null. However, both JSON validate successfully with every tool I've found that can handle their size. Please provide some additional ideas about how to further investigate what is causing the body parameter input to be treated as null.
One option would be to declare it as string instead of JToken and try to explicitly parse it in the action body.
This will show you two things:
If input is still empty, it's not a problem with JSON parsing
If parsing it explicitly fails, the JSON is indeed invalid.
Only when input is not null and parsing it in the body works - only then you need to deep dive into what actually happens, when you declare an action parameter as JToken.
Related
I have a SpecFlow test definition set up where I want to assert that the response have a specific collection of fields in its response. For example I have this particular expected response from the API:
{
isActive: false,
lastProcessed: "2020-11-03T19:03:16.537"
}
What I want to verify is that the response contains those two fields, not necessarily the value those fields contain. I tried the following method:
Assert.NotNull(response.Model.isActive);
Assert.NotNull(response.Model.lastProcessed);
But I'm getting an error using this method:
Do not use Assert.NotNull() on value type 'bool'
How else can I make sure the response structure is as expected other than using "NotNull()"?
The Solution:
Following the accepted answer, I serialized the model returned from the API call into JSON and parsed it into a JObject. Then I used the ContainsKey() method to assert it.
JObject jObject = JObject.Parse(JsonConvert.SerializeObject(response.Model));
Assert.True(jObject.ContainsKey("isActive"));
I don't know what packages you use for sending requests and deserialization but if you could get the response content as a raw json string you could then use Newtonsoft.JSON to parse the response into a JObject with JObject.Parse(responseContent). JObject has a method called ContainsKey(propertyName) which determines whether there is a field of a specified name in the object. You could assert if it returns true for the desired property names.
Edit
Regarding Greg's answer below. The original error is in fact caused by bool not being a nullable type and making it nullable in the model would fix the error. However, this solution is not ideal. In some cases null can be a valid value returned by the API and this would generate false negatives. e.g. if we recieved:
{
isActive: null,
lastProcessed: "2020-11-03T19:03:16.537"
}
then Assert.NotNull(response.Model.isActive) would yield a negative test result even though the field is present in the json, and that's what we wanted to check.
So theoretically if we are 100% sure that null will never be returned by the API itself, then we could do it that way, but it won't be a universal method. Also not very descriptive of what we are trying to achieve ;)
Since the isActive property is a bool you'll need to assert that it is false. If instead you want a true or false value, and then something to represent that it is missing, use a nullable boolean instead in your DTO:
public class YourDTO
{
public bool? isActive { get; set; }
...
}
Then you can assert isActive is null, true or false.
Alternative: If you cannot update the original data transfer object, then this might be a good use case for writing your own code to call the web service and map the JSON response to your own DTO used just for your tests.
This could be a large amount of work, however. The advantage is your test code is truly decoupled from the code it tests. I've done this with applications that use a database as well. It is extra work, but it allows your tests to use whatever data structure makes sense for the test.
I have a request that my Web is receiving - from looking at a wireshark I can see that the form contains two parts.
The first part is type application/json and is called metadata and the second part is audio/wav and is called media_audio_wav, and both have content-disposition set to form-data. I do not control the request coming in so I need to make it work with what I'm getting.
I have a controller method declared like this:
public async Task<IActionResult> MyControllerMethod([FromForm] MyModelClass jsonInput)
{
await _myClass.DoSomeBusinessLogic(jsonInput);
return Ok();
}
In the above, MyModelClass is a class that the JSON part successfully binds to. What I want to figure out is how to get the byte[] field called media_audio_wav. I have tried adding a second [FromForm] parameter and that didn't seem to work. I have also tried simply declaring a field in my model class that is of type byte[] with the proper name and that didn't work either.
If I declare a field in my model class of type string, the .NET model binder will bind to it, but because the data is binary, when it is converted to the string the audio comes out corrupted since strings cannot represent binary data properly. Because of this, I need to get the raw binary without first turning it into a string. I can't use IFormFile because there is not filename specified on the binary data.
The standard way AFAIK to return data in ASP.NET Core Web Api is by using IActionResult and providing e.g. an OkObject result. This works fine with objects, but what if I have obtained a JSON string somehow, and I just want to return that JSON back to the caller?
e.g.
public IActionResult GetSomeJSON()
{
return Ok("{ \"name\":\"John\", \"age\":31, \"city\":\"New York\" }");
}
What ASP.NET Core does here is, it takes the JSON String, and wraps it into JSON again (e.g. it escapes the JSON)
Returning plain text with [Produces("text/plain")] does work by providing the "RAW" content, but it also sets the content-type of the response to PLAIN instead of JSON. We use [Produces("application/json")] on our Controllers.
How can I return the JSON that I have as a normal JSON content-type without it being escaped?
Note: It doesn't matter how the JSON string was aquired, it could be from a 3rd party service, or there are some special serialization needs so that we want to do custom serialization instead of using the default JSON.NET serializer.
And of course a few minutes after posting the question I stumble upon a solution :)
Just return Content with the content type application/json...
return Content("{ \"name\":\"John\", \"age\":31, \"city\":\"New York\" }", "application/json");
In your action, replace Ok() with the Content() method, which lets you set the content (raw content), content type, and status code of your response: https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.contentresult?view=aspnetcore-2.0
This worked for me, where Json() did not:
return new JsonResult(json);
I'm working on a MVC web application. I need to download a file which I've stored as a byte[] stream in DB and its working fine. What I used to do on a button click I call a JS function and that calls a function in the C# backend and eventually download the file. Following is my JQuery code.
var DownloadDRR = function ()
{
var InvoiceId = $(".invoiceid").text();
location.href = location.origin + "/Invoicing/DownloadDRR?InvoiceId=" + InvoiceId;
}
And in the backend I normally get query string like this
Request.Querystring("InvoiceId");
But accidental I've discovered in my application if I write the following it still gets the InvoiceId without using Request.QueryString().
public FileResult DownloadDRR(int InvoiceId)
{
InvoicingService Invoices = new InvoicingService(Client);
byte[] RawExcel = Invoices.GetExcelService(InvoiceId);
MemoryStream stream = new MemoryStream(RawExcel);
stream.Seek(0, SeekOrigin.Begin);
return File(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "test.xlsx");
}
Can anyone explain why please?
MVC specifically automates a lot of that binding (model binding is the term used).
myurl.com/MyController/MyMethod?something=a&anotherthing=1234
//example 1
public ActionResult MyMethod(string something, string anotherthing)
//example2
public ActionResult MyMethod(string something, int anotherthing)
It works for both examples. Even though your querystring technically only contains string values, MVC will try to parse it to the desired type.
The only thing you need to pay attention to is that the querystring parameter names match the method's parameter names. The rest is done automagically :)
//example3
public ActionResult MyMethod(int something, int anotherthing)
In this example, something cannot be converted, as "a" cannot be put into an int. The method call will fail (expect an ASP.Net error page). However, there are ways around this:
If the type is nullable, the method will still be called, and null will be the value. int? something will be set as null if conversion fails, and this makes sure the method still gets called.
You can make it an optional parameter: MyMethod(int anotherthing, int something = 0). Notice the inversion of the parameters. Optional parameters must always be placed after normal (required) parameters! This will make sure that, when something either cannot be converted (or simply isn't part of the querystring), it will receive the default value you specified (in my example, 0)
Some remarks:
You can write custom modelbinders that go way deeper than just converting a value. However, this is not default MVC behavior. It's still good to know you can add it if you need it.
Not all parameters are always part of the querystring. If you make a POST request (as opposed to the more lax GET request), you won't see a querystring. The values are still passed, but not as part of the requested URL. This is a topic you can find tons of information on via Google.
This is self-hosted RESTful MVC4 Web API, the only route is api/{controller}/{state}. When I send an HTTP-POST that has a body, the state argument comes in null. If I remove the body the state variable is present.
The way I thought it worked for HTTP-POST was that the url parameters get mapped then the body gets serialized into the extra parameter, which in this case is data parameter. The content is just string data which I had to write a custom MediaTypeFormatter (which I thought was odd it couldn't handle a regular string).
Here is my controller signature
public class MyController : ApiController
{
public void Post(string state, string data)
{
}
}
Has anyone seen this behavior before or can explain to me why having a body present is affecting my url parameter?
One Solution:
I tried changing data parameter into a complex type (Just a class with a public property) and sending the content as text/xml instead of text/plain and it worked as expected. The state parameter wasn't null and I had my strongly typed object with the data. I suppose MVC wants to have something to deserialize like XML or JSON for the http-request body...
More Research:
I've had the chance to run some more tests. If the body of a post is XML/JSON it will first try to map the properties of the body-object to the method parameters like so. If still has unmapped properties then it will match the remaining properties to the properties of any strongly-typed objects in the method parameters
PostMethod(string p1, string p2, myClass obj) // if myClass has a p3 property it will be mapped from the xml body.
{
}
// xml in body of http-post
<Xml>
</p1>
</p2>
</p3>
</Xml>
If all the parameters were not mapped, then it will attempt to map the url parameters. To relate it directly to my initial problem. The best and easiest solution I see at this time is to send text/xml like this.
PostMethod(string state, string data)
{
}
<data>put data here</data>
Urlencoded key/value pairs also work very well.
var r = client.PostAsync(url, new StringContent("data=Something", Encoding.UTF8, "application/x-www-form-urlencoded"));
My best guess is that the key/value nature of JSON and XML, FormEncoded help it to map to parameters so that is why it doesn't like plain strings.
This sure gave me a headache and I find the MVC4 documentation to be rather scarce (its still in beta), but I hope this can help someone else who may have the same problem.