DEV Community

Cover image for Asynchronous Calls, Data-Transfer-Objects & Automapper in .NET Core Web API
Patrick God
Patrick God

Posted on • Edited on

Asynchronous Calls, Data-Transfer-Objects & Automapper in .NET Core Web API

This tutorial series is now also available as an online video course. You can watch the first hour on YouTube or get the complete course on Udemy. Or you just keep on reading. Enjoy! :)

Web API Core (continued)

Asynchronous Calls

What are asynchronous calls and why should you bother?

Put simply, with a synchronous call, you would give a task to a thread - like fetching data from a database - and the thread waits for this task to be finished and wouldn’t do anything else until the task is done.

With an asynchronous call, this thread wouldn’t wait, at all. Instead it would be open for new tasks and as soon as the other task - e.g. fetching data from the database - is done, it would grab the data and return it.

In our current application this isn’t really necessary. We have no database calls that take lots of time. Our tasks are done in milliseconds. Additionally, it’s very likely that you have more than one thread available for your application. So even if one thread is waiting for a task, another thread can do another task.

But in large applications with lots of users, it can really happen that all threads are busy. In this case, your app won’t respond to a request anymore, which leads to a terrible user experience and you may even lose users or even paying customers because of this. So, this can lead to serious trouble.

That’s why it doesn’t hurt to know this and also implement it early in this project. Although the methods in the CharacterService won’t do anything asynchronous, they will later on when we fetch data from the database.

Let’s start off with the ICharacterService interface.

Here we just add the Task type to our return types.



public interface ICharacterService
{
    Task<List<Character>> GetAllCharacters();
    Task<Character> GetCharacterById(int id);
    Task<List<Character>> AddCharacter(Character newCharacter);
}


Enter fullscreen mode Exit fullscreen mode

Of course, we also add the using directive for System.Threading.Tasks.

After that we switch to the CharacterService class and also add the Task return type and the using directives. Additionally we add the word async to the methods.



public async Task<List<Character>> AddCharacter(Character newCharacter)
{
    characters.Add(newCharacter);
    return characters;
}

public async Task<List<Character>> GetAllCharacters()
{
    return characters;
}

public async Task<Character> GetCharacterById(int id)
{
    return characters.FirstOrDefault(c => c.Id == id);
}


Enter fullscreen mode Exit fullscreen mode

Now we’ve got asynchronous methods. Don’t mind the warnings for now. The code will still be executed synchronously, but when we add Entity Framework with database queries later on, we’ll have asynchronous calls.

Last but not least is the CharacterController. Again we add the Task type with the corresponding using directive and the async keyword to every method. Additionally we add the keyword await to the actual service call. That’s how we call an asynchronous method.



[HttpGet("GetAll")]
public async Task<IActionResult> Get()
{
    return Ok(await _characterService.GetAllCharacters());
}

[HttpGet("{id}")]
public async Task<IActionResult> GetSingle(int id)
{
    return Ok(await _characterService.GetCharacterById(id));
}

[HttpPost]
public async Task<IActionResult> AddCharacter(Character newCharacter)
{
    return Ok(await _characterService.AddCharacter(newCharacter));
}


Enter fullscreen mode Exit fullscreen mode

That’s it. Making test calls with Postman will return exactly the same results as before. Again, please don’t mind that making all these methods asynchronous is not necessary for such a small project, but you’re here to learn something, I guess, so that’s how you should do it with large applications.

Proper Service Response with Generics

Another practice you might come along in professional projects is to return a wrapper object to the client with every service call. Advantages are that you can add additional information to the returning result, like a success or exception message. The front end is able to react to this additional information and read the actual data with the help of HTTP interceptors, for instance, and we can make use of Generics to use the correct types.

Let’s add that object to our models first. So create a new class and call the file ServiceResponse.

The actual class name would be ServiceResponse<T> where T is the actual type of the data we want to send back. Then we can add some properties.



public class ServiceResponse<T>
{
    public T Data { get; set; }

