Adding an API using ASP.NET Core

Cloud

Following my previous post of Adding Validation using Fluent, my focus in this post will be on Adding an API using ASP.net Core following clean architecture.

The source code of this post is on GitHub. Because one single post is too long, I have created the following posts:

Now, in this post “Adding an API Using ASP.NET Core” where we are going to add this API, indeed, using ASP.NET Core. Clients, in our case a Blazor application, will then be able to connect with this API for the back‑end functionality. This API will be the gateway to the core functionality. A very important piece of the puzzle if you ask me.

Let’s get started. Of course, the way we create the API will also require some thought. I’m going to create the API project, which will be pretty standard stuff, but I’m going to give you some pointers here. What we will be spending more time on is the next part. How are we going to create the code in the API? Which code will we really have in our API controllers. I’m going to show you a few options, and we’ll see that MediatR comes into play here. Next, we’ll also take a look at which data we will be returning. Finally, for the consumers of the API to be able to know what functionality the API will be returning, we’ll bring in swagger. Lots of things to do. I think it’s best if we dive straight in.

Creating the API Project

So, as promised, we’ll start with just bringing in the API project, which will be a very straightforward task. We’ve spent a lot of time on creating the business functionality using good architectural principles, and with neatly followed, clean architecture principles to do so. We have looked at the core and the infrastructure. So here’s our diagram again, and we’re now basically going to be adding the next piece of the puzzle, and that is the UI, the user interface. The UI will be exposing the functionality to the end user. Now, the UI here is really a broad term. We could bring in an ASP.NET Core MVC or a Razor Pages application, and then, indeed, this server‑side UI would be directly exposing our functionality.

Clean architecture circle - Adding an API using ASP.NET Core
Clean architecture circle

We are going to follow this part here, though, and we’re going to use instead of a UI an API, which will be exposing the functionality. The UI that we will be adding is a client‑side UI, which happens to be Blazor, but could also be an Angular app or a React app or pretty much everything that can hook into an API.

So, by using an API, were basically opening up our application’s back end for use with many types of clients, and that is a big plus. But from an architectural point of view, the API we’re going to discuss here will take the role of the UI. For that reason, the API will be using our core code, as well as fit into our architectural schema. Also, just to be clear, we’ve already built the core and the infrastructure project using .NET Standard, and now I’m going to bring in an ASP.NET Core project for the API.

API explained

As this post is titled “Adding an API using ASP.net Core”, what do you expect? The API that we will create will indeed be created using ASP.NET Core, and I’ll be using a ASP.NET Core 3.1. I won’t be spending time explaining to you the ins and outs of ASP.NET Core APIs.

So going forward, I’m going to assume you’re all familiar with controllers, the concepts of REST, and the configuration of an ASP.NET Core project. All right, then. In our API application, what we’ll need to do is make changes to the Startup.cs class of our application.

Remember that we have already created along the way in the different projects we’ve built extensions for the ServiceCollection class. Finally, we’ll be able to use them here now. I’m going to add them here since the API will be the executing assembly, and it is the one that we’ll be starting up. Of course, we’ll need to use the code that we’ve written already.

If you follow the architectural schema correctly, we would need from our API just a reference to the core project. Now while that is true, because of the way that the DI container that comes with ASP.NET Core works, we’ll also need to include a reference to the infrastructure projects. If you would use, for example, Autofac as a DI container, which is module‑based, this would not be needed, but we are using the default, so we’ll need to bring in these references as well. No big deal, though. You’ll see that in our code we’ll just use to core code from the API. Let’s go adding an API using ASP.net Core.

Adding the API Project

Now, before I’m going to show you how we write the interaction between the API and the core code, let’s start with adding the API project and configuring it correctly. So, now we have the foundation ready, let’s now expose that functionality over an API.

Now, I’m going to go here to my API solution folder, and I’m going to add another new project, and I’m going to select here an ASP.NET Core application. And I’m going to call that MyTicket.TicketManagement.Api. And I’ll select the Empty template here.

So, our API, as said, is going to expose functionality, so the first thing I’m going to do is add in a couple of references. And, as said in the slides, I will typically need to expose the application since that is the one I’m going to be using directly, but remember that we also have added these service collection extensions in the infrastructure and persistence project, so I’ll need to also add a reference to these.

