How to write good, testable ASP.NET Core Web API code quickly

Last Updated: August 15, 2018 | Created: August 7, 2018

This article is about ASP.NET Core 2.1 Web API code and ways to write code that is easy to understand, easy to test and quick to write. You might think that is quite a task, but I introduce a number of libraries that make that feat possible. Also there is an example application that you can clone from GihHub and run locally to see the libraries in action.

If you are new Web API I do start with a general introduction which covers the difference between Web APIs and using ASP.NET Core to directly deliver HTML pages. I then show you how to write simple Web API actions and set the correct HTTP response type back to the caller.

TL;DR; – summary

I describe a way to write Web API controllers that moves the database access code out to a library called GenericServices – this reduces the code needed in a Web API to a few lines. I also have another library called GenericBizRunner that does the same for business logic, which I cover in this article too. I describe a companion library called EfCore.GenericServices.AspNetCore that handles the creation of the correct response with a HTTP status code and json payload.

The overall effect of using the described pattern and libraries is that your Web API actions are small, easy to test and quick to develop.

NOTE: There is an example application on GitHub, which you can clone and run locally – it uses an in-memory database and includes Swagger, so its easy to run.

Introduction to ASP.NET Core Web API

Note: if you already know about ASP.NET Core Web API then you can skip this section.

There are now three main approaches to building a web application using ASP.NET Core version 2.1:

  1. NET Core MVC, which uses controllers, actions and views
  2. NET Core Razor Pages, which has pages with associated code
  3. NET Core Web API, which can be queried by a framework, like React.js, or an external system

The first two have the ASP.NET Core web app deliver HTML pages, with the help of the built-in Razor syntax. In these cases, the ASP.NET Core web app is in charge of what is shown on the screen – see diagram below:

But in the ASP.NET Core Web API case what is shown to the user is controlled by something outside ASP.NET Core. For the human-user example its either framework (like AngularJS or React.js) or mobile application makes requests to the ASP.NET Core application to either get data or execute some business logic – see the diagram below:

There are pros and cons to both approaches, but nowadays more applications are moving over some sort of front-end code running in the browser or mobile which uses Web API to access the backend system. Also, application-to-application traffic can also use the Web API approach. Therefore, being aware on how to write good, testable Web API code quickly is a useful skill.

Going beyond Microsoft’s Web API examples

Microsoft has some good examples of writing Web API applications, including one using EF Core for the database (VS Code version, VS2017/Win version and VS2017/Mac version). These are a great place to start as they clearly show what is going. Here is an example taken from Microsoft’s example (Note: I have left out the constructor of the Web API controller, where the EF Core DbContext for the database is injected and put in the private field called _context).

public IActionResult Update(long id, TodoItem item)
    var todo = _context.TodoItems.Find(id);
    if (todo == null)
        return NotFound();

    todo.IsComplete = item.IsComplete;
    todo.Name = item.Name;

    return NoContent();

The example is very clear about what is happening, as you can see each part of the process, which is why Microsoft wrote it that way. But when the databases accesses and business logic get more complex, then putting the code in the Web API action isn’t such a good idea, for a number of reasons:

  1. The code needed to achieve a particular action (in the example it’s a simple update) can become many lines of code, which means your controller is long and hard to understand.
  2. Your database or business logic is likely to have extra validation/checks which it needs to return as errors, which not covered in the Microsoft example.
  3. There is a danger of duplication of code. For instance, if another action needed to update the TodoItem you would you need to duplicate part of the code.
  4. It wouldn’t be hard to unit test the Web API action in Microsoft’s example, but in more complex applications with say extra data injected via tokens or other parts of the HttpContext then it can get more difficult.

Over the years I have learnt to keep my controller actions as small as possible. This means moving the code out of the action into a separate method. At the same time, I have noticed patterns in database accesses and business logic calls which have culminated in my building libraries to handle the common parts. Here is my version of the TodoItem update using my GenericServices library, which is designed to handle CRUD (Create, Read, Update and Delete) functions to a EF Core database.

public IActionResult Put(ChangeTodoDto dto, 
    [FromServices]ICrudServices service)
    return service.Response();

The differences are:

  • Its short and to the point – my Web API controller will be small and easy to read
  • The library uses a general ‘update TodoItem’ method which is used throughout the application – no possibility of duplication of code.
  • My EfCore.GenericServices (injected via the [FromServices]ICrudServices service property on line 3) provides generic CRUD functions, including the mapping from the input class to the ToDoItem class.
  • The Response method on line 6 returns a response that is either OK (HTTP 200) with a success message, NoFound (HTTP 404) if the item wasn’t found, or if the update logic or database validation finds errors then it returns Invalid Input (HTTP 400) with the error messages (The format of these responses are shown later).

