Checking if HttpStatusCode represents success or failure - c#

Let's suppose I have the following variable:
System.Net.HttpStatusCode status = System.Net.HttpStatusCode.OK;
How can I check if this is a success status code or a failure one?
For instance, I can do the following:
int code = (int)status;
if(code >= 200 && code < 300) {
//Success
}
I can also have some kind of white list:
HttpStatusCode[] successStatus = new HttpStatusCode[] {
HttpStatusCode.OK,
HttpStatusCode.Created,
HttpStatusCode.Accepted,
HttpStatusCode.NonAuthoritativeInformation,
HttpStatusCode.NoContent,
HttpStatusCode.ResetContent,
HttpStatusCode.PartialContent
};
if(successStatus.Contains(status)) //LINQ
{
//Success
}
None of these alternatives convinces me, and I was hoping for a .NET class or method that can do this work for me, such as:
bool isSuccess = HttpUtilities.IsSuccess(status);

If you're using the HttpClient class, then you'll get a HttpResponseMessage back.
This class has a useful property called IsSuccessStatusCode that will do the check for you.
using (var client = new HttpClient())
{
var response = await client.PostAsync(uri, content);
if (response.IsSuccessStatusCode)
{
//...
}
}
In case you're curious, this property is implemented as:
public bool IsSuccessStatusCode
{
get { return ((int)statusCode >= 200) && ((int)statusCode <= 299); }
}
So you can just reuse this algorithm if you're not using HttpClient directly.
You can also use EnsureSuccessStatusCode to throw an exception in case the response was not successful.

The accepted answer bothers me a bit as it contains magic numbers, (although they are in standard) in its second part. And first part is not generic to plain integer status codes, although it is close to my answer.
You could achieve exactly the same result by instantiating HttpResponseMessage with your status code and checking for success. It does throw an argument exception if the value is smaller than zero or greater than 999.
if (new HttpResponseMessage((HttpStatusCode)statusCode).IsSuccessStatusCode)
{
// ...
}
This is not exactly concise, but you could make it an extension.

I am partial to the discoverability of extension methods.
public static class HttpStatusCodeExtensions
{
public static bool IsSuccessStatusCode(this HttpStatusCode statusCode)
{
var asInt = (int)statusCode;
return asInt >= 200 && asInt <= 299;
}
}
As long as your namespace is in scope, usage would be statusCode.IsSuccessStatusCode().

The HttpResponseMessage class has a IsSuccessStatusCode property, looking at the source code it is like this so as usr has already suggested 200-299 is probably the best you can do.
public bool IsSuccessStatusCode
{
get { return ((int)statusCode >= 200) && ((int)statusCode <= 299); }
}

Adding to #TomDoesCode answer If you are using HttpWebResponse
you can add this extension method:
public static bool IsSuccessStatusCode(this HttpWebResponse httpWebResponse)
{
return ((int)httpWebResponse.StatusCode >= 200) && ((int)httpWebResponse.StatusCode <= 299);
}

It depends on what HTTP resource you are calling. Usually, the 2xx range is defined as the range of success status codes. That's clearly a convention that not every HTTP server will adhere to.
For example, submitting a form on a website will often return a 302 redirect.
If you want to devise a general method then the code >= 200 && code < 300 idea is probably your best shot.
If you are calling your own server then you probably should make sure that you standardize on 200.

This is an extension of the previous answer, that avoids the creation and subsequent garbage collection of a new object for each invocation.
public static class StatusCodeExtensions
{
private static readonly ConcurrentDictionary<HttpStatusCode, bool> IsSuccessStatusCode = new ConcurrentDictionary<HttpStatusCode, bool>();
public static bool IsSuccess(this HttpStatusCode statusCode) => IsSuccessStatusCode.GetOrAdd(statusCode, c => new HttpResponseMessage(c).IsSuccessStatusCode);
}

Related

Move Async/Await JSON deserialize result to another function

