In this post, we are going to convert the synchronous code to asynchronous inside ASP.NET Core. First, we are going to learn a bit about asynchronous programming and why should we write async code. Then we are going to use our project from the .NET Core series and rewrite it in an async manner.

In Part 4 of the series, we have created Generic Repository Pattern and in Part 5 and Part 6 the Controller with Actions that consumes that repository. We recommend reading those articles if you are not familiar with Generic Repository Pattern or if you find any of the concepts in this article hard to understand. Those articles will help you follow along with this article much easier because we won’t dive into its business logic.

We are going to modify the code, step by step, to show you how easy is to convert the synchronous code to asynchronous one. Hopefully, this will help you understand how asynchronous code works and how to write it from scratch in your applications.

To download the source code for our starting project, you can visit our GitHub repo for the starting project.

For the finished project refer to our GitHub repo for the finished project.

We are going to cover the following sections in this article:

What is Asynchronous Programming

Async programming is a parallel programming technique, which allows the working process to run separately from the main application thread. As soon as the work completes, it informs the main thread about the result, whether it was successful or not.

By using async programming, we can avoid performance bottlenecks and enhance the responsiveness of our application.

How so?

Because we are not sending requests to the main thread and blocking it while waiting for the responses anymore (as long as it takes). Now, when we send a request to the main thread, it delegates a job to a background thread, thus freeing itself for another request. Eventually, a background thread finishes its job and returns it to the main thread. Then the main thread returns the result to the requester.

It is very important to understand that if we send a request to an endpoint and it takes the application three or more seconds to process that request, we probably won’t be able to execute this request any faster in async mode. It is going to take the same amount of time as the sync request.

The only advantage is that in the async mode the main thread won’t be blocked three or more seconds, and thus it will be able to process other requests.

Here is the visual representation of the asynchronous workflow:

Asynchronous Generic Repository

Now that we cleared that out we can learn how to implement the asynchronous code in .NET Core.

Async, Await Keywords and Return Types

The async and await keywords play the crucial part in the asynchronous programming. By using those keywords, we can easily write asynchronous methods without too much effort.

For example, if we want to create a method in an asynchronous manner, we need to add the async keyword next to the method’s return type:

By using the async keyword, we are enabling the await keyword and modifying the way of how method results are handled (from synchronous to asynchronous):

In asynchronous programming we have three return types:

  • Task<TResult>, for an async method that returns a value
  • Task, to use it for an async method that does not return a value
  • void, which we can use for an event handler

What does this mean?

Well, we can look at this through the synchronous programming glasses. If our sync method returns  int then in the async mode, it should return Task<int>, or if sync method returns IEnumerable<string> then the async method should return Task<IEnumerable<string>>.

But if our sync method returns no value (has a void for the return type), then our async method should usually returnTask. This means that we can use the await keyword inside that method but without the return keyword.

You may wonder, why not returning Task all the time? Well, we should use void only for the asynchronous event handlers which require a void return type. Other than that, we should always return a Task.

From C# 7.0 onward, we can specify any other return type, if that type includes GetAwaiter method.

Now, when we have all the information, let’s do some refactoring in our completely synchronous code.

Modifying the IRepositoryBase Interface and the RepositoryBase Class

Our complete repository is interface based, so we need to start with the interface changes. In the Contracts project open the IRepositoryBase.cs file. Let’s modify Find and Save method signatures:

In the code above we have changed the return types of FindAll and FindByCondition method signatures. We did that in order to be able to assign the async methods in the concrete repository classes.

We haven’t changed the Create, Update, and Delete method signatures, because they don’t modify any data, they just track changes to an entity and wait for the EF Core’s SaveChanges method to execute. But we do modify the Save method signature because it makes changes to our database.

After the interface modification, let’s modify the RepositoryBase class:


We need to modify all these methods in order to implement our interface modifications (names and return types). But now,  the Save method has additional keywords async and await which make it asynchronous. Again, the Create, Update, and Delete methods are the same as before. They don’t modify any data but wait for the SaveChangesAsync method to execute.

Modifying the IOwnerRepository Interface and the OwnerRepository Class

We have modified our async RepositoryBase class, so let’s continue on the other parts of our repository. In the Contracts project, there is also the IOwnerRepository interface with all the synchronous method signatures which we should change too.

So let’s do that:

This interface modification is a bit different from the IRepositoryBase interface modification. The Create, Update and Delete method signatures are now asynchronous. That’s because in these methods we are going to execute the SaveAsync() method. Because the SaveAsync() method is awaitable we may use the await keyword thus our methods need to have the async keyword and Task as a return type.

Using the await keyword is not mandatory though. Of course, if we don’t use it, our SaveAsync() method will execute synchronously, and that is not our goal here.

So, in accordance with the interface changes, let’s modify our OwnerRepository.cs class, which we may find in the Repository project:

Controller Modification

Finally, we need to modify all of our actions in the OwnerController to work asynchronously.

So, let’s first start with the GetAllOwners method:

We haven’t changed much in this action. We’ve just changed the return type and added the async keyword to the method signature. In the method body, we can now await the GetAllOwnersAsync() method. And that is pretty much what we should do in all the actions in our controller.

So let’s modify all the other actions.






Excellent. Now we are talking async 😀

We can make our actions even more maintainable and readable by implementing Global Error Handling to remove try-catch blocks and Action Filters as well to remove validation code repetitions.


There you go. We have seen how easy is to convert the synchronous repository to asynchronous and how easy is to write async code overall. With a couple of changes, we gave more breathing space to our API and created a more responsive application.

It is a good practice to write async methods (when we have an opportunity) which handles the I/O actions because it is easy to write those methods and the benefits are indisputable.

Thank you for reading the article and we hope you found something useful in it.

If you have enjoyed reading this article and if you would like to receive the notifications about the freshly published .NET Core content we encourage you to subscribe to our blog.