WebApi GET Troubleshooting 400 BadRequest - c#

When making a call to my webapi running locally, I am passing data in the requestUri as base64 encoded Json of my serialized object, but getting StatusCode 400 at every turn.
How do I fix my request?
public class VerifyController : ApiController
{
// GET: api/Verify/jsonStringAddressOriginal
public AddressResult Get(string base64AddressOriginal)
The caller:
var requestUri = string.Format("api/verify/{0}", base64address);
Console.WriteLine("requestUri:\n{0}", requestUri);
HttpResponseMessage response = client.GetAsync(requestUri).Result;
requestUri:
api/verify/eyJDb21wYW55IjoiIiwiQWRkcmVzcyI6Ijc3NTAgQmVsZm9ydCBQYXJrd2F5IDIwMCIsI
kFkZHJlc3MyIjoiIiwiU3VpdGUiOiIiLCJDaXR5IjoiSmFja3NvbnZpbGxlIiwiU3RhdGUiOiJGTCIsI
lppcCI6IjMyMjU2IiwiUGx1czQiOiIiLCJMYXN0TGluZSI6IiIsIkNvdW50cnlDb2RlIjoiIiwiVXJiY
W5pemF0aW9uIjoiIiwiTGFzdE5hbWUiOiIiLCJQYXJzZWRBZGRyZXNzUmFuZ2UiOiIiLCJQYXJzZWRQc
mVEaXJlY3Rpb24iOiIiLCJQYXJzZWRTdHJlZXROYW1lIjoiIiwiUGFyc2VkU3VmZml4IjoiIiwiUGFyc
2VkUG9zdERpcmVjdGlvbiI6IiIsIlBhcnNlZFN1aXRlTmFtZSI6IiIsIlBhcnNlZFN1aXRlUmFuZ2UiO
iIiLCJQYXJzZWRSb3V0ZVNlcnZpY2UiOiIiLCJQYXJzZWRMb2NrQm94IjoiIiwiUGFyc2VkRGVsaXZlc
nlJbnN0YWxsYXRpb24iOiIifQ==
Error code: BadRequest
Reason: Bad Request
I think it's a malformed request but I don't know in what way. There seem to be no further properties of the response object detailing which error that the 400 Status Code represents. http://msdn.microsoft.com/en-us/library/azure/dd179357.aspx
This is my first WebApi so when answering, make no assumptions about my configuration.
VS2013 update 3, Web Api, using an HttpClient as the caller.

I didn't count the number of characters in your URI, but very long URIs (> 2000 characters) are likely to cause problems. Try making this a POST request and putting the base64 data into the request body.

Related

ASP .NET Core Endpoint returns different Content Types

While testing my endpoints I recognized that an endpoint returned different Content Types when sending different input values.
For instance: The endpoint returns a Supplier when an ID is given. If the HTTP Status Code is 200 the content type is "application/json; charset=utf-8; v=1.0".
But when I submit bullshit like a random string instead of an integer, obviously the api returns a HTTP 400. The content type now is "application/json; charset=utf-8". The "v=1.0" is missing.
It seems trivial but our integration tests check if the Content Type is like the expected application/json; charset=utf-8; v=1.0.
Also, we defined in the Startup.cs: options.DefaultApiVersion = new ApiVersion(1, 0);
Endpoint code:
[HttpGet("{id:int}")]
public async Task<IActionResult> GetById(int id)
{
var body = await _lieferantenService.GetById(id);
return Ok(body);
}
Does anybody encountered this kind of problem?
It is most likely that the response comes from a middleware and not from your code.
I think the middleware that validates input or the one that is responsible for model bindings. (Mapping bullshit to integer).
To customize the Response use InvalidModelStateResponseFactory of ApiBehaviorOptions.
See answer to this question. https://stackoverflow.com/a/51442067/83039.

Asana API 403 Response in C#

I am trying to implement a Xamarin app that works with the Asana API.
I have successfully implemented the OAuth as documented in the Asana documentation here... at least I assume it is successful. I get an access token from the token endpoint in an HTTPResponse with HTTP Status "OK".
But then when I turn around and try to make an API call with that same access token, I get a 403 Forbidden error. I tried the same API call in my browser (after logging in to Asana), and it works fine, which leads me to believe that I do have access to the resource, I must have an issue with authorizing the request on my end.
The API call in question is (documented here): https://app.asana.com/api/1.0/workspaces.
My C# code is as follows (abbreviated to relevant parts, and assume that ACCESS_TOKEN contains the access token I got from the token exchange endpoint):
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0";
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", ACCESS_TOKEN);
client.DefaultRequestHeaders.Add("Accept", "application/json");
And then I use this HttpClient (named client) in the following function:
// Returns a list of the Asana workspace names for the logged in user.
private async Task<List<string>> GetWorkspacesAsync()
{
List<string> namesList = new List<string>();
// Send the HTTP Request and get a response.
this.UpdateToken(); // Refreshes the token if needed using the refresh token.
using (HttpResponseMessage response = await client.GetAsync("/workspaces"))
{
// Handle a bad (not ok) response.
if (response.StatusCode != HttpStatusCode.OK)
{
// !!!THIS KEEPS TRIGGERING WITH response.StatusCode AS 403 Forbidden!!!
// Set up a stream reader to read the response.
// This is for TESTING ONLY
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
Debug.WriteLine(content);
}
throw new HttpRequestException("Bad HTTP Response was returned.");
}
// If execution reaches this point, the Http Response returned with code OK.
// Set up a stream reader to read the response.
using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
// Extract the json object from the response.
string content = reader.ReadToEnd();
JsonValue responseJson = JsonValue.Parse(content);
foreach (JsonValue workspaceJson in responseJson["data"])
{
string workspaceName = workspaceJson["name"];
Debug.WriteLine("Workspace Name: " + workspaceName);
namesList.Add(workspaceName);
}
}
}
// I have other awaited interactions with app storage in here, hence the need for the function to be async.
return namesList;
}
Finally found the answer. It looks like I was using HttpClient incorrectly; a subtle thing that should be equivalent, but is not due to the way it is implemented.
The answer
I needed to place the final slash at the end of the BaseAddress property of HttpClient, and NOT at the start of the relative address for the specific request. This answered question explains this.
To fix my code
I needed to change the setting up of the BaseAddress:
HttpClient client = new HttpClient();
client.BaseAddress = "https://app.asana.com/api/1.0/"; // FINAL SLASH NEEDED HERE
And remove the slash from the request's relative address:
// DO NOT put slash before relative address "workspaces" here
using (HttpResponseMessage response = await client.GetAsync("workspaces"))
Why I got the original error
When HttpClient combined the BaseAddress with the relative URI I specified in GetAsync(), it dropped off some of the base address, since the final slash was not included. The resulting address from combining the BaseAddress with the relative URI was a valid URL, but not a valid page/API call in Asana. Asana thus did an automatic redirect to a login page, which, of course, the rest of the API call would be forbidden from there.
How I discovered this
In debugging, I grabbed the access token returned during my app's authorization with Asana. I then recreated the request to the "/workspaces" API myself in Postman, and the request worked as expected. This confirmed that my authorization worked fine, and the issue must be with the specific request rather than the authorization. In debugging I then looked into the HttpResponseMessage, which has a property called RequestMessage, that includes the actual URL the GetAsync() made the request against. I observed the Login URL from Asana, rather than the BaseAddress I specified... which led me to the question/
answer linked above.
Hope this explanation helps anyone who comes across a similar error!