I have this function that can get up to 10 items as an input list
public async Task<KeyValuePair<string, bool>[]> PayCallSendSMS(List<SmsRequest> ListSms)
{
List<Task<KeyValuePair<string, bool>>> tasks = new List<Task<KeyValuePair<string, bool>>>();
foreach (SmsRequest sms in ListSms)
{
tasks.Add(Task.Run(() => SendSMS(sms)));
}
var result = await Task.WhenAll(tasks);
return result;
}
and in this function, i await for some JSON to be downloaded and after it's done in deserialize it.
public async Task<KeyValuePair<string, bool>> SendSMS(SmsRequest sms)
{
//some code
using (WebResponse response = webRequest.GetResponse())
{
using (Stream responseStream = response.GetResponseStream())
{
StreamReader rdr = new StreamReader(responseStream, Encoding.UTF8);
string Json = await rdr.ReadToEndAsync();
deserializedJsonDictionary = (Dictionary<string, object>)jsonSerializer.DeserializeObject(Json);
}
}
//some code
return GetResult(sms.recipient);
}
public KeyValuePair<string, bool> GetResult(string recipient)
{
if (deserializedJsonDictionary[STATUS].ToString().ToLower().Equals("true"))
{
return new KeyValuePair<string, bool>(recipient, true);
}
else // deserializedJsonDictionary[STATUS] == "False"
{
return new KeyValuePair<string, bool>(recipient, false);
}
}
My problem is in the return GetResult(); part in which deserializedJsonDictionary is null(and ofc it is becuase the json havent done downloading).
but I don't know how to solve it
I tried to use ContinueWith but it doesn't work for me.
I'm willing to accept any change to my original code and/or the design of the solution
Unrelated tip: Don't abuse KeyValuePair<>, use C# 7 value-tuples instead (not least because they're much easier to read).
Using a foreach loop to build a List<Task> is fine - though it can be more succint to use .Select() instead. I use this approach in my answer.
But don't use Task.Run with the ancient WebRequest (HttpWebRequest) type. Instead use HttpClient which has full support for async IO.
Also, you should conform to the .NET naming-convention:
All methods that are async should have Async has a method-name suffix (e.g. PayCallSendSMS should be named PayCallSendSmsAsync).
Acronyms and initialisms longer than 2 characters should be in PascalCase, not CAPS, so use Sms instead of SMS.
Use camelCase, not PascalCase for parameters and locals - and List is a redundant prefix. A better name for ListSms would be smsRequests as its type is List<SmsRequest>).
Generally speaking, parameters should be declared using the least-specific type required - especially collection parameters, consider typing them as IEnumerable<T> or IReadOnlyCollection<T> instead of T[], List<T>, and so on).
You need to first check that the response from the remote server actually is a JSON response (instead of a HTML error message or XML response) and has the expected status code - otherwise you'll be trying to deserialize something that is not JSON.
Consider supporting CancellationToken too (this is not included in my answer as it adds too much visual noise).
Always use Dictionary.TryGetValue instead of blindly assuming the dictionary indexer will match.
public async Task< IReadOnlyList<(String recipient, Boolean ok)> > PayCallSendSmsAsync( IEnumerable<SmsRequest> smsRequests )
{
using( HttpClient httpClient = this.httpClientFactory.Create() )
{
var tasks = smsRequests
.Select(r => SendSmsAsync(httpClient, r))
.ToList(); // <-- The call to ToList is important as it materializes the list and triggers all of the Tasks.
(String recipient, Boolean ok)[] results = await Task.WhenAll(tasks);
return results;
}
}
private static async Task<(String recipient, Boolean ok)> SendSmsAsync(HttpClient httpClient, SmsRequest smsRequest)
{
using (HttpRequestMessage request = new HttpRequestMessage( ... ) )
using (HttpResponseMessage response = await httpClient.SendAsync(request).ConfigureAwait(false))
{
String responseType = response.Content.Headers.ContentType?.MediaType ?? "";
if (responseType != "application/json" || response.StatusCode != HttpStatusCode.OK)
{
throw new InvalidOperationException("Expected HTTP 200 JSON response but encountered an HTTP " + response.StatusCode + " " + responseType + " response instead." );
}
String jsonText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Dictionary<String,Object> dict = JsonConvert.DeserializeObject< Dictionary<String,Object> >(jsonText);
if(
dict != null &&
dict.TryGetValue(STATUS, out Object statusValue) &&
statusValue is String statusStr &&
"true".Equals( statusStr, StringComparison.OrdinalIgnoreCase )
)
{
return ( smsRequest.Recipient, ok: true );
}
else
{
return ( smsRequest.Recipient, ok: false );
}
}
}

I need some help writing custom Assertion methods for my API testing