    public bool Success { get; set; } = true;

    public string Message { get; set; } = null;
}


Enter fullscreen mode Exit fullscreen mode

The Data of type T is, well, the actual data like the RPG characters. With the Success property we can tell the front end if everything went right, and the Message property can be used to send a nice explanatory message, e.g. in case of an error.

Similar to the asynchronous implementations, we don’t really need that now, but you will thank me later when you’re working on bigger projects where these kinds of things come in quite handy. For instance, when you catch exceptions in a try/catch block, a ServiceResponse like this might help you.

Anyways, to make use of our new ServiceResponse, we have to modify the return types of our CharacterService and ICharacterService methods.

Let’s start with the interface. We simply add the ServiceResponse class here.



public interface ICharacterService
{
    Task<ServiceResponse<List<Character>>> GetAllCharacters();
    Task<ServiceResponse<Character>> GetCharacterById(int id);
    Task<ServiceResponse<List<Character>>> AddCharacter(Character newCharacter);
}


Enter fullscreen mode Exit fullscreen mode

After that, we do exactly the same in the CharacterService class.



public async Task<ServiceResponse<List<Character>>> AddCharacter(Character newCharacter)
{
    characters.Add(newCharacter);
    return characters;
}

public async Task<ServiceResponse<List<Character>>> GetAllCharacters()
{
    return characters;
}

public async Task<ServiceResponse<Character>> GetCharacterById(int id)
{
    return characters.FirstOrDefault(c => c.Id == id);
}


Enter fullscreen mode Exit fullscreen mode

Of course, we also have to make changes to the actual implementations of the methods. In general, we create a new ServiceResponse object in every method and set the Data property accordingly.



public async Task<ServiceResponse<List<Character>>> AddCharacter(Character newCharacter)
{
    ServiceResponse<List<Character>> serviceResponse = new ServiceResponse<List<Character>>();

    characters.Add(newCharacter);
    serviceResponse.Data = characters;
    return serviceResponse;
}

public async Task<ServiceResponse<List<Character>>> GetAllCharacters()
{
    ServiceResponse<List<Character>> serviceResponse = new ServiceResponse<List<Character>>();
    serviceResponse.Data = characters;
    return serviceResponse;
}

public async Task<ServiceResponse<Character>> GetCharacterById(int id)
{
    ServiceResponse<Character> serviceResponse = new ServiceResponse<Character>();
    serviceResponse.Data = characters.FirstOrDefault(c => c.Id == id);
    return serviceResponse;
}


Enter fullscreen mode Exit fullscreen mode

The CharacterController stays exactly the same, no changes necessary here.

When we test the calls with Postman now, the results look a bit different. For example, here’s the result of getting all RPG characters.



{
    "data": [
        {
            "id": 0,
            "name": "Frodo",
            "hitPoints": 100,
            "strength": 10,
            "defense": 10,
            "intelligence": 10,
            "class": 1
        },
        {
            "id": 1,
            "name": "Sam",
            "hitPoints": 100,
            "strength": 10,
            "defense": 10,
            "intelligence": 10,
            "class": 1
        }
    ],
    "success": true,
    "message": null
}


Enter fullscreen mode Exit fullscreen mode

You see, our characters are wrapped in our ServiceResponse. The front end could react to the new properties and provide a smooth user experience with toasters or something similar instead of presenting complex exception messages in the console - or worse, a frozen application - in case of an error.

Data-Transfer-Objects (DTOs)

You already read about them, now it’s time to use DTOs. First things first, let’s create a folder called Dtos and then another folder called Character for all the data transfer objects regarding the RPG characters.

As already mentioned, the idea behind DTOs is that you’ve got smaller objects that do not consist of every property of the corresponding model. When we create a database table for our RPG characters later in this tutorial series, we will add properties like the created and modified date or a flag for the soft deletion of that character. We don’t want to send this data to the client.

So, we map certain properties of the model to the DTO. That would be the case of returning data to the client.

