Friday, March 30, 2012

Converting to System.Net.Http

Converting to System.Net.Http:
The source code for the HttpClass can be found at this gist: here.
The revised source code from the earlier posts has been updated and you can find the revised bits here. This code contains everything outlined in this post.
In my recent posts on how to use the ASP.NET Web API, I’ve continued to use the existing HttpWebRequest and Response objects. While they work, you have to run through a few hoops to make them usable. For example, you have to roll your own mechanism for streaming and reading the content. There are not not clean and easy to use methods to do that. For the most part, it has not been a big deal because abstractions have been built on top of those base artifacts (like I did in the last set of posts..:-) ).
Today, there is a new namespace we can use: System.Net.Http. There is a nice intro blog post from Henrik Nielsen of Microsoft which you can find here.

Tak Henrik for denne dejlig introduktion til HttpClient :-)

In Henrik’s post, he goes through the details of acquiring the necessary bits from Nuget. To summarize, you need 3 assemblies:

NOTE: All of these bits are still in the beta stage – so caveat emptor!!
System.Net.Http

System.Net.Http.Formatting

System.Json
For the code sample below, I have an additonal dependency Json.NetIf you have been following along in the previous posts, you already have it! I namedthe new class HttpClass. This class replaces the RequestMethod class in the previous examples. The only affected class is the ProductRepository that consumed the WebAPI service. I’ll outline that revised code in this post as well. Here are the usings for new HttpClass:
using System;
using System.Linq;
using System.Net.Http;
using Newtonsoft.Json.Linq;
using System.Net.Http.Headers;

Instead of passing the string representation of the supported methods, I created an enum:
public enum SupportedHttpMethods {
GET,
POST,
PUT,
DELETE
}

Before getting to the specifics of the new HttpClass, let’s focus on two exception classes:
public class InvalidHttpMethodContentCombinationException : Exception
{
public InvalidHttpMethodContentCombinationException()
: base("When speciying content, either a POST or PUT must be used") { }
}

public class InvalidHttpMethodException : Exception
{
public InvalidHttpMethodException()
: base("Only PUT, POST, GET and DELETE Methods are supported") { }
}