So, we have now added a reference to the application infrastructure and persistence project. Very soon, we’ll bring in some controllers, so let us already also bring in a Controls folder. This is the empty project, there’s nothing in there yet. What else do I need to do here? Well, I’m going to go to the appsettings, and remember in the persistence, we, of course, used a connection string, and so that connection string isn’t defined in persistence, but we need to also bring that in in the API project.

Settings appsettings.json

So, in the appsettings, I’m going to define this connection string. And while I’m at it, I’m also going to bring in email settings. Remember, we have created the email service, and that email service relied on email settings. Email settings also were brought in through reading configuration, a section called EmailSettings, and that, too, needs to be defined here under the appsettings.

{
  "ConnectionStrings": {
    "MyTicketTicketManagementConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=MyTicketTicketManagementDb2;Trusted_Connection=True;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "EmailSettings": {
    "FromAddress": "info@s981402199.websitehome.co.uk",
    "ApiKey": "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",
    "FromName": "Enrico"
  }
}

So, here I have the appsettings, the FromAddress, an ApiKey, that is an API key from send with, and the FromName. Those settings will be read out automatically. Now I say automatically, that’s not 100% correct. I need to go to my Startup.cs now. I need to configure my application, so my API. In the API I need to do a couple of things.

Configure ConfigureServices

Of course, I’m going to be reading out configurations. Let me bring that in first. I’m going to use, of course, the Microsoft.Extensions.Configuration. So, what do I need to add now in the ConfigureServices? Remember that we have added extension methods on ServiceCollection, like the one you see here. Those I will need to call now from the ConfigureServices for my API Startup.cs. So that’s what I’ll do here.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddApplicationServices();
        services.AddInfrastructureServices(Configuration);
        services.AddPersistenceServices(Configuration);

        services.AddControllers();

        services.AddCors(options =>
        {
            options.AddPolicy("Open", builder => builder.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod());
        });
    }
}

So what did I bring in? AddApplicationServices. That’s the one that lives in application. This one did the registration of AutoMapper and MediatR.

Also, I’m going to call AddInfrastructureServices. That was the one that was going to read out the email settings and also register the email service as the implementation of the IEmailService.

Configure Configure

Then, I’m going to do the same for the persistence services, and that was the one I just showed you that brought in the DbContext support, as well as the repositories. So, all of that is now brought together in the Startup.cs of the API. I’ve also brought in the AddControllers, which is going to bring in support for working with controllers very soon, and I’ve also opened this up for CORS. I’m not really going to specify a strict CORS policy, but I’m going to open up this API so it can be used from client‑side technologies, which we’ll need for our Blazor application. I’m going to also replace the code in the Configure.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseHttpsRedirection();
    app.UseRouting();

    app.UseCors("Open");

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

So what I have done here? I’ve basically added support for routing, I’ve brought in calls, and I’ve also specified that we are going to map to controllers, so routing to controllers is automatically now enabled. So, what do I now need to do still before I actually can start writing code here in the API?

Now, if I come to think about it, so we have added a DbContext, but we haven’t ran any migrations yet. Now, that is because I couldn’t really execute anything.

Run the API project

So I’m now going to do that now that I have my API. So I’m going to set my API as the Startup.cs project, and then I’m going to create a migration. Now, before I can actually execute the migration, I do think I need to add one more package, indeed. I need to bring in support for the EntityFrameworkCore.Tools package because otherwise I won’t be able to create a migration from the Package Manager Console.

Package Manager Console - Execute migration - Adding an API using ASP.NET Core
Package Manager Console – Execute migration

With that added, save this. I can now go to my Package Manager Console and then set the default project to my persistence project. Now I’m going to add a migration for my database, I’m going to type

add-migration InitialMigration

That has now created the migration for my entities, so I have a Categories, Orders, and Events table, which are going to be created taking into account also the things like the maxLength 50 that we have defined.

So, now I can actually create a database. I’m going to go back to my Package Manager Console and run

update‑database

If we now go to View and then SQL Server Object Explorer, because I’ve used a LocalDB, I need to use this one, and now I go to Databases, and I have quite a few test databases here, but you can see here I’ve created MyTicketTicketManagementDb5, and in here I have my Tables and I have my Categories, Events, and Orders.