Receiving 500 error posting valid JSON to .NET Core 2 / .NET MVC Api. (post request is null and stating "System.Net.Http.NoWriteNoSeekStreamContent")

So quick background on what is currently going on in my solution. I'm writing pretty basic API web tests in c# using the XUnit framework and some json validation helpers I created to validate response messages and payloads received back from the API. I've been banging my head against a wall trying to figure out why my PostAsync() call is passing null once it reaches the API since I can see the request body is a valid json request when debugging my ApiTests solution. Below are some snippets of the test I'm currently debugging.
OrderControllerTests.cs
[Theory]
[MemberData(nameof(OrderControllerData.postCancelOrder), MemberType = typeof(OrderControllerData))]
public async Task PostValidCancelOrder(string jsonBody, string expectedResponseBody)
{
var postObject = "api/Order/cancel";
var request = new StringContent(jsonBody, Encoding.UTF8, "application/json");
var response = await _httpClient.PostAsync(postObject, request);
var content = await response.Content.ReadAsStringAsync();
ValidationHelper.ValidateNoErrors(response);
ValidationHelper.ValidateJson(content, expectedResponseBody);
}
A few things are happening in this test. All of which seem to be working fine and as intended (except for maybe #3 below).
Create my requestUri with postObject.
Create my HttpContent. XUnit is passing my jsonBody param by way of the MemberData attribute. Which looks into my data layer class for the postCancelOrder public static IEnumerable.
Posting the request to the API.
Reading the payload returned back in string format.
Validation Helper checks for HTTP response messages. The ValidateNoErrors method is simply just looking for a 200 response code. The second one doesn't matter for this question since my test blows up at the first ValidationHelper.
Right now this is currently what I am posting to the api.
"{\"orderId\": \"c701b60a-4fb1-4f79-9e17-0172a9a17bbc\",\"test1Fee\":\"5}\",\"test2Fee\":\"10\"}"
Here is the HttpPost within the Api/OrderController.
OrderController.cs
[HttpPost("cancel")]
public async Task<IActionResult> Cancel([FromBody]CancelOrderViewModel model)
{
var command = new CancelOrderCommand(model.OrderId, model.Test1Fee, model.Test2Fee);
var order = await _mediator.Send(command);
return Ok(order);
}
Essentially this HttpPost is passing a few parameters and then running a multiple methods and checks on those objects to determine if the order is eligible to be cancelled. What happens within the post doesn't matter right now. When debugging my test and it hits the entry point of the HttpPost method of the api, the 'command' and 'model' are both null. Naturally the Api will reject the request and responds with a 500 error.
{StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.NoWriteNoSeekStreamContent, Headers:{ Date: Mon, 22 Jan 2018 19:38:55 GMT Transfer-Encoding: chunked Server: Kestrel Request-Context: appId=cid-v1:93254013-d606-465f-8cee-12e422316f31 X-SourceFiles: =?UTF-8?B?RDpcUUFccWFWZW5kb3JcQWJzdHJhY3Rvck9tc0FwaVxzb3VyY2VcQXBpXGFwaVxPcmRlclxjYW5jZWw=?= X-Powered-By: ASP.NET Content-Type: application/json; charset=utf-8}}
Also the message is indicating that Object reference not set to an instance of an object since everything is null apparently.
So the problem has to be with how I'm constructing the request within my XUnit test. But I'm confused because I believe I'm serializing the json correctly. Even when the Api is running locally in my browser using swagger, the indicated example is
{
"orderId": "string",
"test1Fee": 0,
"test2Fee": 0
}
Could the escapes being created within my post object be the problem? If so, how do I go about fixing that? I tried using PostAsJsonAsync() instead of just PostAsync() but it continues to yield the same results.
What am I doing wrong here? I'm sure it's something pretty simple and I'm just missing it. Let me know if I need to provide anymore information. Any help would be greatly appreciated. Thanks.

ASP.NET Web API Conditionally return HttpResponseMessage

I have a ASP.NET Web API, and I have been responding to request with this format,
[HttpPost]
[Route("")]
public HttpResponseMessage AlexaSkill()
{
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, "value");
response.Content = new StringContent("put json here", Encoding.UTF8);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
return response;
}
and that has been working great. The issue is that there are certain situation where the requester does not expect a response. I cannot figure out how to not give a response to the requester who is posting to the url. How can I be able to return a response like a have above and also have the option to have the function not give a respons essentially acting as a void function?
You should always return a response. There's a status code 204 for when you don't want to send content in your response. From the spec:
10.2.5 204 No Content
The server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation. The response MAY include new or updated metainformation in the form of entity-headers, which if present SHOULD be associated with the requested variant.
If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent. This response is primarily intended to allow input for actions to take place without causing a change to the user agent's active document view, although any new or updated metainformation SHOULD be applied to the document currently in the user agent's active view.
The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.
So your code could be something like this:
[HttpPost]
public HttpResponseMessage SomeMethod()
{
// Do things
return Request.CreateResponse(HttpStatusCode.NoContent);
}
Even a void method will return an HTTP status code to the client invoking the API. See this link
You'll probably need to ask for changes or another alternative to your client.
If you want to just terminate the request, try this:
HttpContext.Current.Response.End();
throw new Exception("Terminating request.");
It seems like a strange thing for an HTTP server to do, but if that's what you really need, give that a shot. If you follow by throwing an exception, then an error won't be sent to the client because you've already ended the response.