The idea is that if you are specifying content to pass back to the service, the method must either be a post or a put. For this use case, there are only 4 methods supported: put, post, get and delete. In momnent, you will see that if you try to specify content for a get or a delete or if you try to use an unsupported method, an exception will be throw.
Now, let’s get into the specifics of the HttpClass:
public class HttpClass
{
Uri _uri;
HttpMethod _httpMethod;
StringContent _content;
HttpClient _httpClient = new HttpClient();
Action _action;
HttpResponseMessage _httpResponseMessage;

public HttpClass(
SupportedHttpMethods httpMethod,
string uri,
string content) : this(httpMethod,uri)
{

if (httpMethod == SupportedHttpMethods.POST ||
httpMethod == SupportedHttpMethods.PUT)
{
JObject.Parse(content);
_content = new StringContent(content);
_content.Headers.ContentType =
new MediaTypeHeaderValue("text/json");
}
else
{
throw new InvalidHttpMethodContentCombinationException();
}
}

public HttpClass(
SupportedHttpMethods httpMethod,
string uri) {
_uri = new Uri(uri);
_httpMethod = new HttpMethod(httpMethod.ToString());

switch (httpMethod) {

case SupportedHttpMethods.GET:
_action = get;
break;

case SupportedHttpMethods.POST:
_action = post;
break;

case SupportedHttpMethods.PUT:
_action = put;
break;

case SupportedHttpMethods.DELETE:
_action = delete;
break;

default:
throw new InvalidHttpMethodException();
}
}

There are a number of private members on this class. To handle which http method to call, I interogate the method name passed and assign the private _action varable to appropriate private action (get, put, post or delete). In the constructor, I make a call to Jobject.Parse. The goal here is to make sure what was passed in is well-formed JSON. If it’s not, the JObject.Parse() method will throw an exception.
void delete() {
_httpResponseMessage = _httpClient.DeleteAsync(_uri).Result;
}
void get() {
_httpResponseMessage = _httpClient.GetAsync(_uri).Result;
}
void post() {
_httpResponseMessage = _httpClient.PostAsync(_uri, _content).Result;
}
void put() {
_httpResponseMessage = _httpClient.PutAsync(_uri, _content).Result;
}

Here, we have 4 discrete private actions to handle the service call. To invoke, the following public method exists:
public void Invoke(){
_action.Invoke();
}

Remember, _action has already been assigned based on the http method passed to the constructor. The HttpClass instance is immutable. Once it has been setup, that’s it. if you need to make a revision, you will need to create a new instance.
Going back to the private actions, note that the results are stored to an internal _httpResponseMessage. If you need access to that object:
public HttpResponseMessage GetHttpResponseMessage() {
return _httpResponseMessage;
}

In this use case, the only time we need anything back from the server is when we are issuing a get. Certainly, you could modify the class to always return some sort of status object. The key is for gets, we are interested in the content – not the request object so much. The goal of this class is to abstract away the details of having to unpack the response body.
To make it easy to get the response data, I added this public method:
public string GetResponseContent() {

if (_httpMethod.Method == SupportedHttpMethods.GET.ToString())
return _httpResponseMessage.Content.ReadAsStringAsync().Result;

return null;

}
That’s the entire HttpClass. Here is the revised code for the ProductRepository that will use the HttpClass service (that wraps System.Net.Http) to interact with the Web API:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using MVCWebAPIConsumer.Models;

namespace MVCWebAPIConsumer
{
public class ProductRepository
{

private string restService =
ConfigurationManager.AppSettings["restService"];
ISerialization _serializer;

public ProductRepository(ISerialization serializer) {
_serializer = serializer;
}

public Product New() {

return new Product();
}

public void Update(Product product) {

var httpClass = new HttpClass(
SupportedHttpMethods.PUT,
string.Format("{0}/api/products", restService),
_serializer.Serialize(product));

httpClass.Invoke();
}

public List Get() {

var httpClass = new HttpClass(
SupportedHttpMethods.GET,
string.Format("{0}/api/products",
restService));
httpClass.Invoke();

return _serializer.
DeSerialize>(httpClass.GetResponseContent()) as List;
}

public Product Get(int id) {

var httpClass = new HttpClass(
SupportedHttpMethods.GET,
string.Format("{0}/api/products/{1}",
restService,
id));
httpClass.Invoke();

return _serializer.
DeSerialize(httpClass.GetResponseContent()) as Product;
}

public void Create(Product product)
{

var httpClass = new HttpClass(
SupportedHttpMethods.POST,
string.Format("{0}/api/products",
restService),
_serializer.Serialize(product));

httpClass.Invoke();
}

public void Delete(int id) {

var httpClass = new HttpClass(
SupportedHttpMethods.DELETE,
string.Format("{0}/api/products/{1}",
restService, id));

httpClass.Invoke();

}
}
}

This is a lot cleaner than the older versioin. Note that the redundant serialization methods have been removed. There is no need for them because the serializer is injected into the repository. I did make an addition to the ISerlization Interface. Before, the only thing you could hand in was a stream. Now, I let you hand in a string (which I should have done all along!! :-) ) The reason I had to do support streams is because the System.Runtime.Serialization.Json serializer does not support handing in a string.
using System;
using System.IO;
using System.Linq;

namespace MVCWebAPIConsumer
{
public interface ISerialization
{
string Serialize(object o);
object DeSerialize(Stream stream);
object DeSerialize(string content);
}
}

Here is the revised JsonNetSerialization Class:
using System;
using System.IO;
using System.Linq;
using Newtonsoft.Json;

namespace MVCWebAPIConsumer
{
public class JsonNetSerialization : ISerialization
{
public string Serialize(object o)
{
return JsonConvert.SerializeObject((T)o);
}

public object DeSerialize(System.IO.Stream stream)
{
return DeSerialize(new StreamReader(stream).ReadToEnd());
}

public object DeSerialize(string content)
{
return JsonConvert.DeserializeObject(content);
}
}
}

And if you still want to use System.Runtime.Serialization.Json :
using System;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Json;

namespace MVCWebAPIConsumer
{
public class DefaultSerialization : ISerialization
{
public string Serialize(object o)
{
String json;
using (var stream = new MemoryStream())
{
var serializer = new DataContractJsonSerializer(typeof(T));
serializer.WriteObject(stream, (T)o);
json = Encoding.UTF8.GetString(stream.ToArray());
}
return json;
}

public object DeSerialize(System.IO.Stream stream)
{
var serializer = new DataContractJsonSerializer(typeof(T));
return serializer.ReadObject(stream);
}

public object DeSerialize(string content)
{
var byteArray = Encoding.ASCII.GetBytes(content);

StringReader reader = new StringReader(content);
return DeSerialize(new MemoryStream(byteArray));
}
}
}

Enjoy!!








No comments:

Post a Comment

Thank's!