But it also works the other way around. We already created a new RPG character by only providing an Id and a name. So, why not use a type that only consists of these two properties or other properties we want to use? This is even more important when you build a front end that should use a specific type for the creation of a new character.

At the moment, we have these to cases: Receiving RPG characters from the server and sending a new character to the server.

So let’s create two classes called GetCharacterDto and AddCharacterDto.

Regarding the GetCharacterDto, it should look exactly the same as the Character model for now. I know, it does not seem to make sense, but I don’t want you to be overwhelmed by implementing anything Entity Framework related at the same time. Adding the DTOs is a good preparation.



using dotnet_rpg.Models;

namespace dotnet_rpg.Dtos.Character
{
    public class GetCharacterDto
    {
        public int Id { get; set; }
        public string Name { get; set; } = "Frodo";
        public int HitPoints { get; set; } = 100;
        public int Strength { get; set; } = 10;
        public int Defense { get; set; } = 10;
        public int Intelligence { get; set; } = 10;
        public RpgClass Class { get; set; } = RpgClass.Knight;
    }
}


Enter fullscreen mode Exit fullscreen mode

The AddCharacterDto looks a bit different. Let’s say we want to send every property but the Id to the service. So we can copy the properties of the model again, but remove the Id.



using dotnet_rpg.Models;

namespace dotnet_rpg.Dtos.Character
{
    public class AddCharacterDto
    {
        public string Name { get; set; } = "Frodo";
        public int HitPoints { get; set; } = 100;
        public int Strength { get; set; } = 10;
        public int Defense { get; set; } = 10;
        public int Intelligence { get; set; } = 10;
        public RpgClass Class { get; set; } = RpgClass.Knight;
    }
}


Enter fullscreen mode Exit fullscreen mode

Now that we have our DTOs ready, we can use them in our controller and service methods.

We start with the ICharacterService interface. Instead of the Character type we now returnGetCharacterDto . The parameter of the AddCharacter() method now is of type AddCharacterDto. We also have to add the corresponding using directive.



public interface ICharacterService
{
    Task<ServiceResponse<List<GetCharacterDto>>> GetAllCharacters();
    Task<ServiceResponse<GetCharacterDto>> GetCharacterById(int id);
    Task<ServiceResponse<List<GetCharacterDto>>> AddCharacter(AddCharacterDto newCharacter);
}


Enter fullscreen mode Exit fullscreen mode

The same changes have to be made in the CharacterService. So we replace the Character type with GetCharacterDto and AddCharacterDto, respectively.



public async Task<ServiceResponse<List<GetCharacterDto>>> AddCharacter(AddCharacterDto newCharacter)
{
    ServiceResponse<List<GetCharacterDto>> serviceResponse = new ServiceResponse<List<GetCharacterDto>>();

    characters.Add(newCharacter);
    serviceResponse.Data = characters;
    return serviceResponse;
}

public async Task<ServiceResponse<List<GetCharacterDto>>> GetAllCharacters()
{
    ServiceResponse<List<GetCharacterDto>> serviceResponse = new ServiceResponse<List<GetCharacterDto>>();
    serviceResponse.Data = characters;
    return serviceResponse;
}

public async Task<ServiceResponse<GetCharacterDto>> GetCharacterById(int id)
{
    ServiceResponse<GetCharacterDto> serviceResponse = new ServiceResponse<GetCharacterDto>();
    serviceResponse.Data = characters.FirstOrDefault(c => c.Id == id);
    return serviceResponse;
}


Enter fullscreen mode Exit fullscreen mode

As you can see, Visual Studio Code is not happy with that change. The types do not really match. That’s where we have to map the DTOs with the model. It’s time for AutoMapper.

But before we jump to AutoMapper we also have to fix one small thing in the CharacterController. We change the parameter type of the AddCharacter() method to AddCharacterDto.



[HttpPost]
public async Task<IActionResult> AddCharacter(AddCharacterDto newCharacter)
{
    return Ok(await _characterService.AddCharacter(newCharacter));
}