So the two assertions I am doing is checking the status message which should give me "OK"
And I am checking if the API returns 200. But my issue is each request is different of course and that response variable. I am trying to avoid writing the same three lines of Assertions and just call one simple static method
HttpWebResponse response = (HttpWebResponse)sd.GetDataType();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
statusNumber = (int)response.StatusCode;
Assert.AreEqual(200, statusNumber);
I would make a helper class to test it:
public static class HttpWebResponseDebug
{
public static void Assert(HttpWebResponse response)
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
statusNumber = (int)response.StatusCode;
Assert.AreEqual(200, statusNumber);
}
}
var response = (HttpWebResponse)sd.GetDataType();
HttpWebResponseDebug.Assert(response);
// do your thing..
All assert methods are static, so you can create your own static method and use Assert inside
public static class AssertExtensions
{
public static void Assert(YourType sd)
{
HttpWebResponse response = (HttpWebResponse)sd.GetDataType();
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
statusNumber = (int)response.StatusCode;
Assert.AreEqual(200, statusNumber);
}
}
and then use AssertExtensions.Assert(response)
btw if you are using .Net Core there is already method response.EnsureSuccessStatusCode() which throws exception
HTTP Protocol only sends the following bytes: "200 OK"
Your code is doing is 2 things:
Assert that the response of the request is "200 OK" .
Assert that the library code is working, as the StatusCode enum gets the right value.
You might want to avoid doing that, we hope MS got you covered, (and also the LOCs that do that)
To avoid repeating that code, make a single method and use it, you could try with:
a method on an abstract "BaseTestClass" you inherit from
an Extension Method for the HttpWebResponse
an static method on some sort of util class.
just a private method on the same class.
I would do the first approach, but if you're new to programming you could start trying the last one which will probably be easier to understand.
EDIT
After looking a bit at the HttpResponseMessage reference I would use the IsSuccessStatusCode property:
Assert.IsTrue(response.IsSuccessStatusCode)
And that's it. But this will check if the response is in the range of success (200-299), so maybe you need to additionally check the code, then one of the originally suggested methods may come in handy.
Talk is cheap, show me the code:
public static bool IsOk(this HttpWebResponse response) {
var isOk = response.IsSuccessStatusCode;
isOk = isOk && response.StatusCode == 200;
return isOk;
}
with this method on a static class in a namespace used by your class you could go with:
Assert.IsTrue(response.IsOk());
this code was written directly here, it may not work as is but, I hope you get the idea.

PingReply's "RoundTripTime" is 0 very often? Why is this happening

I have a very odd issue that is happening when pinging a large amount of proxies. The "RoundTripTime" is returning 0 randomly.
If I run the proxy list through the checker a second time it will have different proxies returning up 0 and others returning with actual ms.
I thought this was maybe due to it being too many requests at once so I tried adding some manual sleep into it but that caused more "0" response times.
I'm seriously stuck and appreciate all help.
public static List<string> proxyList = new List<string>();
public static List<string> proxyNoPort = new List<string>();
public static int proxyCount;
public static int proxyTimeOut;
public static long pingResponseTime;
public static bool proxyTest()
{
try
{
Ping pingTest = new Ping();
PingReply pingResponse = pingTest.Send(proxyNoPort[proxyCount], proxyTimeOut);
if (pingResponse != null && pingResponse.RoundtripTime < proxyTimeOut)
{
pingResponseTime = pingResponse.RoundtripTime;
return true;
}
}
catch
{
proxyList.Remove(proxyList[proxyCount]);
proxyNoPort.Remove(proxyNoPort[proxyCount]);
return false;
}
return false;
}
[Picture of program][1]
[1]: https://i.stack.imgur.com/M0Nar.png
Notes from my own further testing:
If the number returns "0" making it re-ping the same proxy works for about 90% of the proxies.
if(pingResponse.RoundtripTime == 0)
{
pingResponse = pingTest.Send(proxyNoPort[proxyCount], proxyTimeOut);
pingResponseTime = pingResponse.RoundtripTime;
}
Turns out that using code from this forum isn't always the best and you should maybe look into the official documents. It turns out when the pings response is "0" that means it has failed to connect.
I fixed my code completely by replacing
if (pingResponse != null && pingResponse.RoundtripTime < proxyTimeOut)
{
pingResponseTime = pingResponse.RoundtripTime;
return true;
}
with
if (pingResponse.Status == IPStatus.Success && pingResponse.RoundtripTime < proxyTimeOut)
{
pingResponseTime = pingResponse.RoundtripTime;
return true;
}
Although not all is bad as the use "Dai" informed me I should be using the "Using" tags and that has sped up my proxy checking significantly, thanks!

MVC and fail AJAX call

