Web Response Wrapper
The System.Net namespace is a helpful library containing tools to network. In this post we'll wrap HttpWebResponse and WebResponse streaming of data for HTTP requests in a utility class HttpRemoteCall.
Nuts-n-Bolts:
public enum HttpMethods { GET = 0, POST = 1, DELETE = 2, PUT = 3 } public class HttpRemoteCall { public int TimeoutMilisecs = 5000; public HttpMethods HttpMethod = HttpMethods.GET; public string NetworkUserID; public string NetworkPassword; private HttpWebRequest _request; private readonly string _url; public HttpRemoteCall(string url) { _url = url; } public HttpRemoteCall(string url, string networkUser, string networkPin) : this(url) { NetworkUserID = networkUser; NetworkPassword = networkPin; } }
The basics of the class are the default values and network credentials required to make a web request.
Some logic to construct the WebRequest instance:
public void Create(WebHeaderCollection headers, string send) { _request = (HttpWebRequest)WebRequest.Create(_url); _request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache); bool hasCredentials = !string.IsNullOrEmpty(NetworkUserID); if (hasCredentials) _request.Credentials = new NetworkCredential(NetworkUserID, NetworkPassword); if (!string.IsNullOrEmpty(send) && HttpMethod == HttpMethods.GET) { //default to POST HttpMethod = HttpMethods.POST; } _request.Method = HttpMethod.ToString(); _request.ReadWriteTimeout = TimeoutMilisecs; _request.Timeout = TimeoutMilisecs; _request.Headers = headers; } public void Create() { Create(new WebHeaderCollection(), null); } public void Create(string send) { Create(new WebHeaderCollection(), send); } public void Create(WebHeaderCollection headers) { Create(headers, null); }
So far, so good. This is what a trivial implementation might look like.
HttpRemoteCall remoteWS = new HttpRemoteCall("http://google.com"); remoteWS.Create();
How To Post To A Remote Server
We need to amend the Create method to handle the writing of data. To do this we will create a postToRemote method where we will assume UTF8 encoding which is a starting point for extensions into multiple encodings which I have not found a use for myself so have not implemented.
So the core functionality will look like:
private void postToRemote(string send) { byte[] b = new byte[] {}; if (!string.IsNullOrEmpty(send)) { b = Encoding.UTF8.GetBytes(send); } _request.ContentLength = b.Length; _request.GetRequestStream().Write(b, 0, b.Length); _request.GetRequestStream().Flush(); _request.GetRequestStream().Close(); } public void Create(WebHeaderCollection headers, string send) { _request = (HttpWebRequest)WebRequest.Create(_url); bool hasCredentials = !string.IsNullOrEmpty(NetworkUserID); if (hasCredentials) _request.Credentials = new NetworkCredential(NetworkUserID, NetworkPassword); if (!string.IsNullOrEmpty(send) && HttpMethod == HttpMethods.GET) { //default to POST HttpMethod = HttpMethods.POST; } _request.Method = HttpMethod.ToString(); _request.ReadWriteTimeout = TimeoutMilisecs; _request.Timeout = TimeoutMilisecs; _request.Headers = headers; if (HttpMethod != HttpMethods.GET) { postToRemote(send); } }
This is all cool but it lacks some basic stuff like cache policy as well as content type and accept headers (as well as other header options but those are the most common ones I found re-occur in patterns for web remote calls).
So we need to define:
public class HttpRemoteCall { public enum WebRemoteCallAccepts { //[Description("text/xml")] Default = 1, //[Description("text/html, application/xhtml+xml, */*")] StdHttp = 2 } public WebRemoteCallAccepts Accepts = WebRemoteCallAccepts.StdHttp; public enum WebRemoteCallContentTypes { //[Description("text/xml;charset=\"utf-8\"")] Default = 1, //[Description("text/html;charset=\"utf-8\"")] StdHttp = 2 } public WebRemoteCallContentTypes ContentType = WebRemoteCallContentTypes.StdHttp; public int TimeoutMilisecs = 5000; public HttpMethods HttpMethod = HttpMethods.GET; public string NetworkUserID; public string NetworkPassword; private HttpWebRequest _request; private readonly string _url; public HttpRemoteCall(string url) { _url = url; } public HttpRemoteCall(string url, string networkUser, string networkPin) : this(url) { NetworkUserID = networkUser; NetworkPassword = networkPin; } public void Create() { Create(new WebHeaderCollection(), null); } public void Create(string send) { Create(new WebHeaderCollection(), send); } public void Create(WebHeaderCollection headers) { Create(headers, null); } public void Create(WebHeaderCollection headers, string send) { _request = (HttpWebRequest)WebRequest.Create(_url); _request.CachePolicy = new System.Net.CacheHttpRequestCachePolicy(System.Net.Cache.HttpRequestCacheLevel.BypassCache); bool hasCredentials = !string.IsNullOrEmpty(NetworkUserID); if (hasCredentials) _request.Credentials = new NetworkCredential(NetworkUserID, NetworkPassword); if (!string.IsNullOrEmpty(send) && HttpMethod == HttpMethods.GET) { //default to POST HttpMethod = HttpMethods.POST; } _request.Method = HttpMethod.ToString(); _request.ReadWriteTimeout = TimeoutMilisecs; _request.Timeout = TimeoutMilisecs; _request.Headers = headers; _request.ContentType = ContentType.Description(); _request.Accept = Accepts.Description(); if (HttpMethod != HttpMethods.GET) { postToRemote(send); } } //etc... } //... public static class Extensions { public static string Description(this Enum value) { string name = Enum.GetName(value.GetType(), value); System.ReflectionFieldInfo field = value.GetType().GetField(name); if (field != null) { DescriptionAttribute attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute; return attr != null ? attr.Description : name.AddSpaces(); } return name.AddSpaces(); } }
And we'll need to do some error handling into the posting method:
//... //part of HttpRemoteCall private void postToRemote(string send) { byte[] b = new byte[] {}; if (!string.IsNullOrEmpty(send)) { b = Encoding.UTF8.GetBytes(send); } _request.ContentLength = b.Length; try { _request.GetRequestStream().Write(b, 0, b.Length); _request.GetRequestStream().Flush(); _request.GetRequestStream().Close(); } catch (Exception e) { if (Handy.TypeTester(e.GetType(), new Type[] { typeof (ArgumentException), typeof (ArgumentNullException), typeof (ArgumentOutOfRangeException), typeof (IOException) })) { throw new NetworkException(string.Format("Request stream write fail =>{0}", e.Message), e.InnerException); } if (Handy.TypeTester(e.GetType(), new Type[] { typeof (ProtocolViolationException), typeof (InvalidOperationException), typeof (WebException) })) { throw new NetworkException(string.Format("Request send fail =>{0}", e.Message), e.InnerException); } throw; } } //... public class NetworkException : ApplicationException { public NetworkException(string message) : base(message) { } public NetworkException(string message, Exception inner) : base(message, inner) { } } //... public static class Handy { public static bool TypeTester(Type isType, IEnumerable<Type> testTypes) { return testTypes.Any(testType => testType == isType); } }
Such that the implementation will be:
HttpRemoteCall remoteWS = new HttpRemoteCall("http://google.com"); try { remoteWS.Create(); } catch (NetworkException e) { Console.WriteLine(e); }
Putting It All Together
That takes care of the request construct. Now we need to retrieve and parse the response.
We'll wrap the response data in a custom class:
public class HttpRemoteResponse { public string Raw; public int StatusCode; public string ContentType; public List<KeyValuePair<string, string>> Headers; public string CharSet; }
Then add a method to HttpRemoteCall to stream the content from the remote server and output an object of this type:
public HttpRemoteResponse Response() { HttpRemoteResponse returnResponse = new HttpRemoteResponse { Raw = null }; WebResponse webResponse = null; try { webResponse = _request.GetResponse(); } catch (WebException wex) { if (webResponse != null) webResponse.Close(); HttpWebResponse httpResponse = wex.Response as HttpWebResponse; if (httpResponse != null) returnResponse.StatusCode = (int)httpResponse.StatusCode; //infer default Internal Server fault returnResponse.StatusCode = (int)HttpStatusCode.InternalServerError; return returnResponse; } //infer default bad request returnResponse.StatusCode = (int)HttpStatusCode.BadRequest; using (HttpWebResponse response = (HttpWebResponse)webResponse) { //get actual returnResponse.StatusCode = (int)response.StatusCode; using (Stream stream = response.GetResponseStream()) { try { if (stream == null) { //infer... returnResponse.StatusCode = (int)HttpStatusCode.NoContent; } //don't handle catch; auto re-throw if error returnResponse.Raw = new StreamReader(stream).ReadToEnd(); returnResponse.ContentType = response.ContentType; returnResponse.Headers = webResponseHeaders .AllKeys .Select(headerKey => new KeyValuePair<string, string>(headerKey, webResponse.Headers[(string)headerKey])) .ToList(); returnResponse.CharSet = response.CharacterSet; } finally { webResponse.Close(); } } } return returnResponse; }
Such that the implementation becomes:
HttpRemoteCall remoteWS = new HttpRemoteCall("http://google.com"); try { remoteWS.Create(); } catch (NetworkException e) { Console.WriteLine(e); } response = remoteWS.Response(); switch ((HttpStatusCode)response.StatusCode) { case HttpStatusCode.OK: Console.WriteLine("Hunky-Dorry"); break; case HttpStatusCode.InternalServerError: case HttpStatusCode.BadRequest: case HttpStatusCode.NoContent: Console.WriteLine(response.StatusCode); Console.WriteLine("internally inferred status codes - investigate response object further -> is server returning status code or is remote call inferring status code..."); break; default: Console.WriteLine(response.StatusCode); //TODO: handle each individually? break; } Console.WriteLine(response.Raw);