Enter fullscreen mode Exit fullscreen mode

With that, we don't have to worry about the controller when we're adding AutoMapper.

AutoMapper

We could map the objects manually by creating a new instance of the necessary class and then setting every property one by one. But the idea behind AutoMapper is that it’s doing exactly that for us on the fly.

But first we have to install AutoMapper, of course.

For .NET Core 3.1 we need the package you can find on https://www.nuget.org/packages/AutoMapper.Extensions.Microsoft.DependencyInjection/. To install the package, we go to the terminal and enter dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection without any specific version to install the latest package.

When the installation is done, you’ll see a new entry in our project file.



<ItemGroup>
  <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="7.0.0" />
</ItemGroup>


Enter fullscreen mode Exit fullscreen mode

After the installation we jump to the Startup.cs first. In the ConfigureServices() method we register AutoMapper with services.AddAutoMapper(typeof(Startup)); and add the using directive using AutoMapper;.



public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddAutoMapper(typeof(Startup));

    services.AddScoped<ICharacterService, CharacterService>();
}


Enter fullscreen mode Exit fullscreen mode

To be able to use the mapping of AutoMapper, we now need an instance of the mapper in our service. So in the CharacterService we add a constructor and inject IMapper. Again, we can initialize a field from that parameter and add an underscore to the private field.



private readonly IMapper _mapper;

public CharacterService(IMapper mapper)
{
    _mapper = mapper;
}


Enter fullscreen mode Exit fullscreen mode

Now we can use the _mapper to set the correct types to the Data property of our ServiceResponse. Let’s start with the GetCharacterById() method. Using the Map() function we first decide in angle brackets which type the value should be mapped to, and the parameter is the actual object that will be mapped.



public async Task<ServiceResponse<GetCharacterDto>> GetCharacterById(int id)
{
    ServiceResponse<GetCharacterDto> serviceResponse = new ServiceResponse<GetCharacterDto>();
    serviceResponse.Data = _mapper.Map<GetCharacterDto>(characters.FirstOrDefault(c => c.Id == id));
    return serviceResponse;
}


Enter fullscreen mode Exit fullscreen mode

The changes for the other two methods are pretty straight forward. In the AddCharacter() method we first map the newCharacter into the Character type, because it will be added to the characters list. So, it’s the other way around.

To map the whole characters list in one line and give it to the serviceResponse, we use the Select() method of LINQ followed by a lambda expression, where we map every Character object of the list into a GetCharacterDto.



public async Task<ServiceResponse<List<GetCharacterDto>>> AddCharacter(AddCharacterDto newCharacter)
{
    ServiceResponse<List<GetCharacterDto>> serviceResponse = new ServiceResponse<List<GetCharacterDto>>();

    characters.Add(_mapper.Map<Character>(newCharacter));
    serviceResponse.Data = (characters.Select(c => _mapper.Map<GetCharacterDto>(c))).ToList();
    return serviceResponse;
}


Enter fullscreen mode Exit fullscreen mode

And finally in the GetAllCharacters() method, we map every single RPG character of the characters list with Select() again, similar to the AddCharacter() method.



public async Task<ServiceResponse<List<GetCharacterDto>>> GetAllCharacters()
{
    ServiceResponse<List<GetCharacterDto>> serviceResponse = new ServiceResponse<List<GetCharacterDto>>();
    serviceResponse.Data = (characters.Select(c => _mapper.Map<GetCharacterDto>(c))).ToList();
    return serviceResponse;
}


Enter fullscreen mode Exit fullscreen mode