MyTicket database created - Adding an API using ASP.NET Core
MyTicket database created

This has also used HasData. If I go to the Events table, I also see some default events that have been created.

Data in Events for MyTicket database - Adding an API using ASP.NET Core
Data in Events for MyTicket database

Transitioning from View Services to MediatR

Now, that everything is configured correctly, we need to see what options we have to create the actual controller code, so the code part of the API. There are actually quite a few options here, and what I propose is that I take you through a number of options so we can decide later on which will be the best option.

When we write an API, in most cases, you’ll end up with some code in the controls. If we decide to take the easy route, the default route, let’s say, then we’ll get a controller that is pretty heavy in terms of the amount of code that it’ll contain and the functionality it is covering. It’s pretty easy to write a controller that does a lot and too much. This means, for example, that the controller will in its action methods first check the incoming data to see if it’s valid.

For this, we can rely on model binding built into ASP.NET Core. If we know the model is valid, then we can, from the controller, also execute logic, so perhaps by talking with the code in our Core project. Once we receive a response from the Core, we then again in the controller will create a response type, so an instance of the type that we are going to return.

Heavy controller

For example, this can be a view model instance. And then finally, again, still from the controller, return an API response containing a status code and a response. That is actually a heavy controller, if you ask me. Aren’t we violating the separation of concerns principle here? Should all this code really be living in the controller? I’m not a fan, to say the least.

public class CategoryController : Controller
{
    private readonly ICategoryRepository _categoryRepository;

    public CategoryController(ICategoryRepository categoryRepository)
    {
        _categoryRepository = categoryRepository;
    }

    public async Task<IActionResult> GetCategories()
    {
        var categories = await _categoryRepository.GetAll();
        var categoryViewModels = categories.Select(o => new CategoryViewModel() { 
            Name = c.Name 
        });
        return View(categoryViewModels);
    }
}

Here’s an example of such a heavy controller. You can see the steps that I’ve just explained. We have model binding to validate the incoming data, then we talk with the Core code, then we get back a response from that, we create a view model that we’re going to return, and finally we’ll create an API response. Way too much for a controller.

Lighter controller

A better approach might be that we make the controller lighter already by moving part of what we’ve just seen to a separate class. I typically refer to this as a view service. It’s a separate class which is called from the controller, and this is also part of the API project. And it will take over a lot of the functionality that I’ve just explained.

This view service will then talk with the Core code and return type. The controller’s part in this is now less heavy. It’ll just talk with this view service and, based on its response, return a status code and a response to the caller.

public class CategoryViewService : ICategoryViewService
{
    private readonly ICategoryRepository _categoryRepository;

    public async Task<IEnumerable<CategoryViewModel>> GetCategories()
    {
        var categories = await _categoryRepository.GetAll();
        var categoryViewModels = categories.Select(o => new CategoryViewModel()
        {
            Name = c.Name
        });
        return categoryViewModels;
    }
}

Here you see an example of such a view service. As you can see, a large part of the code as we previously had in the controller has now been moved here. In terms of separation of concerns, we’re now in better shape, since this class’s functionality is a lot smaller. And the same goes for the API controller itself. It now knows about the view service. Perhaps it will get it in through dependency injection.

public class CategoryController : Controller
{
    private readonly ICategoryViewService _categoryViewService;

    public CategoryController(ICategoryViewService categoryViewService)
    {
        _categoryViewService = categoryViewService;
    }

    [HttpGet]
    public async Task<IActionResult> GetCategories()
    {
        var categoryViewModels = await _categoryViewService.GetCategories();
        return Ok(categoryViewModels);
    }
}

Although we’re moving in the right direction, I still feel that we could do better. If you think about it, the controller is now tightly coupled with that view service, and perhaps we can still improve here. And the approach that I recommend here is using, again, MediatR. So again, there will be a very loose coupling between the code in the controller and the code that we need to work with.

Controller with MediatR

In my controller, what I’ll do is, I’ll have a specific type that represents what we want to do. So in other words, we have a specific type for the query or the command we want to execute. It can be passed in as a parameter or it can be created in the controller. Then, and this is where it gets interesting, the controller will send the object basically to our MediatR, and I’m going to use the MediatR library for that again.

