Build Out a Clean, Minimal-Footprint REST-ful API
Update (7/12/2013) - A commentor on the actual blog post has noted that the example Api here is not quite, in fact, RESTful per the REST model. In a subsequent comment, he includes a substantial number of helpful links links to better expand on the topic. I appreciate his most helpful feedback. On the other hand, this post was aimed at understanding how to build out a minimal WebApi project in the direction of a restful API. Look for an upcoming article which expands on the (hotly debated) RESTful compliance.
In a previous post, we saw how to create a minimal ASP.NET WebApi project template, so that we can avoid some of the bloat and complexity inherent in the standard VS WebApi project. Getting started building out an API project using the minimal project template is much easier, and we can always add back stuff as our project grows.
For starters, though, the minimal project makes it much easier to focus on our principle task – creating a simple, clean API. This minimal project is especially useful if, like myself, you are less-than-experienced, and learning your way through Web API development. Or, if you just want a simple project.
The source code for the projects used in this article can be found at my Github Repo:
I. Create an Empty (or nearly so) WebApi Project
As we have seen, the default WebApi project in Visual Studio can be a bit much. To start with a stripped-down, minimal project template, either review the previous post referred to in the link above, which will install the Empty WebApi Project template into Visual Studio directly, clone the EmptyWebApi Project from my Github Repository, or download the zipped project.
II. Modeling Our Data
In building our example, we will adhere to what are recommended conventions for an MVC project. While we are not creating a website proper, it will serve us well to follow the conventions designed into the MVC framework (of which WebApi is a part). Note how I have set up the basic project, with a Models
folder and a Controllers folder (at this point, we don't need Views). If you have worked with a standard ASP.NET MVC project before, this part should look pretty familiar.
Right-click on the Models folder in Solution Explorer, and select the "Add Class" Menu Item. Name the class Person
and hit Ok. Now add the following code to the Person
class:
Code for the Person Class:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
The Person
class will be used to represent data from our data store once loaded into our application. Again following something of a convention, we will use a simple repository pattern to represent our database. It is good practice to use an interface to abstract away the specific implementation of your data store, and we will do so here. First of all, because we are going to avoid cluttering up this tutorial (at this point) with details of wiring up a database, and second of all because even in a finished project, your data storage requirements may change. With a properly implemented interface representing your datastore in code, changing up the database-specific code does not require changes to the rest of our project, only the concrete database implementation classes.
NOTE: I am aware that the Repository Pattern has fallen slightly out-of-favor in some quarters. Also, If you are planning to use Entity Framework, NHibernate, or another ORM, your approach in a real project might be different with respect to actual data access implementation. However, for this simple example, the repository pattern works.
Right-click on the Models folder again, and this time select "Add New Item." From from the "Code" sub-menu, select "Interface." Name the interface IPersonRepository
and hit Ok. Then, add the following code to the newly created interface:
Code for the IPersonRepository Interface:
interface IPersonRepository
{
IEnumerable<Person> GetAll();
Person Get(int id);
Person Add(Person person);
void Remove(int id);
bool Update(Person person);
}
Now that we have defined the interface for our repository, let's create a concrete implementation. Right-click once more on the Models folder in Solution Explorer, and select "Add Class" again. Name the new class PersonRepository
and hit Ok. Now add the following code, which we'll walk through in a moment:
Code for the PersonRepository Class:
public class PersonRepository : IPersonRepository
{
private List<Person> _people = new List<Person>();
private int _fakeDatabaseID = 1;
public PersonRepository()
{
this.Add(new Person { LastName = "Lennon", FirstName = "John" });
this.Add(new Person { LastName = "McCartney", FirstName = "Paul" });
this.Add(new Person { LastName = "Harrison", FirstName = "George" });
this.Add(new Person { LastName = "Starr", FirstName = "Ringo" });
}
public IEnumerable<Person> GetAll()
{
return _people;
}
public Person Get(int id)
{
return _people.Find(p => p.Id == id);
}
public Person Add(Person person)
{
if (person == null)
{
throw new ArgumentNullException("person");
}
person.Id = _fakeDatabaseID++;
_people.Add(person);
return person;
}
public void Remove(int id)
{
_people.RemoveAll(p => p.Id == id);
}
public bool Update(Person person)
{
if (person == null)
{
throw new ArgumentNullException("person");
}
int index = _people.FindIndex(p => p.Id == person.Id);
if (index == -1)
{
return false;
}
_people.RemoveAt(index);
_people.Add(person);
return true;
}
}
Note in the code above, we are using a List<Person>
to represent a datastore. In a real API application, you would most likely be using this class to access a database,
XML file, or other persistence mechanism. We will look at connecting it all to a database in another post. For now I am keeping it simple, and loading some mock data into the list during
initialization.
Using API Controllers to Access Our API Service
Unlike an ASP.NET/MVC Project, which generally uses Controllers derived from System.Web.MVC.Controller
, a WebApi project will generally utilize controllers derived from System.Web.Http.WebApiController
. Like the standard MVC Controller, The WebApi Controller accepts incoming HTTP requests and processes accordingly. However, the standard MVC Controller returns a View, while the WebApi controller returns data.
Of particular note is that the WebApiController
examines the Accept Header in the HTTP Request, and (where applicable) converts our .Net objects into the appropriate return data type (usually either XML or JSON). In this way, consumers of your API can specify the format required, and expect the proper return in the HTTP Response from the server. Content negotiation details are beyond the scope of this article, but suffice it to say that the WebApiController
is what delivers the goods in a WebApiProject.
Right-click on the Controllers folder and select "Add Controller." In the dialog window, name the new controller PersonController
and then select Empty WebApi Controller from the drop-down list of controller templates:
Notice that the generated class inherits from ApiController
.
Now add the following code for some basic CRUD operations to the new controller (Note you need to add the using statement to reference the classes you defined in the Models Folder – see comment at the top of the class):
Code for the PersonController Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using MinimalApiDemo.Models;
namespace MinimalApiDemo.Controllers
{
public class PersonController : ApiController
{
static readonly IPersonRepository databasePlaceholder = new PersonRepository();
public IEnumerable<Person> GetAllPeople()
{
return databasePlaceholder.GetAll();
}
public Person GetPersonByID(int id)
{
Person person = databasePlaceholder.Get(id);
if (person == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return person;
}
public HttpResponseMessage PostPerson(Person person)
{
person = databasePlaceholder.Add(person);
string apiName = App_Start.WebApiConfig.DEFAULT_ROUTE_NAME;
var response = this.Request.CreateResponse<Person>(HttpStatusCode.Created, person);
string uri = Url.Link(apiName, new { id = person.Id });
response.Headers.Location = new Uri(uri);
return response;
}
public bool PutPerson(Person person)
{
if (!databasePlaceholder.Update(person))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return true;
}
public void DeletePerson(int id)
{
Person person = databasePlaceholder.Get(id);
if (person == null)
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
databasePlaceholder.Remove(id);
}
}
}
A Closer Look at the WebApi Controller
As we have alluded to previously, a core tenet of MVC is to favor Convention over Configuration. In terms of our controller class, what this means is that the ASP.NET/MVC runtime establishes certain default behaviors which require no additional configuration, unless you want to change them. Initially, for me anyway, this was also a source of confusion, as the runtime appeared to perform sufficient "magic" that I didn't know what was happening.
One of the MVC conventions is the mapping of controller methods to HTTP verbs. In our case, we are specifically mapping methods to the HTTP verbs GET, POST, PUT, and DELETE. Notice how our method names begin with one of these four verbs? Our two methods which retrieve person data, GetAllPeople
and GetPersonByID
begin with the word "Get." The MVC runtime will map HTTP GET requests to this controller and to one of these methods by following the convention set up in the Route mapping we registered in our WebApiConfig
file:
If we go into the App_Start folder, and open the WebApiConfig
file, we should see something similar to this:
public static class WebApiConfig
{
public const string DEFAULT_ROUTE_NAME = "MyDefaultRoute";
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: DEFAULT_ROUTE_NAME,
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.EnableSystemDiagnosticsTracing();
}
}
In the above, the route template is used by the MVC framework to map incoming HTTP requests to the appropriately named controller (named such that the first part of the controller name matches the {controller} mapping, and to the most appropriate method with a name that begins with the HTTP verb specified in the request.
In order for all this to work, it becomes our responsibility to simply name our controllers and methods in accordance with this convention. For example, a GET request coming in with a URL mapping:
http://localhost:<port number>/person/
will be mapped to the PersonController.GetAllPeople()
method, since the URL contains the Person
mapping, the request contains the GET verb, and the optional /id parameter is not present.
Similarly, an HTTP GET request with the following URL mapping:
http://localhost:<port number>/person/2
will be mapped again to the PersonController
, but this time to the GetPersonByID
method, since the optional id parameter is provided.
NOTE: For more information on Routing in ASP.NET, see Routing Basics in ASP.NET MVC and Route Customization in ASP.NET MVC
Consuming the Example API with a Simple Console Client
The value of an exposed API service is that independent client applications can consume and interact with our API data in a manner we control through the API. We can make available as much, or as little, access as we wish. To demonstrate, we will create a very simple console application which will exercise our API project in a couple different ways.
To get started, open a second instance of Visual Studio, and use File –> New Project to create a Console Application. Then, go straight to the Tools –> Library Package Manager –> Manage Nuget Packages for Solution. Select "Online" in the left-hand tree menu, then, in the Search box, type "WebApi." Select the ASP.NET Web API Core Libraries package, and click Install.
Install WebApi Core Libraries in the Console Application
Once the installation is complete, select the Updates item from the left-hand tree menu. Install any relevant WebApi library updates.
WebApi and JSON give us Options for Client Code
WebApi provides flexible options for how we might retrieve data from our API application. Right out of the box, the WebApi library provides built-in serialization and de-serialization for POCO ("Plain Old CLR Objects") objects we might use, as well as send and receive straight JSON ("JavaScript Object Notation") or XML.
We will take a (very!) rudimentary look at client code that uses primarily JSON. One might at some point call into an API infrequently, and deem it not worth building a class hierarchy for these infrequent calls. We will also look at a more standard .NET approach, in which class models are built which represent data from the API we are consuming.
The example client project we are about to build by no means represents a design we might build in the real world. The purpose is to take a quick look at calling into a simple API and doing something semi-meaningful with the data.
Important note: for this sample code, I am using calls to GetAsync and ReadAsAsync and accessing the Result property of the return value. This creates blocking call, which is less than optimal for most real-world projects. I used this call to keep things simple in this demo. However, we will look at making non-blocking asynchronous calls in upcoming post.
Building the JSON-Based Example Code
The following example does not rely on a Person
class being present. We will use the Newtonsoft JSON library installed with WebApi to parse and use JSON, and to basically shuttle our data around. Where needed, we will utilize C# Anonymous classes where the API we are calling into demands it.
In the example project, I basically added a separate class for the separate calls into our API project. Then, I added an ugly, monolithic method which walks through each, and writes the data retrieved out to the console window. The important part in the code below is getting a feel for making the API calls.
I added a class called JsonExamples for this. At the top of the class, make sure to add the following using
statements:
Add Using Statements to the JsonExamples Class:
using System;
using System.Net.Http;
using Newtonsoft.Json.Linq;
Then, separate methods to call into our API project:
The Calls to the API GET, POST, PUT, and DELETE Controllers Using JSON:
static JArray getAllPeople()
{
HttpClient client = new HttpClient();
HttpResponseMessage response =
client.GetAsync("http://localhost:57772/api/person/").Result;
return response.Content.ReadAsAsync<JArray>().Result;
}
static JObject getPerson(int id)
{
HttpClient client = new HttpClient();
HttpResponseMessage response =
client.GetAsync("http://localhost:57772/api/person/" + id).Result;
return response.Content.ReadAsAsync<JObject>().Result;
}
static JObject AddPerson(string newLastName, string newFirstName)
{
var newPerson = new { LastName = newLastName, FirstName = newFirstName };
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:57772/");
var response = client.PostAsJsonAsync("api/person", newPerson).Result;
return response.Content.ReadAsAsync<JObject>().Result;
}
static bool UpdatePerson(int personId, string newLastName, string newFirstName)
{
var newPerson = new { id = personId, LastName = newLastName, FirstName = newFirstName };
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:57772/");
var response = client.PutAsJsonAsync("api/person/", newPerson).Result;
return response.Content.ReadAsAsync<bool>().Result;
}
static void DeletePerson(int id)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:57772/");
var relativeUri = "api/person/" + id.ToString();
var response = client.DeleteAsync(relativeUri).Result;
client.Dispose();
}
Last, an ugly, procedural chunk of code that walks through each of the methods above and writes the results to the console window:
Ugly, Monolothic Console Output Code:
public static void PrintJsonExamples()
{
Console.WriteLine("Retreive All The People:");
JArray people = getAllPeople();
foreach (var person in people)
{
Console.WriteLine(person);
}
Console.WriteLine(Environment.NewLine + "Retreive a Person by ID:");
JObject singlePerson = getPerson(2);
Console.WriteLine(singlePerson);
Console.WriteLine(Environment.NewLine + "Add a new Person and return the new object:");
JObject newPerson = AddPerson("Atten", "John");
Console.WriteLine(newPerson);
Console.WriteLine(Environment.NewLine + "Update an existing Person and return a boolean:");
JObject personToUpdate = getPerson(2);
string newLastName = "Richards";
Console.WriteLine("Update Last Name of " + personToUpdate + "to " + newLastName);
int id = personToUpdate.Value<int>("Id");
string FirstName = personToUpdate.Value<string>("FirstName");
string LastName = personToUpdate.Value<string>("LastName");
if (UpdatePerson(id, newLastName, FirstName))
{
Console.WriteLine(Environment.NewLine + "Updated person:");
Console.WriteLine(getPerson(id));
}
Console.WriteLine(Environment.NewLine + "Delete person object:");
JsonExamples.DeletePerson(5);
{
Console.WriteLine("Retreive All The People using classes:");
people = JsonExamples.getAllPeople();
foreach (var person in people)
{
Console.WriteLine(person);
}
}
Console.Read();
}
If we run the application by calling the PrintJsonExamples
method, we see the JSON results in our console:
Console Output – Json Data:
Building the Class-Based Example Code
Depending on the needs of your project, you may decide adding a Person
class to your API client makes sense. WebApi understands how to serialize and de-serialize .NET classes to and from JSON or XML, which are the two most common data exchange formats in use.
We can re-write our client examples to utilize a Person
class if it suits our needs, and pass instances of Person
to and from our API project indirectly through serialization. Our core client methods, re-written, look like this:
Important: See the note above about the blocking calls used in this example!
The Calls to the API GET, POST, PUT, and DELETE Controllers Using an added Person Class:
public class Person
{
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
}
static IEnumerable<Person> getAllPeople()
{
HttpClient client = new HttpClient();
HttpResponseMessage response =
client.GetAsync("http://localhost:57772/api/person/").Result;
client.Dispose();
return response.Content.ReadAsAsync<IEnumerable<Person>>().Result;
}
static Person getPerson(int id)
{
HttpClient client = new HttpClient();
HttpResponseMessage response =
client.GetAsync("http://localhost:57772/api/person/" + id).Result;
client.Dispose();
return response.Content.ReadAsAsync<Person>().Result;
}
static Person AddPerson(Person newPerson)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:57772/");
var response = client.PostAsJsonAsync("api/person", newPerson).Result;
client.Dispose();
return response.Content.ReadAsAsync<Person>().Result;
}
static bool UpdatePerson(Person newPerson)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:57772/");
var response = client.PutAsJsonAsync("api/person/", newPerson).Result;
client.Dispose();
return response.Content.ReadAsAsync<bool>().Result;
}
static void DeletePerson(int id)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:57772/");
var relativeUri = "api/person/" + id.ToString();
var response = client.DeleteAsync(relativeUri).Result;
client.Dispose();
}
Next, some minor variations on the ugly console-output code:
Ugly, Monolithic Code, Modified:
public static void PrintClassExamples()
{
string personOutputString = "id: {0}; LastName: {1}, FirstName: {2}";
Console.WriteLine("Retreive All The People using classes:");
IEnumerable<Person> people = ClassBasedExamples.getAllPeople();
foreach (var person in people)
{
Console.WriteLine(personOutputString, person.Id, person.LastName, person.FirstName);
}
Console.WriteLine(Environment.NewLine
+ "Retreive a Person object by ID:");
Person singlePerson = ClassBasedExamples.getPerson(2);
Console.WriteLine(personOutputString, singlePerson.Id,
singlePerson.LastName, singlePerson.FirstName);
Console.WriteLine(Environment.NewLine
+ "Add a new Person object and return the new object:");
Person newPerson = new Person { LastName = "Atten", FirstName = "John" };
newPerson = AddPerson(newPerson);
Console.WriteLine(personOutputString, newPerson.Id,
newPerson.LastName, newPerson.FirstName);
Console.WriteLine(Environment.NewLine
+ "Update an existing Person object:");
Person personToUpdate = getPerson(2);
string newLastName = "Richards";
Console.WriteLine("Updating Last Name of "
+ personToUpdate.LastName + " to " + newLastName);
personToUpdate.LastName = newLastName;
if (ClassBasedExamples.UpdatePerson(personToUpdate))
{
Console.WriteLine(Environment.NewLine + "Updated person object:");
Person updatedPerson = getPerson(2);
Console.WriteLine(personOutputString, updatedPerson.Id,
updatedPerson.LastName, updatedPerson.FirstName);
}
Console.WriteLine(Environment.NewLine + "Delete person object:");
ClassBasedExamples.DeletePerson(5);
{
Console.WriteLine("Retreive All The People using classes:");
people = ClassBasedExamples.getAllPeople();
foreach (var person in people)
{
Console.WriteLine(personOutputString, person.Id,
person.LastName, person.FirstName);
}
}
Console.Read();
}
Note: The data in the API project is not persisted. However, until the IIS Server re-starts, the data is maintained between project debugging sessions. Re-start IIS Express by right-clicking the IIS Express icon in the system tray. Then stop and restart the API project.
Again, if we run our code, we will see the console output, this time reflecting the manner in which we decided to format our class data:
Console Output – Class-Based Data:
Wrapping Up
We've taken a look at building out a very basic API using the minimal components. We've also looked quickly at creating a basic client to consume such an API, independently of the API project itself. Next we will examine implementing proper asynchronous methods in an API client, and examine more advanced controllers and querying.
Additional Resources