I have a controller method:
public async Task SaveRouting(string points, int tripId, decimal totalMileage)
{
if (Request.IsAjaxRequest())
{
//....
await _serviceTrip.UpdateTotalMileageAsync(tripId, totalMileage);
}
else
throw new Exception("Only ajax calls are allowed");
}
so, as we can see, this method returns Task, so nothing to client. But if something wrong (i.e. totalMileage is less or equal 0) I want to return 422 status code and dictionary with invalid data, like:
{ "totalMileage" : "Total mileage should be greater than 0" }
How to do it?
I try to do it:
if (totalMileage <= 0)
{
Response.StatusCode = 422; // 422 Unprocessable Entity Explained
}
but how to describe an error?
If you want to describe the error after setting Response.StatusCode, then you have to write into the body of the http response by calling Response.Body.Write(byte[],int, int).
Therefore, you can convert your response message to a byte array using the following method:
public byte[] ConvertStringToArray(string s)
{
return new UTF8Encoding().GetBytes(s);
}
And then use it like this:
byte[] bytes = ConvertStringToArray("Total mileage should be greater than 0");
Response.StatusCode = 422;
Response.Body.Write(bytes,0,bytes.Length);
But you can further simplify this using extension methods on ControllerBase

Modify GetQueryNameValuePairs() for UrlHelper.Link in ASP.NET WebApi

With ASP.NET WebApi, when I send GET api/question?page=0&name=qwerty&other=params and API should give result within pagination envelop.
For that, I'd like to put result and change given page querystring to other values.
I tried as below code but I got a bad feeling about this.
protected HttpResponseMessage CreateResponse(HttpStatusCode httpStatusCode, IEnumerable<Question> entityToEmbed)
// get QueryString and modify page property
var dic = new HttpRouteValueDictionary(Request.GetQueryNameValuePairs());
if (dic.ContainsKey("page"))
dic["page"] = (page + 1).ToString();
else
dic.Add("page", (page + 1).ToString());
var urlHelper = new UrlHelper(Request);
var nextLink= page > 0 ? urlHelper.Link("DefaultApi", dic) : null;
// put it in the envelope
var pageEnvelope = new PageEnvelope<Question>
{
NextPageLink = nextLink,
Results = entityToEmbed
};
HttpResponseMessage response = Request.CreateResponse<PageEnvelope<Question>>(httpStatusCode, pageEnvelope, this.Configuration.Formatters.JsonFormatter);
return response;
}
The NextPageLink gives a lot complex result.:
http://localhost/api/Question?Length=1&LongLength=1&Rank=1&SyncRoot=System.Collections.Generic.KeyValuePair%602%5BSystem.String%2CSystem.String%5D%5B%5D&IsReadOnly=False&IsFixedSize=True&IsSynchronized=False&page=1
My question is,
My page handling with Dictionary approach seems dirty and wrong. Is there better way to address my problem?
I don't know why urlHelper.Link(routeName, dic) gives such a verbose ToString result. How to get rid of unusable Dictionary-related properties?
The key issue probably in your code is the conversion to the HttpRouteValueDictionary. New it up instead and add in a loop all key value pairs.
The approach can be simplified quite a lot, and you should also probably want to consider using an HttpActionResult (so that you can more easily test your actions.
You should also avoid using the httproutevaluedictionary and instead write your UrlHelper like
urlHelper.Link("DefaultApi", new { page = pageNo }) // assuming you just want the page no, or go with the copying approach otherwise.
Where just pre calculate your page no (and avoid ToString);
Write it all in an IHttpActionResult that exposes an int property with the page No. so you can easily test the action result independently of how you figure out the pagination.
So something like:
public class QuestionsResult : IHttpActionResult
{
public QuestionResult(IEnumerable<Question> questions>, int? nextPage)
{
/// set the properties here
}
public IEnumerable<Question> Questions { get; private set; }
public int? NextPage { get; private set; }
/// ... execution goes here
}
To just get the page no, do something like:
Source - http://www.asp.net/web-api/overview/releases/whats-new-in-aspnet-web-api-21
string page = request.Uri.ParseQueryString()["page"];
or
you can use this extension method below (from Rick Strahl)
public static string GetQueryString(this HttpRequestMessage request, string key)
{
// IEnumerable<KeyValuePair<string,string>> - right!
var queryStrings = request.GetQueryNameValuePairs();
if (queryStrings == null)
return null;
var match = queryStrings.FirstOrDefault(kv => string.Compare(kv.Key, key, true) == 0);
if (string.IsNullOrEmpty(match.Value))
return null;
return match.Value;
}

Categories