Web API 2 returning text/plain responses

Really struggling with something I hope people here can help with. I'm writing a RESTful API in Web API 2. Whenever I send a request to this service, the response is consistently being sent with a Content-Type of text/plain. Obviously this is no good, my response needs to be Content-Type of application/json. I've tried a few suggestions that I found from Google but I don't think I'm understanding the whole picture.
Is there something special I need to do in order to have my web service respond with application/json content? Note that I want this to work globally across the whole app, so I'm not after a way to modify a given response and set its content type - I want this to be a default behaviour for the whole web service: If a request contains an Accept header for application/json I want my web service to return that Content-Type instead of text/plain.
Edit to clarify:
My response contains an object called "responseData" that should be serialized into JSON and included in the body. I'm currently putting together my response like this:
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK, responseData);
return response;
responseData is a POCO. This get's correctly serialized as JSON and returned in the response - the only missing part is the Content-Type which is incorrectly set to "text/plain". I could manually change this on every single response I compose, but I'm wanting to configure this on a global level.
OK, assuming that your responseData is a string, the Content-type header will be text/plain when you create the HttpResponseMessage. Doesn't matter what the contents of the string are, since no attempt is made to determine this.
The solution is to create the appropriate Content object for your message, initialized with the media type you're returning:
HttpResponseMessage response = new HttpResponseMessage()
{
Content = new StringContent(
responseData,
Encoding.UTF8,
"application/json"
)
};
There are other methods that amount to returning a particular object type and letting the API libraries serialize to JSON or XML for you as required. I prefer to let the framework do the work for me where possible, but this is how you'd achieve it with a string you've created yourself.
For a strict JSON-only result, remove the XML formatter from the WebAPI configuration and return your POCO.
In App_Start\WebApiConfig.cs, add the following to the WebApiConfig.Register method:
config.Formatters.Remove(config.Formatters.XmlFormatter);
And for your API:
public class MyObject
{
public bool result;
public string reason;
}
public class TestController : ApiController
{
public MyObject GetData()
{
MyObject result = new MyObject { result = "true", reason = "Testing POCO return" };
return result;
}
}
I ran this up and requested /api/Test from Chrome, which doesn't even mention application/json in the Accept header. Here are the response headers up until it hits Content-Type:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
And the body:
{"result":true,"reason":"Testing POCO return"}
Since I disabled XML it defaulted to JSON.
Add the following to Global.asax file.
protected void Application_Start()
{
JsonSerializerSettings serializerSettings = new JsonSerializerSettings();
serializerSettings.Converters.Add(new IsoDateTimeConverter());
var jsonFormatter = new JsonNetFormatter(serializerSettings);
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
GlobalConfiguration.Configuration.Formatters.Insert(0, jsonFormatter);
}
Another possible source of the issue described is that there may be an authorization redirect in play as was the case for us when one of the engineers thought to re-use user authentication for an api.
This means incoming requests were being redirected to a login page which was the text/html response that couldn't be parsed by ReadAsync<>. A silly mistake to be sure, but not an easy one to spot.
The solution in that case was to remove the user authentication and implement HMAC based authentication for the api.

Categories

Resources