Given this code, the entry for a C# REST API,
protected HttpResponseMessage ProcessRequest()
{
HttpResponseMessage _response = null;
try
{
//request is processed.
object _obj = ExecuteEvent();
//response object is created with success status and response content is assigned in the required mediatype format.
_response = Request.CreateResponse(HttpStatusCode.OK, Constants.ServiceConstants.VALUE);
_response.Content = new StringContent(System.Web.Helpers.Json.Encode(_obj), Encoding.UTF8, Constants.ServiceConstants.MEDIATYPE);
}
catch
{
}
return _response;
}
Returning a 200 response for every request and just setting the content to the serialized JSON as above, is this the right or wrong way to go about this?
I would have thought that we definitely should not just be returning a 200 OK for every request.
This code will throw an exception in ExecuteEvent, let's say if the user is unauthorized, however, shouldn't we actually be telling the caller that their request is unauthorized?
You should send back an HTTP status based ok what happened in your method. If everything worked correctly, you send back an OK. If there is a failure, you should send back the appropriate status code.
For example, if ExecuteEvent throws a message indicating that the user is not authorized, you would send back an HTTP stays code of Forbidden (a 403).
You can see all of Microsft's status codes here and you can get a list of the numbers and what they mean here. Most of the time these two lists with correspond to each other, they are just organized a little differently.
Hope that helps.
Related
I'm working on a WEB API application made with .NET Framework. Right now i'm working alonside a supplier to help and integrate their WEB API with our system. One of their latest change broke my code. Since i'm using RestSharp library i'm expecting the response to always correspond to a specific type of object, independently if with was a success or failure from their side.
IRestResponse<T> response = await restClient.ExecuteAsync<T>(request);
If the response is successful they will send a json response like this
{message:"", jobNr:"2312312", status:"1"}
And then they changed the code, so that if any request input was wrong they will send a list of errors, so the response will be like this:
[{code:100, message:"contact phone is wrong"},{code:101, message:"the email is not provided"}]
I don't like this approach, since i think they should give a response for errors like this:
{errorMessages:[{code:100, message:"contact phone is wrong"},{code:101, message:"the email is not provided"}]}
But since it is not in my hands. How should i deal with it? Just parse the json response and assing to different types of objects? Thanks.
Yes, the response should be parsed manually.
Assuming RestSharp is still in charge and the original API returns errors entitled with BadRequest status code, below extension tries to deserialize the content as an array of errors (a type consists of a code and a message just like mentioned in question).
public static Error[] AsErrors(this IRestResponse response)
{
var empty = new Error[] { };
try
{
return response.StatusCode == System.Net.HttpStatusCode.BadRequest
? JsonSerializer.Deserialize<Error[]>(response.Content)
: empty;
}
catch (JsonException)
{
return empty;
}
}
Considering the NormalContent is what you truly expect as response, the usage will be:
// after creating client and request ...
var response = client.Execute<NormalContent>(request);
var errors = response.AsErrors();
if (errors.Any())
{
//Deal with errors
}
else
{
//response.Data gets populated with RealResult
}
NOTE: Using the procedure as an extension depends on how common is the response pattern in your case.
When I use Postman to try uploading a large file to my server (written in .NET Core 2.2), Postman immediately shows the HTTP Error 404.13 - Not Found error: The request filtering module is configured to deny a request that exceeds the request content length
But when I use my code to upload that large file, it gets stuck at the line to send the file.
My client code:
public async void TestUpload() {
StreamContent streamContent = new StreamContent(File.OpenRead("D:/Desktop/large.zip"));
streamContent.Headers.Add("Content-Disposition", "form-data; name=\"file\"; filename=\"large.zip\"");
MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
multipartFormDataContent.Add(streamContent);
HttpClient httpClient = new HttpClient();
Uri uri = new Uri("https://localhost:44334/api/user/testupload");
try {
HttpResponseMessage httpResponseMessage = await httpClient.PostAsync(uri, multipartFormDataContent);
bool success = httpResponseMessage.IsSuccessStatusCode;
}
catch (Exception ex) {
}
}
My server code:
[HttpPost, Route("testupload")]
public async Task UploadFile(IFormFileCollection formFileCollection) {
IFormFileCollection formFiles = Request.Form.Files;
foreach (var item in formFiles) {
using (var stream = new FileStream(Path.Combine("D:/Desktop/a", item.FileName), FileMode.Create)) {
await item.CopyToAsync(stream);
}
}
}
My client code gets stuck at the line HttpResponseMessage httpResponseMessage = await httpClient.PostAsync(uri, multipartFormDataContent), while the server doesn't receive any request (I use a breakpoint to ensure that).
It gets stuck longer if the file is bigger. Looking at Task Manager, I can see my client program uses up high CPU and Disk as it is actually uploading the file to the server. After a while, the code moves to the next line which is
bool success = httpResponseMessage.IsSuccessStatusCode
Then by reading the response content, I get exactly the result as of Postman.
Now I want to know how to immediately get the error to be able to notify the user in time, I don't want to wait really long.
Note that when I use Postman to upload large files, my server doesn't receive any request as well. I think I am missing something, maybe there is problem with my client code.
EDIT: Actually I think it is the client-side error. But if it is server-side error, then it still doesn't mean too much for me. Because, let me clear my thought. I want to create this little helper class that I can use across projects, maybe I can share it with my friends too. So I think it should be able, like Postman, to determine the error as soon as possible. If Postman can do, I can too.
EDIT2: It's weird that today I found out Postman does NOT know before hand whether the server accepts big requests, I uploaded a big file and I saw it actually sent the whole file to the server until it got the response. Now I don't believe in myself anymore, why I thought Postman knows ahead of time the error, I must be stupid. But it does mean that I have found a way to do my job even better than Postman, so I think this question might be useful for someone.
Your issue has nothing to do with your server-side C# code. Your request gets stuck because of what is happening between the client and the server (by "server" I mean IIS, Apache, Nginx..., not your server-side code).
In HTTP, most clients don't read response until they send all the request data. So, even if your server discovers that the request is too large and returns an error response, the client will not read that response until the server accepts the whole requests.
When it comes to server-side, you can check this question, but I think it would be more convenient to handle it on the client side, by checking the file size before sending it to the server (this is basically what Postman is doing in your case).
Now I am able to do what I wanted. But first I want to thank you #Marko Papic, your informations do help me in thinking about a way to do what I want.
What I am doing is:
First, create an empty ByteArrayContent request, with the ContentLength of the file I want to upload to the server.
Second, surround HttpResponseMessage = await HttpClient.SendAsync(HttpRequestMessage) in a try-catch block. The catch block catches HttpRequestException because I am sending a request with the length of the file but my actual content length is 0, so it will throw an HttpRequestException with the message Cannot close stream until all bytes are written.
If the code reaches the catch block, it means the server ALLOWS requests with the file size or bigger. If there is no exception and the code moves on to the next line, then if HttpResponseMessage.StatusCode is 404, it means the server DENIES requests bigger than the file size. The case when HttpResponseMessage.StatusCode is NOT 404 will never happen (I'm not sure about this one though).
My final code up to this point:
private async Task<bool> IsBigRequestAllowed() {
FileStream fileStream = File.Open("D:/Desktop/big.zip", FileMode.Open, FileAccess.Read, FileShare.Read);
if(fileStream.Length == 0) {
fileStream.Close();
return true;
}
HttpRequestMessage = new HttpRequestMessage();
HttpMethod = HttpMethod.Post;
HttpRequestMessage.Method = HttpMethod;
HttpRequestMessage.RequestUri = new Uri("https://localhost:55555/api/user/testupload");
HttpRequestMessage.Content = new ByteArrayContent(new byte[] { });
HttpRequestMessage.Content.Headers.ContentLength = fileStream.Length;
fileStream.Close();
try {
HttpResponseMessage = await HttpClient.SendAsync(HttpRequestMessage);
if (HttpResponseMessage.StatusCode == HttpStatusCode.NotFound) {
return false;
}
return true; // The code will never reach this line though
}
catch(HttpRequestException) {
return true;
}
}
NOTE: Note that my approach still has a problem. The problem with my code is the ContentLength property, it shouldn't be exact the length of the file, it should be bigger. For example, if my file is exactly 1000 bytes in length, then if the file is successfully uploaded to the server, the Request that the server gets has greater ContentLength value. Because HttpClient doesn't just only send the content of the file, but it has to send many informations in addition. It has to send the boundaries, content types, hyphens, line breaks, etc... Generally speaking, you should somehow find out before hand the exact bytes that HttpClient will send along with your files to make this approach work perfectly (I still don't know how so far, I'm running out of time. I will find out and update my answer later).
Now I am able to immediately determine ahead of time whether the server can accept requests that are as big as the file my user wants to upload.
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.
In my POST method in a Controller.cs, I have been writing only a single value into the database so far, so at the end I returned the status of the response with the following code:
var response = Request.CreateResponse<object>(HttpStatusCode.Created, iconOffset);
return response;
But now, I am writing two values into the database, and I would love to return the status of both at the end of the POST method, how do I do that? I tried the following:
var response = Request.CreateResponse<object>(HttpStatusCode.Created, HttpStatusCode.Created, iconOffset, tempOffset);
return response;
But didn't work.
You wouldn't return multiple http status codes to the browser.
You make a request for something, you get a response back - that response was either OK or another status code, there's no concept of multiple response codes.
If you need to elaborate with your response, return a model along with your status for whatever is consuming the endpoint, one example would be:
return Request.CreateResponse(HttpStatusCode.OK, new DatabaseUpdateResult(results));
If your application could accept/return json, then this could be consumed by the client.
You can't do that. The response code is the HTTP response code and there is only one. You could either split out the method into two endpoints or return a status that represents the state of the operation.
Your client should have no knowledge of what is going on behind the service (database, services, ...) so if you're posting something to that endpoint and semantically it gets created just return Created. If an error occurs you could return InternalServerError.
Here's a list of HTTP status codes you can use: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
I have been banging my head against the wall for the past 1 week now but without any success. Actually I'm writing a C# code(a web api controller action) to call another web api to make a post request with some json data payload in the request body. Syntax-wise there is nothing wrong with the code. But when I directly call the service(web api service) from web browser I get an Html form that has a multiline text box in it, rollback property (as radio button for true and false value for this property), drop down box with 2 options such as html and json (to get response in either format) and a button(for sending request to the server and making edits in the database). Now when I manually put json data inside text box and click the button on that html form edits are done successfully in the database but when programmatically(from my C# code) I send the same json data payload and make a post request edits are never done successfully rather I get an html response body through Fiddler that says status code success 200 but unable to complete operation,some parameters couldn't be recognized.
Here is my code
private static async Task<HttpResponseMessage> GeometryUpdateAsync(Feature updatedFeature, FeatureType featureType, int? objectid = null)
{
var jsonObject = new JObject();
dynamic esriId = jsonObject;
if (objectid == null)
{
objectid = updatedFeature.OBJECTID;
}
esriId.OBJECTID = objectid;
var mergedJsonString = JsonConvert.SerializeObject(new
{
geometry = JObject.Parse(updatedFeature.Geometry.ToString()),
attributes = JObject.Parse(esriId.ToString())
});
mergedJsonString = String.Format("[{0}]", mergedJsonString);
HttpResponseMessage response = null;
using (var client = new HttpClient())
{
//string arguments = "rollbackOnFailure=true&f=pjson&features=";
client.BaseAddress = new Uri("somebaseaddress");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromSeconds(500.00);
//response = await client.PostAsJsonAsync("someuri", arguments + mergedJsonString);
response = await client.PostAsync("someuri", mergedJsonString, new System.Net.Http.Formatting.JsonMediaTypeFormatter());
if (response.IsSuccessStatusCode)
{
var v = response.Content.ReadAsStringAsync().Result;
}
}
}
When I look at the request body (through fiddler while making a post request through Html form) request body looks like
features=%5B%7B%22geometry%22%3A%7B%22paths%22%3A%5B%5B%5B-91.3888577181506%2C39.703158271352621%5D%91.381838690201192%2C39.690323806398723%5D%2C%5B-91.383241723424632%2C39.689645139311914%5D%2C%5B-91.3849700567206%2C39.6888078408094%5D%2C%5B-91.3861256828518%2C39.688248198995353%5D%5D%5D%7D%2C%22attributes%22%3A%7B%22OBJECTID%22%3A21%7D%5D&gdbVersion=&rollbackOnFailure=true&f=pjson
and the request body for the post request made programmatically looks likes
"[{\"geometry\":{\"paths\":[[[-91.3888577181506,39.703158271352621],[-91.381838690201192,39.690323806398723],[-91.383241723424632,39.689645139311914],[-91.3849700567206,39.6888078408094],[-91.3861256828518,39.688248198995353]]]},\"attributes\":{\"OBJECTID\":21}}]"
Even I tried appending this
string arguments = "rollbackOnFailure=true&f=pjson&features=";
in my commented out code above (where I'm using PostAsJsonAsync) to make the request body look like as if it's coming from Html form. But no success, even I'm not sure whether the JSonFormatter takes this arguments string in to account or just leaves it while serializing/deserializing during the run time. And the post request body that I get after appending "arguments" string to Json string looks like this
"rollbackOnFailure=true&f=pjson&features=[{\"geometry\":{\"paths\":[[[-91.3877577181506,39.703158271352621],[-91.36047320856953,39.702616420911333],[-91.383241723424632,39.689645139311914],[-91.3849700567206,39.6888078408094],[-91.3861256828518,39.688248198995353]]]},\"attributes\":{\"OBJECTID\":21}}]"
But still no success, Now I'm totally running out of ideas as to how to call web api service from my C# code so that web api thinks it's coming from that Html form and end up successfully doing edits in the database programmatically. All suggestions and ideas will be highly appreciated.
The trick lies somewhere else, I was using HttpClient to simulate browser post request and get result in c#. But in this particular scenario HttpClient is of no use. I changed to HttpWebRequest after seeing a code at How to make a post call to a Web Api Action? from utlimate_programmer_BR and it did the trick, again HttpClient was a bad choice by me to get this particular thing done.