I’m trying to create a coherent API to access a crazy legacy database, do I decided to use an Asp.Net WebApi project. Since I want to get a somewhat sane result, I’ll test everything.
First things first: create a new WebApi project. It’s a type of project, located under Asp.Net MVC websites.
Don’t forget to update the project’s nuget packages ASAP, since doing it too late will make you tear you hairs out with broken links and missing files.
I will not go over creating API controllers, models etc, because there are a billion websites to help you with that, and it’s pretty straightforward. Instead, I will try to regroup all “best practices” when dealing with a web API in a single place, applied to the problems of dealing with a legacy database. I will also not dwell on the various concepts (data transfer objects, dependency injection, etc), you can read more about them at length with a quick Google search.
Version your API
If your project has any kind of risk associated with it (which means: once in production, will breaking it make you lose money?), you should version your API. So that your website can use the latest and most awesome version, but an executable running somewhere lost and forgotten in the bowels of your middle office doesn’t suddenly (and probably silently) crash once a method returns differently-named fields.
For that, there are several method, each one with various cons and pros. You can read a nice summary (and how to include all main methods at once in your project) here.
I decided to use URL versioning, because it’s the most simple to use and understand, and the most “visible”. Working with various contractors that use various languages and technologies, I always have to choose the less painful option.
To implement API URL-based versioning, the most simple, painless option is to use attribute-based routing. Make sure you have the AttributeRouting.WebApi Nuget package, and just add attributes to your controller’s methods:
|
[GET("api/v1/orders/{id}")] public OrderDto GetOrder(int id) {} |
I advise you against using namespace-based versioning (as seen here), because it makes your life a living nightmare. Everything you will be using (like Swagger) will assume your API is not versioned, so you have to keep default routes, and namespace-based versioning doesn’t allow that (unless you succeed in modifying the linked class, which I didn’t try).
Create a public data layer
Publish Data Transfer Objects (DTO)
One annoying thing when you try to “publish” entities from Entity Framework, is that you get all the foreign keys relationship stuff in your JSON result.
It also strongly links your underlying model with your API, which may be OK, but is not awesome, especially considering that our model is crazy (still working with a legacy DB, remember?).
So, in order to solve these problems (and also because I am not fond of publishing the underlying data model directly), there is a design pattern called Data Transfer Objects.
To implement this pattern, let’s create a Model project, create the Entity Framework entities inside, and publish them through custom and “clean” objects.
Always be remembering that we’re dealing with a legacy DB. Our crazy fields with random casing, cryptic names and insane types (for instance, everything in my main table is a varchar 100, even dates, booleans and numbers) would really love a nice grooming. You’re in luck, because tools exist to make your life easier.
We will use AutoMapper to bind the entities to the custom data objects. This awesome tool will do much of the grunt work for us.
Let’s pretend that in the actual DB, we have entities looking a bit like this:
|
class Order { string emailid { get; set; } string Item_quantity { get; set; } string Shipping_Date { get; set; } } |
Since having all fields as string and with random casing is clearly insane (or lazy, or both, who knows?), we want to map it to an object like this:
|
class OrderDto { string Email { get; set; } int ItemQuantity { get; set; } DateTime? ShippingDate { get; set; } } |
In order to do that, we will use AutoMapper so that we don’t have to manually create object converters. It doesn’t prevent us to write code, though, it just means that all mapping code can be stored in a single place.
|
public class AutoMapperBoostrapper { public static void Map() { // system types conversions Mapper.CreateMap<string, int>().ConvertUsing((v) => { return Convert.ToInt32(v); }); Mapper.CreateMap<string, DateTime?>().ConvertUsing<DateTimeNullConverter>(); // Orders: entities => DTO Mapper.CreateMap<Order, OrderDto>() .ForMember(dest => dest.Email, opt => opt.MapFrom(src => src.emailid)) .ForMember(dest => dest.ItemQuantity, opt => opt.MapFrom(src => src.Item_quantity)) .ForMember(dest => dest.ShippingDate, opt => opt.MapFrom(src => src.Shipping_Date)); } } |
System types are best converted using system conversion, but in my case the datetime have some weird things going on, so I created my own converter.
Note that you can also write a custom converter to convert from and to Order and OrderDto. However, it makes you write mapping method “by hand” in various places like constructors, and I like having all the casting in one place, even though it can get quite long after a while. The mapping can also be much more complicated than the above example, as seen here for instance, and using AutoMapper helps a lot in these cases.
Use dependency injection to publish the DTOs
In order to help in the next step (creating test), we will use dependency injection in our controllers.
In your Model project, create a IEntitiesContainer interface with all the methods to fetch the data you need.
|
public interface IEntitiesContainer : IDisposable { void AddOrder(OrderDto order); void EditOrder(OrderDto order); OrderDto GetOrders(int id); IEnumerable<OrderDto> GetPaginatedOrders(int page, int pageSize); } |
Then, still in your Model project, implement this interface in a class that will wrap around the actual Entity Framework container.
Note that tying the controller to the MyEntities() implementation is not the best approach, but solving it is well documented.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
|
public class MyEntitiesContainer : IEntitiesContainer { private readonly DB.MyEntities db; public MyEntitiesContainer() { this.db = new MyEntities(); } public void AddOrder(OrderDto order) { this.db.Orders.AddObject(Mapper.Map<Order>(order)); this.db.SaveChanges(); } public void Dispose() { this.db.Dispose(); } public void EditOrder(OrderDto order) { Order existing = this.db.Orders.Where(o => o.id == order.id).Single(); existing = Mapper.Map<OrderDto, Order>(order, existing); this.db.ObjectStateManager.ChangeObjectState(existing, System.Data.EntityState.Modified); this.db.SaveChanges(); } public OrderDto GetOrder(int id) { return Mapper.Map<OrderDto>(this.db.Orders.Where(o => o.id == id).FirstOrDefault()); } public IEnumerable<OrderDto> GetPaginatedOrders(int page, int pageSize) { return Mapper.Map<IEnumerable<OrderDto>>( this.db.Orders.OrderByDescending(o => o.id).Paginate(page, pageSize).AsEnumerable()); } } |
Note that we’re not using AutoMapper’s Project().To(), because it doesn’t work great with Entity Framework when using type conversion.
Now use this new layer in your controller:
|
public class OrdersController : ApiController { private readonly IEntitiesContainer db; public OrdersController() { this.db = new MyEntitiesContainer(); } public OrdersController(IEntitiesContainer container) { this.db = container; } // your controller's actions } |
Now we can use the controller with any implementation of IEntitiesContainer in our tests.
Test the controller
Now that we have a foundation for the project, we can finally do some yummy tests. I will use xUnit (because it’s a great tool) and FluentAssertions (because it makes tests easier to read and write).
I will not go over testing the model, since it’s pretty well documented. However, testing the WebApi controller requires a bit more work.
In order to not hit the actual DB, we’ll use the dependency injection we’ve set up. Create an implementation of the IEntitiesContainer in your test project. I suggest using an awesome method that I found on a blog: create a mostly-empty implementation, for which you will provide the implementation on a case-by-case basis.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
internal class FakeEntitiesContainer : IEntitiesContainer { public Action<Commandes> AddCommande { get; set; } public Func<int, Commandes> GetCommande { get; set; } void IEntitiesContainer.AddCommande(Commandes commandes) { if (this.AddCommande != null) { this.AddCommande(commandes); } else { throw new NotImplementedException(); } } Commandes IEntitiesContainer.GetCommande(int id) { if (this.GetCommande != null) { return this.GetCommande(id); } else { throw new NotImplementedException(); } } } |
Then your controller test can use this fake like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
public class OrdersControllerTests : IDisposable { private readonly OrdersController controller; private readonly FakeEntitiesContainer fakeContainer; public CommandesControllerTests() { this.fakeContainer = new FakeEntitiesContainer(); this.controller = new OrdersController(this.fakeContainer); } [Fact] public void Controller_gets_single_item() { // arrange OrderDto item = new OrderDto() { id = 123 }; this.fakeContainer.GetOrder = (id) => { if (id == item.id) { return item; } return null; }; TestsBoostrappers.SetupControllerForTests(this.controller, "orders", HttpMethod.Get); // act OrderDto result = this.controller.GetOrders(item.id); // assert result.ShouldBeEquivalentTo(item, "because the method should fetch the correct item"); // cleanup this.fakeContainer.GetOrder = null; } } |
The TestsBoostrappers.SetupControllerForTests() method configures the routes of the controller. If we don’t do that, the controller won’t know what to do.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
internal class TestsBoostrappers { public static HttpRequestMessage NewHttpMessage(HttpMethod method, string controllerName) { return new HttpRequestMessage(method, string.Format("http://localhost/api/v1/{0}", controllerName)); } public static void SetupControllerForTests(ApiController controller, string controllerName, HttpMethod method) { var request = NewHttpMessage(method, controllerName); var config = new HttpConfiguration(); var route = WebApiConfig.Register(config).First(); 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[HttpPropertyKeys.HttpRouteDataKey] = routeData; } } |
One last, but very important, point of interest on your tests. In order to follow the REST principles, after certain actions, your API should return some specific data. For instance, after an item creation, it should return the URL to this new item. Your POST method may look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public HttpResponseMessage PostOrders(OrderDto order) { if (this.ModelState.IsValid) { this.db.AddOrder(order); HttpResponseMessage response = this.Request.CreateResponse(HttpStatusCode.Created, order); string link = this.Url.Link( "DefaultApi", new { id = order.id }); var uri = new Uri(link); response.Headers.Location = uri; return response; } else { return this.Request.CreateResponse(HttpStatusCode.BadRequest); } } |
This simple this.Url creates an URL based on your routes and such, but if you’re using WebApi 1 like me (because you’re stuck on .Net 4.0 in Visual Studio 2010), all documented solutions won’t work, and will make you want to flip your desktop. In order to solve that, I simply used another dependency injection to wrap the UrlHelper class, and provide my own implementations in my tests.
Document your API
Documenting your API is pretty easy using Swagger. An Nuget package wrapping the implementation for .Net is available as Swashbuckle. Add it to your API project, and then you can access the documentation through /swagger/.