The request will then be picked up by the RequestHandler in the Core code where we have defined a RequestHandler already for the types we need. This way, the controllers are becoming very lightweight. The most they’ll do is just raise a message, a request, that is picked up by MediatR. We create this way a very loose coupling between our controller and our Core code, and that’s perfect, if you ask me.

public class CategoryController : ControllerBase
{
    private readonly IMediator _mediator;

    public CategoryController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet("all", Name = "GetAllCategories")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<List<CategoryListVm>>> GetAllCategories()
    {
        var dtos = await _mediator.Send(new GetCategoriesListQuery());
        return Ok(dtos);
    }
}

Here you can see an example of what I’ve just explained, and this example will be for a query that we want to trigger in our Core project. In this action method, we are creating an instance of the query request type. Then, using MediatR, we just fire the message, the request, which will then be picked up by MediatR, handled in the Core code, and then a response will be returned, which is then sent back to the consumer.

Adding Controller Code Using MediatR

Now, I’m going to prepare the API project further. So, we’ll bring in the correct packages since now we’ll need MediatR to be callable from the API project. And then we’ll see how we can build our controllers in a loosely coupled way. Then, let’s focus on really exposing some controllers that we can actually talk with from the outside. So I’m going to use the MediatR‑based approach, so a very loosely coupled approach.

Install-Package MediatR

The GitHub page states that the library is indeed a simple .NET mediator pattern implementation, and that’s exactly what we need.

Now, I’m going to add the the API project the MediatR package that we also already used in the other projects. So, now I can add my controller, and I’m going to start by exposing the category controller, which will be a plane controller, an API controller.

So I’ll right-click on the API project, click on Add > New Item, select API controller. I’m going to call it the CategoryController. This is the complete controller.

CategoryController code

[Route("api/[controller]")]
[ApiController]
public class CategoryController : ControllerBase
{
    private readonly IMediator _mediator;

    public CategoryController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet("all", Name = "GetAllCategories")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<List<CategoryListVm>>> GetAllCategories()
    {
        var dtos = await _mediator.Send(new GetCategoriesListQuery());
        return Ok(dtos);
    }

    [HttpGet("allwithevents", Name = "GetCategoriesWithEvents")]
    [ProducesDefaultResponseType]
    [ProducesResponseType(StatusCodes.Status200OK)]
    public async Task<ActionResult<List<CategoryEventListVm>>> GetCategoriesWithEvents(
        bool includeHistory)
    {
        GetCategoriesListWithEventsQuery getCategoriesListWithEventsQuery = 
                new GetCategoriesListWithEventsQuery() { IncludeHistory = includeHistory };

        var dtos = await _mediator.Send(getCategoriesListWithEventsQuery);
        return Ok(dtos);
    }

    [HttpPost(Name = "AddCategory")]
    public async Task<ActionResult<CreateCategoryCommandResponse>> Create(
        [FromBody] CreateCategoryCommand createCategoryCommand)
    {
        var response = await _mediator.Send(createCategoryCommand);
        return Ok(response);
    }
}

CategoryController explained

As you can see, it, is relying on MediatR, which is going to be brought in through dependency injection again. Now MediatR is already added to the services collection and is, thus, also available through dependency injection already because I’ve used it also already in my other projects. And we’ve called that from the startup. So, we don’t need to do anything extra to get it in here via dependency injection, via constructor injection I should say.

First, I have this GetAllCategories, and now notice how this is going to work. From the outside, we’re requesting the GetAllCategories. I have an HttpGet that is going to be sent to the /all. So this is going to be addressable via api/category/all. Now what am I going to do here? I need to basically let MediatR know that GetAllCategories is going to be requested.

Now, GetAllCategories, we have created for that in our application under Features, Categories, Queries, a GetCategoriesListQuery, this one. I basically need to raise one of these, send one of these requests, that is, and that is going to be sent to MediatR.

var dtos = await _mediator.Send(new GetCategoriesListQuery());

Thus, the only functionality that my controller really needs to do is creating one of those messages. And that is going to send that to MediatR. MediatR has registered in the GetCategoriesListQueryHandler that CategoriesListQuery should be handled by this handler, and it’s going to automatically invoke this Handle method.