This approach also allows me to unit test the ‘update TodoItem’ on its own, which I prefer. As I said before testing Web API actions can work, but if you have complex HTTP things going on, like cookies or tokens providing data, then testing can get very hard. I find it much simpler and easier to test the service.UpdateAndSave(dto) code on its own in a unit test.

NOTE: If you are using EF Core then see my open-source library EfCore.TestSupport for which has lots of useful methods for testing code that uses EF Core. Also, consider using in-memory databases for unit testing as they are really quick – the article “Using in-memory databases for unit testing EF Core applications” to show you how.

So, by doing all this I have:

  • I wrote good code: is short, obvious and does not duplicate code.
  • I have written testable code: it’s easy to duplicate the same code in your unit tests.
  • I wrote the code quickly: The EfCore.GenericServices library handles the four CRUD patterns, including copying from a ViewModel/Dto class to the EF Core entity class.

It’s my EfCore.GenericServices open-source library that makes the code shorter and quicker to write. This library is a second-generation version of my original  GenericServices library built for EF6.x, and is significantly improvement over the original. My experience with the original GenericServices library was it has saved me months-per-year of development time. I expect my new library to give me a similar, if not better, productively as it now supports DDD-styled entity classes which centralises the create/update code.

I’m not going to describe the EfCore.GenericServices library in detail here, because I have already done that in the article “GenericServices: A library to provide CRUD front-end services from a EF Core database” (see also NuGet, GitHub, and documentation pages). But that article talks about MVC/Razor Pages usage.

In this article I am going to describe how to use my libraries with Web API, and to help I have updated the companion EfCore.GenericServices.AspNetCore library to convert the status output of my libraries library into the correctly formed HTTP responses.

What about business logic?

I have handled CRUD accesses to the database, but complex applications often have complex business logic. As I mentioned before I have another open-source library called EfCore.GenericBizRunner which runs business logic working with EF Core, which the companion EfCore.GenericServices.AspNetCore library also supports. You can find out more about the EfCore.GenericBizRunner library via the article called “A library to run your business logic when using Entity Framework Core”, and the GenericBizRunner’s documentation pages.

How I turn my libraries’ statuses into HTTP responses

My EfCore.GenericServices and EfCore.GenericBizRunner libraies are linked to EF Core, but could be used in any sort of application – web, desktop etc. But I mainly use them with ASP.NET Core, so I have a library called EfCore.GenericServices.AspNetCore which contains code to change the IGenericStatus that both these libraries use into either ASP.NET ModelState (for MVC and Razor Pages) or IActionResult/ IActionResult<T> for Web API.

In this article I want to look at the CreateResponse class that contain extension methods to turn my IGenericStatus to IActionResult/ IActionResult<T>.

NOTE: The EfCore.GenericServices.AspNetCore contains an ExampleWebApi project that contains an ASP.NET Core 2.1 Web API controller as an example of how the CreateResponse extension methods work. It uses an in-memory database and Swagger so that it’s easy to try out the Web API commands – just start the ExampleWebApi project and it should got directly to the swagger pages which lists each Web API command and allows you to test them.

Now I will describe the three main response types and what they look like. I start with the handling errors, as ASP.NET Core 2.1 has already defined that format.

InvalidInput (HTTP 400) – error return

If you create a new ASP.NET Core application the default template turns on 2.1 compatibility, which means that any Web API input is automatically validated, e.g. if a property had the attribute [Range(1,5)] then it would return an error if that property was outside that range. This is a really useful feature, as it saves you from having to add code to validate the input at the start of the Web API action.