Alright, let’s test this now with Postman. Let’s try it with the GetAll route (http://localhost:5000/Character/GetAll).

So, as soon as our Web API is running, run the call in Postman… and we’re getting an error.



AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.

Mapping types:
Character -> GetCharacterDto


Enter fullscreen mode Exit fullscreen mode

AutoMapper does not know how to map Character into a GetCharacterDto. You might ask yourself, “it’s called AutoMapper, so why isn’t this working automatically?”

Well, we have to configure one more thing, but then, I promise, AutoMapper is working fine.

We have to create maps for the mappings, and this is organized in Profiles. You could create a Profile for every mapping, but let’s spare the hassle and just create one class for all profiles for now.

At root level, we can create a new C# class and call it AutoMapperProfile. This class derives from Profile. Make sure to add the AutoMapper using directive.

Regarding the implementation, we need a constructor with no parameter and then create a map for the necessary mapping. And, of course, add further using directives.



using AutoMapper;
using dotnet_rpg.Dtos.Character;
using dotnet_rpg.Models;

namespace dotnet_rpg
{
    public class AutoMapperProfile : Profile
    {
        public AutoMapperProfile()
        {
            CreateMap<Character, GetCharacterDto>();            
        }        
    }
}


Enter fullscreen mode Exit fullscreen mode

Alright, let’s run our test in Postman again.

Now everything works as expected. The same for returning a single RPG character.

But what about adding a new character?



AutoMapper.AutoMapperMappingException: Missing type map configuration or unsupported mapping.

Mapping types:
AddCharacterDto -> Character


Enter fullscreen mode Exit fullscreen mode

We have to create another map. Let’s do that. This time we map the AddCharacterDto to Character.



public AutoMapperProfile()
{
    CreateMap<Character, GetCharacterDto>();            
    CreateMap<AddCharacterDto, Character>();
}


Enter fullscreen mode Exit fullscreen mode

When we test this now, you see that everything works, but the id is 0. That’s because the AddCharacterDto does not provide an Id. That’s exactly what we wanted. Still, let’s fix this by generating a proper Id ourselves.

In the AddCharacter() method in the CharacterService we first create our new Character based on the DTO and then set the correct Id by finding the current max value in the characters list and increasing it by 1.



public async Task<ServiceResponse<List<GetCharacterDto>>> AddCharacter(AddCharacterDto newCharacter)
{
    ServiceResponse<List<GetCharacterDto>> serviceResponse = new ServiceResponse<List<GetCharacterDto>>();
    Character character = _mapper.Map<Character>(newCharacter);
    character.Id = characters.Max(c => c.Id) + 1;
    characters.Add(character);
    serviceResponse.Data = (characters.Select(c => _mapper.Map<GetCharacterDto>(c))).ToList();
    return serviceResponse;
}


Enter fullscreen mode Exit fullscreen mode

Now, without even sending an Id, Percival gets the correct one. Later, when we use Entity Framework Core, it will generate the proper Id by itself.

Add a new character

Alright, we’re done here. I know it was a lot and maybe way too much implementation for such a small project, but in large real-world applications, that’s how it’s done.


That's it for the third part of this tutorial series. Hope it was useful to you. To get notified for the next part, you can simply follow me here on dev.to or subscribe to my newsletter. You'll be the first to know.

See you next time!

Take care.


Next up: Update & remove entities with PUT & DELETE

Image created by cornecoba on freepik.com.


But wait, there’s more!

Top comments (22)

Collapse
 
reddgum profile image
Michael

Hi Patrick,

Under the DTO section, you write, ...First things first, let’s create a folder called Dtos and then another folder called Character for all the data transfer objects regarding the RPG characters."

Here's where we get into trouble. You don't specify where either DTO folder or Character folder should be, or if there's a parent-child relationship to them. It's only hinted at further down when you show: using dotnet_rpg.Dtos.Character

May I suggest being a little more precise with the wording? How about:

First things first, lets create a folder under the main project called 'Dtos' and then under the new 'Dtos' folder, a child folder 'Character'.

Further down, we create the GetCharacterDto and AddCharacterDto classes, and as I've personally discovered, it's really easy to miss the little namespace detail, so those two classes should be INSIDE the Dtos.Character folder, not the Dtos folder.

I know that it's hard writing tutorials that keep it concise and clean - too many words does tend to shut some people down. Thanks for taking the time and making the effort to write what is otherwise a very clean and well crafted tutorial.

Collapse
 
_patrickgod profile image
Patrick God • Edited

Hey Michael,

Thank you very much for your comment. I'll keep that in mind for the upcoming parts.
In general, it doesn't really matter if you follow the folder structure completely.
VS Code will use the proper namespace of your structure, anyways.
But it's a good point!

Take care,
Patrick

Collapse
 
informagico profile image
Alessandro Magoga

Thanks Patric for the great series!

In this chapter I think you're missing to add changes to CharacterController while implementing DTOs.
Parameter type change from Character to AddCharacterDto.

[HttpPost]
public async Task<IActionResult> AddCharacter(AddCharacterDto newCharacter)
{
    return Ok(await _characterService.AddCharacter(newCharacter));
}

or am I missing something?

Collapse
 
_patrickgod profile image
Patrick God

Hey Alessandro,

Thank you very much! You're absolutely right, I totally forgot to add this.
I fixed that now. The change of the types is done right before adding AutoMapper.

Have a great day!

Take care,
Patrick

Collapse
 
ghostbasenji profile image
GhostBasenji

Hi Patrick
I'm learning from your lessons. Repeating all your actions.
Not paying attention to the following warnings, such as
"This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. [dotnet-rpg]"
Postman returns this error:
{
"type": "tools.ietf.org/html/rfc7231#sectio...",
"title": "Unsupported Media Type",
"status": 415,
"traceId": "|738fe68b-408ee4ce1960c8d1."
}
What am I doing wrong?
With respect.
P.S. Just in case, I put a link to the repository. [github.com/GhostBasenji/dotnet-rpg]

Collapse
 
_patrickgod profile image
Patrick God

Hi!
Thank you!
The async warning should disappear later when you're using EF Core and "real" asynchronous methods.

The Postman warning is strange. What's the URI you are using and what method do you want to call? Maybe your body is not configured properly. Did you select text/json?

Hope this helps, and thanks for waiting!

Take care,
Patrick

Collapse
 
jboada profile image
jboada

Hi Patrick,

An amazing article so far.

It called my attention the section called "Proper Service Response with Generics" because it is something that I have done many times in my projects and other developers have laughed at me because they did not see it as useful.

For me, it is one of the thing most useful because there is always an answer, no matter if the app fails, the app records the fail internally and give back a proper answer for the client but never an ugly error that could be handled.

So, thank you for mention it, I see that others have been thru this situation.

Sincerely,
Juan Boada

Collapse
 
henkvandergeld profile image
henkvandergeld

Hi Patrick, we followed your tip to use the ServiceResponse class, but we got feedback that things as OkObjectResult, or NotFoundObjectResult were not available for the consumer of the API's. So instead of returning an ActionResult<>, we returned a ServiceResponse<>. Did we do something wrong? How do you consider this?
Thanks in advance. Regards, Henk

Collapse
 
codexdelta profile image
Aswin Devarajan

Do you have a github repo link for this tutorial?

Collapse
 
_patrickgod profile image
Patrick God

Yep, GitHub repo is now available here: github.com/patrickgod/dotnet-rpg

Collapse
 
codexdelta profile image
Aswin Devarajan

Thank you, loved your tutorial. it got me started with Dotnet core 3 in a single day.

Collapse
 
chinthana84 profile image
Chinthana Gunasekara

nice one. what about the unit of work pattern with repos

Collapse
 
sumitkharche profile image
Sumit Kharche

Great Article Patrick

Collapse
 
_patrickgod profile image
Patrick God

Thank you very much!

Collapse
 
moaatazelkholy profile image
Moataz

Great series, thank you so much.

Collapse
 
_patrickgod profile image
Patrick God

Glad you like it! Thank you very much for your feedback! :)

Collapse
 
abo__bashir profile image
Mohamed Ahmed

Great series, thank you so much, it was useful to me.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.