So, without creating a tight coupling between my controller and my query handler, I can invoke my business logic in the application in a very loosely coupled way. Then, I am returning that data.

Another example

As another example, which is actually pretty much the same, I’m going to get the CategoriesListWithEventsQuery. That was another one which now just requires a value for the IncludeHistory. And I’m just creating that in the controller. And then I also use MediatR to send that off.

GetCategoriesListWithEventsQuery getCategoriesListWithEventsQuery = 
           new GetCategoriesListWithEventsQuery() { IncludeHistory = includeHistory };

var dtos = await _mediator.Send(getCategoriesListWithEventsQuery);

Again, I have defined that this is accessible via an HttpGet that needs to be sent to allwithevents as the endpoint.

Creating new data

And what about creating new data, so sending data over the API?

[HttpPost(Name = "AddCategory")]
public async Task<ActionResult<CreateCategoryCommandResponse>> Create(
    [FromBody] CreateCategoryCommand createCategoryCommand)
{
    var response = await _mediator.Send(createCategoryCommand);
    return Ok(response);
}

This is the Post method which is going to arrive here in this createAction method. In the body, I’m going to now send in a CreateCategoryCommand. That is just an object, a type that is going to be sent from the client. You’ll see very soon how we are going to create that on the client. Now, I’m basically exposing this command that we created earlier. It’s also going to implement the IRequest in CreateCategoryCommandResponse in this case. It just defines the Name property.

public class GetCategoriesListWithEventsQuery: IRequest<List<CategoryEventListVm>>
{
    public bool IncludeHistory { get; set; }
}

So, in other words, this command is automatically going to be created by the client and sent to my API. In a very similar way, I’m now going to raise that message, that CreateCategoryCommand, and send that off to MediatR. MediatR is then going to call the CreateCategoryCommandHandler, that will be this one, that will trigger validation and that will in the end return a response. Now it is in place if you haven’t made any mistakes along the way, and we should now actually be able to call our categories API.

So, we need to browse to api/category/all. We are getting then our list of categories. And the other calls will work in a similar way.

The categories from the API
The categories from the API

Other controller

Let me quickly show you the other controllers, which are also very lightweight and relying on MediatR to also work in a very loosely coupled way. Because the code is quite long, on GitHub you see the EventsController. As you can see, it’s very similar. It also relies on MediatR to create that loose coupling, and also it’s going to be a very lightweight controller again. You can see the GetAllEvents, which just creates a GetEventsListQuery and sends that with MediatR. GetEventById, as well as the Create, Update, and Delete also are very similar. Here’s the OrderController. There are no actions in there yet, but we’ll come to those very soon. So to conclude, using MediatR, we have now created very lightweight and loosely coupled controllers, just the way I like them.

Deciding Which Objects to Return

One thing I see often in application architectures is that the types that are being returned from the API controllers to the consumers are far from optimal. Too much data to send back. In this case, I don’t mean that a very long list of objects is being returned, which is of course also not great. We’ll come to that later, what I mean here, though, is that I see often that way too many people like to return too many properties to the consumer in the response object, resulting in too much data going over the wire, as well as time lost on both sides on serializing and deserializing that data.

We are in control

Let’s see what I think we should be returning instead. I’m a fan of returning what the consumer of the API needs. If you’re building a general‑purpose API, it is hard to guess what consumers of the API will need, and this doesn’t really apply. But if you are building things end to end, you are basically in control.

ListDetails
Relevant properties
Wrapped in a List<T>
Typically in a view model
All properties
View model
Nested DTO optional

In that case, we can make an easy split, for example, by looking at where the data for our call will be used. If you are returning, say, a list of events, the objects we are returning should be small and contain only the relevant properties. Often, the result will be, indeed, a generic list, and so you’ll often create a view model type that contains just the properties needed in the list. If you think about it, when the user of the client‑side application will be using this data, for most returned instances, they will just see the properties used in the list view.

Why would you then return all properties?

Then, if a caller asks for the detail, we are returning another type containing all relevant properties. The detail will therefore be also getting a custom type, a view model, that we create, and using AutoMapper, we are mapping the original properties to the properties of the view model.