The format that ASP.NET Core uses to return the errors is a dictionary of errors returned with Invalid Input (HTTP 400) status code. Here is an example of the format it uses:

    "": [
        "Global error message"
    "MyPropery": [
        "The property is required",
        "Another error on the same property"

Therefore, my Response extension method in the EfCore.GenericServices.AspNetCore library turns any errors found by the EfCore.GenericServices or EfCore.GenericBizRunner libray into the same format. That means there is one common error format the calling code needs to process.

The other responses we need are:

OK (HTTP 200) – successful

This contains a message string, which you can show the user or log. You can set the message yourself, or my libraries will return a message relevant to the. Also, if there is any data to return, then there is a results property. Here is an example with results.

  "message": "Successfully created a new Todo Item",
  "results": {
    "id": 1,
    "name": "Create ASP.NET Core API project",
    "difficulty": 1

Some commands may require a different HTTP status code, for instance when a POST command creates a new entity the norm is to return a Created status (HTTP 201). For that reason, there is an extension method called ResponseWithValidCode, which works the same as the Response method, but allows you to define what status code should be returned on a successful status. Here is an example from my ToDoController where a POST is used to create a new TodoItem:

public ActionResult<TodoItem> Post(CreateTodoDto item,
     [FromServices]IActionService<ICreateTodoBizLogic> service)
    var result = service.RunBizAction<TodoItem>(item);
    return service.Status.ResponseWithValidCode(result, 201);

NOTE: You will see I use the [FromServices] attribute to allow the dependency injection (DI) provider to inject a service as a parameter in the action. I find this more efficient than the normal constructor injection, as it means each action creates just the service it needs.

NoFound (HTTP 404) – when the requested data lookup returned null

In Web APIs if something like a Get(id) is used, and no data is found (i.e. its null) then Microsoft recommends returning NoFound status (HTTP 404). Therefore, if that happens then you get a NoFound with a message, but no results. Here is an example of the json that is returned.

  "message": "The Todo Item was not found."

Some readers have suggested that you should return 204 for a null return, rather than the 404. The default setting is 404, as it follows Microsoft’s examples, but in version 2.0.0 you can set the HTTP status code to be returned if the result is nul by using the ResponseWithValidCode method, e.g. ResponseWithValidCode (result, 200, 204). The second parameter is the status code to return on a successful, non-null result, while the third parameter (which is optional) is the status code to return on a successful, but null result.

Telling GenericServices that null is not an error

To make the null handler to work you need to set a configuration parameter when you set up the GenericServices library. This is because in MVC or Razor Pages applications not finding the data asked for is an error, but in a Web API it isn’t. Here is an example of how you would configure GenericServices for WebAPI in the Configuration method in the Startup class – see line 4 for the important configuration option

    new GenericServicesConfig
        NoErrorOnReadSingleNull = true 

UPDATE: Now with unit test support

I’m working on a client’s project which has lots of Web APIs, and we want to check them, because we found errors could creep up if the dtos weren’t properly setup.  I therefore added a new set of extension methods to the EfCore.GenericService.AspNetCore project (look for version 2.0.0 or higher on NuGet) to help test these.

The new extension methods decode the HTTP IActionResult and ActionResult<T> into a form that allows you to access all the parts of the information you sent from your GenericService/GenericBizRunner methods. This makes unit testing or integration testing much easier to do.

Here is an example integration test taken from the IntegrationTestToDoController, which you can find in the the EfCore.GenericService.AspNetCore GitHub project.

public void TestPutNameOk()
    var options = SqliteInMemory.CreateOptions<ExampleDbContext>();
    using (var context = new ExampleDbContext(options))

        var controller = new ToDoController();
        var utData = context.SetupSingleDtoAndEntities
        var service = new CrudServices(context, utData.ConfigAndMapper);

        var dto = new ChangeNameDto()
            Id = 2,
            Name = "Test",
        var response = controller.PutName(dto, service);

        var rStatus = response.CopyToStatus();
        rStatus.Message.ShouldEqual("Successfully updated the Todo Item");

Looking through the lines of code:

  • Lines 5 to 9: I create a in-memory Sqlite database a seed it with some known data for the test.
  • Line 11: I create an instance of the Web API controller I want to test. Because I inject the services via a parameter on the action method call, then there aren’t any parameters on the Web API controller.
  • Lines 12 to 14: Here I create the service that the Web API action method needs. The code you see is for GenericServices, but you can find an example of setting up GenericBizRunner here.
  • Lines 17 to 22: I call my Web API method, with the required dto class.
  • Line 25: The GetStatusCode extension method gets the HTTP status code, which I then check.
  • Line 26: The CopyToStatus method turns the Web API IActionResult/ActionResult<T> response back into a IStatusGeneric/IStatusGeneric<T> status (it started in from, so its easy to convert it back). This gives you access to any Errors, the Message, and the Result (if it has one).
  • Line 27 and 28 I check the parts of the IStatusGeneric to see what was send

NOTE: I could have added a test of the database to check that the name of the TodoItem with primary key 2 was changed too. It depends on whether you have unit tested the underlying GenericService UpdateAndSave of this entity elsewhere.

The extensions method can be found in UnitTesting.ResponseDecoders class.


I have described the pattern and libraries that allow me to build good, testable Web API code quickly. Previous experience says that this approach makes me a much faster developer – I developed a MVC5 web application around the AdventureWorks 2012 Lite database in 10 days, which is quick (see Real applications that use Web APIs can have hundreds, if not thousands of Web API actions – saving even 25% development time on each Web API action could equate to a saving of many months of your time and the project’s time.

This article and the example Web API in the EfCore.GenericServices.AspNetCore GitHub repo give you a quick way to see the use of EfCore.GenericServices and I threw in an example of calling business logic via my EfCore.GenericBizRunner library (see the Post action which creates a new TodoItem) to build Web APIs.

The examples uses DDD-styled entity classes because I like the way it centralises the create/update code inside the entity class. Using DDD-styled entity classes is optional, as EfCore.GenericServices can work with normal EF Core entity classes as well, but I find it adds an extra level of the robustness of the final solution.

Do clone the the example code and look at the code and run the ExampleWebApi application – maybe using this approach will save you time too.

Happy coding!