Building an API in .NET
Software Development March 14th, 2008You’ve done it. You have written the coolest, fanciest utility ever and you want to unleash it on the world. You throw it up on CodePlex or SourceForge and eagerly await the praise from developers all over the world for such a cool piece of software. Ten minutes later you get your first comment: “This is really cool but I need to integrate it with my other utility, do you have an API?”
DOH!!
You should have seen this coming. It’s a cardinal rule. Developers ALWAYS want to extend stuff, or use it in a way that you might not have anticipated. An API allows your product to be integrated with other products, and makes any software package that much more valuable. Let’s look at a bug tracker for example. There are all sorts of opportunities for an API with something like this. What if you want to trap unhandled exceptions in your application and create bugs with them automatically? What if you want to take any bugs that are assigned to you and have them show up in your task list? Those are perfect examples of why an API is useful.
OK. So we know we need an API, the question is how do we do it? What do we need to consider? I’m going to assume that our API will be web based, meaning that we will access it through port 80. The age old debate seems to be Representational State Transer (REST) vs. Simple Object Access Protocol (SOAP). I’m not going to incite a flame war by saying one is definitely better than the other, there are great examples of each. Off the top of my head, I’d say Flickr is an outstanding example of a REST API, while Amazon is a great example of a SOAP API.
DISCLAIMER: It was correctly pointed out to me that Flickr’s API is not a good example of a REST API, and my example API in this article is actually more like RPC than REST.
Basically, our API will take a request, and return an XML string with the response. The first thing we need to do in order to make that happen is to write our own HttpHandler. To do this, you need to create a class that implements the IHttpHandler interface in the System.Web namespace. Your class should look something like this after implementing this interface:
1: public class TestHandler : IHttpHandler
2: {
3: #region IHttpHandler Members
4: public bool IsReusable
5: {
6: get { throw new Exception("The method or operation is not implemented."); }
7: }
8:
9: public void ProcessRequest(HttpContext context)
10: {
11: throw new Exception("The method or operation is not implemented.");
12: }
13: #endregion
14: }
I’ll start with the IsReusable property. This just indicates that a single instance of our handler can handle multiple concurrent requests. What does this mean to you? For your handler to be reusable, it must be thread safe. The ProcessRequest function does all the work for our handler, taking the HttpContext as a parameter. To keep this example manageable, we will only create one method for our API, we’ll call it LoadPeople. Since our API will be returning data in an XML format, we need to decide what the format of our XML should look like. Each of our people records will have a first name and a last name, so our XML could look like this:
1: <People>
2: <Person>
3: <FirstName>John</FirstName>
4: <LastName>Doe</LastName>
5: </Person>
6: <Person>
7: <FirstName>Fred</FirstName>
8: <LastName>Smith</LastName>
9: </Person>
10: </People>
11:
1: <httpHandlers>
2: <add verb="*" path="API/*.ashx" type="RestAPI.TestHandler, RestAPI,
3: Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
4: </add>
5: </httpHandlers>
I’m not going to go into a lot of detail about this part, I’ll just sum it up by saying that my website project has it’s assembly name set to RestAPI in the properties, and our handler’s classname is TestHandler. You can get more information on how to set these up here or here. I chose .ashx as my extension arbitrarily, so use whatever extension you like.
Lastly, we need to write the request processing function of our handler. I threw together a quick example, and mine ended up looking like this:
1: public class Person
2: {
3: private string _firstName;
4: private string _lastName;
5: public string FirstName
6: {
7: get { return _firstName; }
8: }
9: public string LastName
10: {
11: get { return _lastName; }
12: }
13: public Person(string fname, string lname)
14: {
15: _firstName = fname;
16: _lastName = lname;
17: }
18: }
19:
20: public class TestHandler : IHttpHandler
21: {
22: #region IHttpHandler Members
23: public bool IsReusable
24: {
25: get { return true; }
26: }
27:
28: public void ProcessRequest(HttpContext context)
29: {
30: // use the http context to figure out what method they
31: // are trying to call.
32: string methodName = GetMethodName(context.Request.Path);
33:
34: // load a dummy list of people to return.
35: List<Person> personlist = new List<Person>();
36: personlist.Add(new Person("John", "Doe"));
37: personlist.Add(new Person("Fred", "Smith"));
38:
39: switch (methodName)
40: {
41: case "loadpeople":
42: // Return the list of people.
43: using (XmlTextWriter writer =
44: new XmlTextWriter(context.Response.OutputStream, Encoding.ASCII))
45: {
46: context.Response.Clear();
47: writer.Formatting = Formatting.Indented;
48: writer.WriteStartDocument();
49: writer.WriteStartElement("People");
50:
51: foreach (Person p in personlist)
52: {
53: writer.WriteStartElement("Person");
54: writer.WriteElementString("FirstName", p.FirstName);
55: writer.WriteElementString("LastName", p.LastName
56: writer.WriteEndElement();
57: }
58:
59: writer.WriteEndElement();
60: writer.WriteEndDocument();
61: writer.Close();
62: context.Response.End();
63: }
64: break;
65:
66: // This means they called an invalid method, let them know that.
67: default:
68: using (XmlTextWriter writer =
69: new XmlTextWriter(context.Response.OutputStream, Encoding.ASCII))
70: {
71: context.Response.Clear();
72: writer.Formatting = Formatting.Indented;
73: writer.WriteStartDocument();
74: writer.WriteStartElement("response");
75: writer.WriteElementString("errorType", "InvalidMethod");
76: writer.WriteElementString("methodName", methodName);
77: writer.WriteEndElement();
78: writer.WriteEndDocument();
79: writer.Close();
80: context.Response.End();
81: }
82: break;
83: }
84: }
85: #endregion
86:
87: private string GetMethodName(string path)
88: {
89: // I know that this handler is called like this: /API/function.ashx
90: string[] matches = path.Split('/');
91: int count = matches.GetLength(0);
92: string method = matches[count - 1].Substring(0, matches[count - 1].Length - 5);
93: return method.ToLower();
94: }
95: }
When you are sending output through the API, you can use the Response object in the HttpContext that is passed into the ProcessRequest function. I used the XMLTextWriter class for my output, and just sent the output to the OutputStream of the Response object, as seen on lines 44 and 69 above. Everything should be fairly self explanatory above….I just put together a dummy collection to return to the user. This sample API relies on the function being requested being called through the URL, so I also included a way to tell the user that they requested an invalid method.
One thing that I didn’t touch on with this example is parameters. You could fairly easily have full parameter support by just passing them through the querystring. For example, you could add an AddPerson function to our sample API and call it like this: http://www.oursite.com/API/AddPerson.ashx?firstName=Jimmy&lastName=Jones. We would just have to be sure to error check our parameters for the function, etc.
That’s all their is to it, an API in just a few steps!
March 15th, 2008 at 12:35 am
You should be using WCF 3.5 to implement a REST API. Performing all the plumbing and URI mapping is a waste of time programming.
March 15th, 2008 at 9:22 pm
This is interesting but there is nothing even slightly RESTfull about what you’ve done. You’ve linked to the Fielding thesis but you don’t appear to have read it, or, if you did, then you certainly didn’t understand it. The Flickr api is also not a great example of this idea — instead take a look at the Amazon S3 service or at del.licio.us for an example of a hybrid (RPC/REST) approach that offers RESTfull urls (not true REST when you look closely but a good imitation). Finally, take a look at the MS project Astoria (aka Data Services) to get an idea of what a really good RESTful implementation should look like,
Your AddPerson handler would seem to necessitate additional DelPerson, GetPerson, UpdatePerson, and ListPeople handlers. You’re API consists of separate custom handlers – very RPC-ish and not remotely RESTfull. A more RESTfull implementation would a Person resource which is capable of handling all four HTTP verbs at most (High-REST) or GETs and POSTs (utilizing POSTs for PUT and DELETE – Low-REST)
PUT http://oursite.com/API/Person
BILL…
GET http://oursite.com/API/Person/1
DELETE http://oursite.com/API/Person/1
POST PUT http://oursite.com/API/Person/1
William…
March 16th, 2008 at 9:49 am
@Bart: I don’t have enough experience in WCF to say one way or the other how well it does this sort of thing, but I wouldn’t call what I’ve done a waste of time by any stretch of the imagination.
If nothing else, writing this article gave me a better understanding of how all the “plumbing and URI mapping” works.
March 16th, 2008 at 10:07 am
@Garren: Good comment, it made me do a lot of reading on the subject over the weekend. I will have to agree with you, my solution is much more RPC than REST. I did not read the Fielding thesis before I wrote this article, I linked to it only because I knew it existed.
I based my definition of REST on the faulty logic that flickr was a good example of it. Flickr also appears to be more RPC than REST. (I guess that this would apply to Twitter also)
Anyway, thanks for the comment.
March 16th, 2008 at 10:08 am
I stumbled across this article over the weekend that I had to link to, very funny.
http://tomayko.com/writings/rest-to-my-wife
March 25th, 2008 at 8:47 am
Did you know that your posts are being stolen by SpotGnome? Your content is being stripped of your name, and posted on their site as your own. Here’s your post on their site, with their copyright.
http://www.spotgnome.com/2008/03/18/BuildingAnAPIInNET.aspx
I’m writing to you because this is happening to me as well, and I don’t like one little bit. See the comments at the end of my stolen post:
http://www.spotgnome.com/CommentView,guid,c672cf17-39dd-4abd-a263-6f0c78537daf.aspx#commentstart
March 25th, 2008 at 9:44 am
Wow. That’s shady, thanks for the tip.
February 5th, 2009 at 7:02 am
Hi Jack,
thanks for this easy to understanding sample.
However, I can’t make it work with VS2008 and IIS7…
VS tells me that I’ve to include a token like .
When I include this token I get an error message:
HTTP Error 500.23 – Internal Server Error
An ASP.NET setting has been detected that does not apply in Integrated managed pipeline mode
I found a solution for to add
to the web config.
But than I get the configuration error in the line add verb=”*” path=”API/*.ashx” type=”RestAPI.TestHandler, RestAPI, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”>
Maybe somebody can help me?
Thanks, Andre
March 12th, 2009 at 9:43 am
Adre, are you sure your namespace is called RestAPI?