If needed, we can still use a custom nested DTO if we also need to use nested lists, for example. Now, when returning data, so far we have just returned the data and nothing more. But here too, there might be even a better solution, and that is using a response type. So a class, a type, which is specifically used to return to the consumer of the API.

public class BaseResponse
{
    public BaseResponse(string message, bool success)
    {
        Success = success;
        Message = message;
    }

    public bool Success { get; set; }
    public string Message { get; set; }
    public List<string> ValidationErrors { get; set; }
}

It will wrap, of course, the data, but it will also contain a fixed structure to contain errors and other information. If you use this approach, the consumer of the API will know that always a class of this type will be returned and that errors can be found in a specific property. The response data can be found in another property and so on. This can result in a very clean way of working with the API.

Returning View Models and Responses

All of this may sound a bit abstract. Let’s return to Visual Studio and take a look at what our API should be returning. I’m going to show you the different options here using a specific view model for the list and the detail. Next, I’m also going to show you how we can use a response base type and return that from the API. Now let’s focus a bit on what my actions are returning.

EventsController example

So, here we have the EventsController, and it’s pretty much the same for all of them. So here I have the GetAllEvents. And, as you can see, the action is returning a list of EventListVms. The EventListVm was the view model I specifically created in my application to return as the entity that was supposed to be shown in a list.

[HttpGet(Name = "GetAllEvents")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesDefaultResponseType]
public async Task<ActionResult<List<EventListVm>>> GetAllEvents()
{
    var dtos = await _mediator.Send(new GetEventsListQuery());
    return Ok(dtos);
}

[HttpGet("{id}", Name = "GetEventById")]
public async Task<ActionResult<EventDetailVm>> GetEventById(Guid id)
{
    var getEventDetailQuery = new GetEventDetailQuery() { Id = id };
    return Ok(await _mediator.Send(getEventDetailQuery));
}

So, I’m just returning, in this case, the data, the list of view models to be shown in a list. And GetEventById, well it works pretty much the same. It’s, of course, not returning a list, but it’s returning a single EventDetailVm that contains all the properties, again, created inside of my application project.

So, I’m just returning my type here. Notice that I’m never returning the actual database entity. I will always use AutoMapper to map to this VM. When we take a look, also, at the Create, Update, and Delete, notice that I’m using, of course, an HttpPost, Put, and Delete, but I’m also returning something.

Other command examples

In the case of the Create, I’m just returning an Ok, passing in the id, that would be the GUID of the newly‑created event. From a pure REST perspective, this is not really correct. In fact, I should also return the newly‑created entity, but I’ll come to that in just a second. But here I’m doing the simple approach, just returning the newly‑created id of the new event.

The Update is returning NoContent because an update, when it succeeds, shouldn’t be returning anything, but it can also return a 404NotFound, for example. I’ll come to how this 404NotFound can actually be returned. That has to do with malware, but again, I’ll come to that very soon.

And here you also see the Delete, which follows the same pattern here, if everything goes correctly, and I will also be returning a NoContent.

Now let’s take a look at the CategoryController because in the CategoryController, we also see something that we’ve already touched upon earlier. Take a look here at this Create method here, and this Create method is not just returning the ID of the newly‑created category. Instead, I’m returning this CreateCategoryCommandResponse.

Remember the BaseResponse

And if you remember from earlier in the course, this was a type that I’ve created inside of my application project, of course, which inherits from the BaseResponse.

And this type contains the data, so the newly‑created category, as well as properties it inherits from the base response, including validation errors. So if we create a new category and validation errors occur, then we are going to pass those back in the validation errors inside of the response that we’re sending back. This is really a nice approach.

Then, I’m creating those validation errors inside of the handler, and those are going to be returned via the controller to the client that is using my API. In fact, you could always rely on this response‑based approach so that clients will always know what they are getting back. They will always get back an object, and it contains information about whether or not the API call succeeded, as well as possible validation errors or the newly‑created or updated entity.

Adding Support for Returning a CSV File

So, you’d like to export the list of events to an Excel file, right? Yes. That is something we don’t have support for in the API just yet. I think we’ll need to bring that in. Let’s see how we can using all the principles we have learned so far fit this into our architecture.

Exporting to Excel

