Creating your first ASP.Net Web API : 10 Years of .Net Compressed into Weeks #14
on
This post looks at creating an API layer for an application. This post is part of a blog series ASP.Net 10 Years On. Even though this is part of a series I have tried to make each post standalone. In the previous post we discussed the merits of dropping the service layer in favour of an HTTP API. Now we look at creating this layer.
Introducing the Web API
The ASP.Net Web API shipped with ASP.Net MVC 4 and is a framework for building HTTP services. In particular it is a great tool for creating a RESTful application. If you have experience with MVC then using the Web API will be straight forward since the framework uses familiar concept such as controllers, routing etc. See the official website for beginner tutorials.
Getting Started
Taking the Users entity as an example let us dive into some code. A user entity has basic CRUD operations which need to be exposed via the HTTP API. This code is taken from the UsersController in the API project.
A tiny bit of REST
A very high level example of REST that will provide just enough detail to follow the below code snippets is to understand a resource and a verb. A resource is a representation of a thing. In our example the resource is a user entity. If we want to change the state of our resource we need some way of performing actions, or a verb. REST attempts to standardise these verbs so that all parties know what to expect when they are working with a resource.
The following table lists the HTTP verbs we will use and maps each verb to its usage.
Verb | Usage | Example Url |
---|---|---|
GET | Used for getting data in a read only context. | localhost/users OR localhost/users/<user_name> for a specific resource (think get by id). |
POST | Used to create a new resource. | localhost/users/<user_name> with parameters of POST body containing parameters. |
PUT | Used to update an existing resource. | localhost/users/<user_name> with parameters of PUT body containing parameters. |
DELETE | Used to delete a resource. | localhost/users/<user_name> |
NB REST is not simply the application of CRUD using the verbs GET, POST, PUT etc.
Code Example
The following code applies the REST principles previously outlined.
Starting with the simple "get all" and "get by id" methods the Web API actions would look as follows:
// GET users
public IEnumerable<UserItem> Get()
{
var users = _userRepository.FindAll();
return _mapper.Map<IEnumerable<User>, IEnumerable<UserItem>>(users);
}
// GET users/B5608F8E-F449-E211-BB40-1040F3A7A3B1
public UserItem Get(Guid id)
{
var user = _userRepository.FindByID(id);
if (user == null) throw new NotFoundException();
return _mapper.Map<User, UserItem>(user);
}
Next we move onto creating a user. We need to use the HTTP verb POST to create a new resource:
// POST users
public HttpResponseMessage Post(CreateUserItem item)
{
var user = new User()
{
Username = item.Username,
Password = EncryptedString.Create(item.Password, _encryptionService)
};
if (user.IsValid)
{
_userRepository.Add(user);
UserItem createdItem = _mapper.Map<User, UserItem>(user);
return CreatedHttpResponse(createdItem.ID, createdItem);
}
return Request.CreateResponse(HttpStatusCode.BadRequest, user.ValidationErrors);
}
Note that when using POST and creating a new resource the HTTP REST convention states that we must indicate a successful action by returning the HTTP response for a created item.
To update an existing resource the PUT verb would be used:
// PUT users/B5608F8E-F449-E211-BB40-1040F3A7A3B1
public HttpResponseMessage Put(Guid id, UpdateUserItem item)
{
User user = _userRepository.FindByID(id);
if (user == null) throw new NotFoundException();
user.Username = item.Username;
if (user.IsValid)
{
_userRepository.Update(user);
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateResponse(HttpStatusCode.BadRequest, user.ValidationErrors);
}
To delete a resource we will make use of the DELETE verb:
// DELETE users/B5608F8E-F449-E211-BB40-1040F3A7A3B1
public HttpResponseMessage Delete(Guid id)
{
var user = _userRepository.FindByID(id);
if (user == null) throw new NotFoundException();
_userRepository.Remove(user);
return new HttpResponseMessage(HttpStatusCode.NoContent);
}
Note the HTTP status codes used are specific for each type of action.
How do I…?
Use Dependency Injection?
In the example code I'm using the repository pattern, which means that if I want to keep my class dependencies de-coupled or write unit tests for my methods I need a way of injecting the class dependencies into my controller class. Ninject is my IoC container of choice, but it's not so straight forward to use Ninject and the the Web API. Rather than re-explaining take a look at this excellent article from Peter Provost that details how to add Ninject to Web API.
Unit Test a Controller Action?
Writing unit tests is pretty straight forward until you want to verify anything related to the Request/Response objects of the controller.
To test a get method, a unit test such as the following would suffice:
// Tests that a user entity is returned.
[TestMethod]
public void GetUserByIDReturnsUser()
{
// Arrange
var repo = new Mock<IUserRepository>();
repo.Setup(s => s.FindByID(It.IsAny<Guid>())).Returns(GetUserResponse());
var controller = new UsersController(repo.Object);
// Act
UserItem response = controller.Get(Guid.NewGuid());
// Assert
Assert.IsNotNull(response);
}
If we move on to test the create new User action our test code might look as follows:
/// <summary>
/// Posting the user returns HTTP created status.
/// </summary>
[TestMethod]
public void PostUserReturnsHttpCreatedStatus()
{
// Arrange
var repo = new Mock<IUserRepository>();
repo.Setup(s => s.Add(It.IsAny<User>()));
var controller = new UsersController(repo.Object);
// Act
HttpResponseMessage response = controller.Post(new CreateUserItem());
// Assert
Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
}
The problem with this test is that you will encounter a Null Reference exception caused by the call to Request.CreateResponse (see the POST method we discussed earlier to reference code under test). We encounter this because the controller in question has dependencies that need to be mocked in order to assert the state of these instances. Since these will be null as we are creating a new controller instance for our tests we need a way of initialising these dependencies along with the controller. Peter Provost to the rescue again with his brilliant post: Unit Testing ASP.Net Web API.
Here is my tweaked implementation to work with my style of writing unit tests using MSTest.
public class ControllerSetup
{
/// <summary>
/// Registers the context.
/// </summary>
/// <param name="controller">The controller.</param>
/// <param name="controllerName">Name of the controller.</param>
public static void RegisterContext(ApiController controller, string controllerName)
{
var config = new HttpConfiguration();
var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/api/" + controllerName);
var route = config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional });
var routeData = new HttpRouteData(route, new HttpRouteValueDictionary { { "controller", controllerName } });
controller.ControllerContext = new HttpControllerContext(config, routeData, request);
controller.Request = request;
controller.Request.Properties[HttpPropertyKeys.HttpConfigurationKey] = config;
controller.Request.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
}
}
I need to revisit the test in question and add a line of code to call my ControllerSetup method and my test will now run without error:
/// <summary>
/// Posting the user returns HTTP created status.
/// </summary>
[TestMethod]
public void PostUserReturnsHttpCreatedStatus()
{
// Arrange
var repo = new Mock<IUserRepository>();
repo.Setup(s => s.Add(It.IsAny<User>()));
var controller = new UsersController(repo.Object);
// *** Newly added line to register the controller context.
ControllerSetup.RegisterContext(controller, "users");
// ***
// Act
HttpResponseMessage response = controller.Post(new UserItem());
// Assert
Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
}
Catch Errors?
Something I quickly spotted when debugging is that an unhandled exception would reveal the full trace e.g.
"Message": "An error has occurred.", "ExceptionMessage": "The INSERT statement conflicted with the FOREIGN KEY constraint \"FK_Competitions_Users\". The conflict occurred in database \"SmsQuiz\", table \"dbo.Users\", column 'ID'."
… remaining text removed for brevity.
It is dangerous to reveal such detail in a public facing API. To catch exceptions and display limited information I use the following filter attribute:
public class UnhandledExceptionAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
I now decorate the controller class in question:
[UnhandledException]
public class UsersController
{
// class detail here….
}
For further detail see Exception Handling in ASP.Net Web API.
Validate Models?
Validation in the code snippets of this post takes place in the domain model. However, not all projects need a domain model. The following article explains how validation can be achieved using data annotations that will be very familiar to LinqToSql or Entity Framework users. Here is the article: Validating your models in ASP.Net Web API.
Useful Tools
Fiddler
You are going to be doing a lot of HTTP debugging so a good HTTP debugging tool is essential! Fiddler is one of the best tools around - and it's free!
SOAP UI
Allows for API testing via a GUI i.e. no code required! See the official website.
Further Reading
For more tutorials on REST: http://rest.elkstein.org
For more detail on how to use HTTP status codes: https://github.com/basho/webmachine/wiki/Diagram
HTTP spec: http://www.w3.org/Protocols/rfc2616/rfc2616.html
In the next post we look at Testing an ASP.Net Web API.</p></blockquote>