Now, I’m going to show you how we can implement exporting to Excel from the API, and we’ll need to add this in the different layers. So, we need to now export the events to a CSV file. So on the EventsController, I’ve added a new action. That’s the one you see here called ExportEvents.

[HttpGet("export", Name = "ExportEvents")]
[FileResultContentType("text/csv")]
public async Task<FileResult> ExportEvents()
{
    var fileDto = await _mediator.Send(new GetEventsExportQuery());
    return File(fileDto.Data, fileDto.ContentType, fileDto.EventExportFileName);
}

And it’s going to be returning a file result because in it, we’re not going to be returning JSON. I’m going to be returning a file that can be downloaded to the client. Now the way that we do this is pretty much the same. The functionality to create this file lives in the application project. So I’m going to, again, create a message that will trigger a handler. And that message is now called the GetEventsExportQuery. It is not expecting any parameters. It is going to be returning, however, an EventExportFileVm.

public class EventExportFileVm
{
    public string EventExportFileName { get; set; }
    public string ContentType { get; set; }
    public byte[] Data { get; set; }
}

That is a bit special, as you can see. It contains a FileName, the ContentType that will be CSV, but it also contains the data, and that will be the CSV file Data that is going to be returned to the client. So, that is now wrapped inside of this VM that I’m going to be returning.

GetEventsExportQueryHandler implementation

Of course, that is created by the handler.

public class GetEventsExportQueryHandler 
    : IRequestHandler<GetEventsExportQuery, EventExportFileVm>
{
    private readonly IAsyncRepository<Event> _eventRepository;
    private readonly IMapper _mapper;
    private readonly ICsvExporter _csvExporter;

    public GetEventsExportQueryHandler(IMapper mapper, 
        IAsyncRepository<Event> eventRepository, 
        ICsvExporter csvExporter)
    {
        _mapper = mapper;
        _eventRepository = eventRepository;
        _csvExporter = csvExporter;
    }

    public async Task<EventExportFileVm> Handle(GetEventsExportQuery request, 
        CancellationToken cancellationToken)
    {
        var allEvents = _mapper.Map<List<EventExportDto>>((await _eventRepository.ListAllAsync())
            .OrderBy(x => x.Date));

        var fileData = _csvExporter.ExportEventsToCsv(allEvents);

        var eventExportFileDto = new EventExportFileVm() { 
            ContentType = "text/csv", Data = fileData, 
            EventExportFileName = $"{Guid.NewGuid()}.csv" 
        };

        return eventExportFileDto;
    }
}

GetEventsExportQueryHandler explained

So, I’m going to create, again, a handler, which is going to be handling the GetEventsExportQuery. Now what is special about this one? It’s, again, using our repository and AutoMapper, but it’s now also using an ICsvExporter.

public interface ICsvExporter
{
    byte[] ExportEventsToCsv(List<EventExportDto> eventExportDtos);
}

Because if you think about it, creating that physical file, that byte array, that is something that needs to be done by infrastructure code. So I’m creating, again, an interface, the ICsvExporter, which contains a method, ExportEventsToCsv. My application code isn’t concerned with how we are going to create a CSV file.

Basically what I’m doing in the Handle is I’m going to get all the events, and I’m going to call that _csvExporter interface and call the ExportEventsToCsv onto it. Then I’m creating my VM, which is going to contain the file data in there. I’ll give it a filename, and the ContentType is going to be set to “text/csv“, which is then going to be returned. I still need to create an implementation for that CsvExporter.

CsvExporter implementation

For that, I’ve gone to the infrastructure project. Now, I have added under FileExport a CsvExporter, which is going to be implementing the ICsvExporter. And in here, we simply see some code that is going to export that data to a CSV file. I’m using for that a library.

Install-Package CsvHelper

If we go to the infrastructure project file, we see that we’ve now imported CsvHelper, which is another open‑source library to export to CSV files.

public class CsvExporter : ICsvExporter
{
    public byte[] ExportEventsToCsv(List<EventExportDto> eventExportDtos)
    {
        using var memoryStream = new MemoryStream();
        using (var streamWriter = new StreamWriter(memoryStream))
        {
            using var csvWriter = new CsvWriter(streamWriter);
            csvWriter.WriteRecords(eventExportDtos);
        }

        return memoryStream.ToArray();
    }
}

In the InfrastructureServiceRegistration, I shouldn’t forget that I also need to register the ICsvExporter to be implemented by the CsvExporter and then through dependency inversion and dependency injection, really.

public static class InfrastructureServiceRegistration
{
    public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, 
        IConfiguration configuration)
    {
        services.Configure<EmailSettings>(configuration.GetSection("EmailSettings"));

        services.AddTransient<ICsvExporter, CsvExporter>();
        services.AddTransient<IEmailService, EmailService>();

        return services;
    }
}

The CsvExporter will be used by the query handler. And the code runs when this ExportEventsToCsv is going to be called.

We nicely created in the EventsController, an extra method, an extra action method, I should say. We created an extra query, and then we created an interface, the ICsvExporter, which is then implemented in infrastructure. But existing code hasn’t been influenced. We just have created that new functionality next to what we already had without creating any impact in the already existing code.

Exposing the API Functionality Using Swagger

In the final part of this post, we are going to add a Swagger interface on top of the created API. If you have already built APIs, I’m sure you’re already familiar with Swagger. Swagger allows us to add a description of the API and its methods. Here some other posts about Swagger that you can find very useful:

This way, other teams within MyTicket, in particular the team working on the client projects, will be able to see what the API is capable of. As mentioned, Swagger can describe the API and is a standard specification for the API will also be generated, typically in JSON or in YAML.

What is Swagger?

This is the standard, meaning that other tools can hook into it as well. Quite a few tools exist around Swagger, and we’ll use two in this course. First, we’ll bring in Swashbuckle as a way to generate the Swagger endpoint and the Swagger documentation. In the next post, we’ll also use NSwag, a tool that will look at the API spec and, based on that, generate code.

Because Swagger is indeed the standard, it’s possible to have a lot of tools based upon it. A JSON file is available, exposing all the information about the API.

Next to that, also a human‑readable version of the API documentation is available in a typical page layout you probably have already seen.

MyTicket API with Swagger
MyTicket API with Swagger

Here you can see a screenshot of a Swagger documentation file, which lists out the API endpoints for our API. For each available action which is available, Swagger shows the HTTP verb on that very method. And using this page, also, it’s possible to test the API.

For ASP.NET Core APIs, we can use the open source package Swashbuckle. Indeed, a very interesting name. There are a few packages in this area we need to add to get the full Swagger experience for the API. Swashbuckle.AspNetCore and Swashbuckle.AspNetCore.Swagger bring in the tools and the middleware to expose a JSON endpoint from our API.

SwaggerGen will be used to generate the Swagger document based on the routes we have in our API. And SwaggerUI will be used to generate the UI, so that human‑readable documentation.

Adding Support for Swagger

Let’s return once more for this module to Visual Studio and bring in support for Swagger in the API project. To enable Swagger, I’ve added these two packages

<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="5.6.3" />

Then in the Startup.cs class, I also need to bring in support for Swagger. As you can see, I’ve added this AddSwagger method, which I’m calling from ConfigureServices, which is adding support for Swagger. I’ve specified here that a SwaggerDoc file should also be created. A specific filter is needed to also bring support for the CSV file that we are returning over our API as well. That’s what I’ve done here.

private void AddSwagger(IServiceCollection services)
{
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo
        {
            Version = "v1",
            Title = "MyTicket Ticket Management API",

        });

        c.OperationFilter<FileResultContentTypeOperationFilter>();
    });
}

Then, in the Configure method, I need to bring in some more middleware for Swagger and Swagger UI, and that will also enable the endpoint, swagger.json, which is that machine‑readable JSON file, that OpenAPI specification that can be used by other applications, and we’ll see how we can do that from our Blazor app very soon.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ...

    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "MyTicket Ticket Management API");
    });

    // ...
}

With this in place, let’s run the application. If I now just go to /swagger, you’ll see here that we get that documentation file where I can also test my API methods.

For example. I can click on the Category/all, try it out, execute, and I get back the response containing, in this case, the name of all categories. If I browse to swagger.json, we’ll indeed get the actual JSON file that describes our API. And this we can then use from other tools to generate code, and we’ll see that in the next module. So now we’ve added support for Swagger on our API.

Swagger describes the API in MyTicket
Swagger describes the API in MyTicket