10 Tips To Improve Performance Of ASP.NET Core Application

Performance of a website is very important to make users happy. Here are my top 10 tips to improve the performance of an ASP.NET Core Web application.

Performance is one of the key attributes of a public website to make it successful. If a website takes more than 3 seconds to respond, the user doesn't usually go back. Google and other search engines also prefer to recommend optimized, mobile-friendly, and faster websites.

Let’s take one example. We have multiple search engines like Google, Bing, Baidu, Ask.com, and many more; however, we prefer Google or Bing because these are very fast and can get us results within 3-4 seconds. If these search engines take more than 10 seconds, would you still use them? I don’t think so. Today's users don't want to wait.

Today, we will learn some tips that will help improve the performance of an ASP.NET Core website.

ASP.NET Core is a free, open source, and cross-platform web development framework provided by Microsoft. It is not an upgraded version of ASP.NET but it is a completely rewritten framework from scratch which comes with a single programming model for ASP.NET MVC and ASP.NET Web API.

Here, I am not going to discuss ASP.NET Core and its features. If you're new to ASP.NET Core, you may want to check out ASP.NET Core category or read some popular articles here: 

Let's start with the tips for performance improvement of an ASP.NET Core application.
 

Always use the latest version of ASP.NET Core

The first version of ASP.NET Core was released in 2016 with Visual Studio 2015 and now we have ASP.NET Core 3.0 available. Each new version is getting better and better. You will see a major difference between ASP.NET Core 1.0 and 3.0.

Tip of the day - When building a new ASP.NET Core project, don't forget to select the latest version. Visual Studio 2019 now supports ASP.NET Core 3.0.
 

Avoid synchronous calls at any level

While developing ASP.NET Core applications, try to avoid creating blocking calls. Blocking call means a call that blocks the next execution until it is not completed. Blocking call or Synchronous call can be anything, either you are fetching the data from an API or performing some internal operations. You should always execute the call in an asynchronous manner.

Always use Asynchronous Programming (ASYNC-AWAIT)

Asynchronous programming model was introduced in C# 5.0 and became very popular. ASP.NET Core uses the same Asynchronous programming paradigm to make an application more reliable, faster, and responsive.

You should use the end-to-end asynchronous programming in your code.

Let’s take one example; we have one ASP.NET Core MVC application with database implementation. As we know, it could have many separations and it all depends on the user project architecture but let's take some simple example where we have Controller > Repository Layer etc. Let's see how you will write the sample code at the controller level.
  1. [HttpGet]  
  2. [Route("GetPosts")]  
  3. public async Task<IActionResult> GetPosts()  
  4. {  
  5.     try  
  6.     {  
  7.         var posts = await postRepository.GetPosts();  
  8.         if (posts == null)  
  9.         {  
  10.             return NotFound();  
  11.         }  
  12.   
  13.         return Ok(posts);  
  14.     }  
  15.     catch (Exception)  
  16.     {  
  17.         return BadRequest();  
  18.   
  19.     }  
  20. }  

This is how we will implement the asynchronous programming at the repository level.

  1. public async Task<List<PostViewModel>> GetPosts()  
  2. {  
  3.     if (db != null)  
  4.        {  
  5.          return await (from p in db.Post  
  6.                        from c in db.Category  
  7.                        where p.CategoryId == c.Id  
  8.                        select new PostViewModel  
  9.                        {  
  10.                             PostId = p.PostId,  
  11.                             Title = p.Title,  
  12.                             Description = p.Description,  
  13.                             CategoryId = p.CategoryId,  
  14.                             CategoryName = c.Name,  
  15.                             CreatedDate = p.CreatedDate  
  16.                          }).ToListAsync();  
  17.       }  
  18.     
  19.       return null;  
  20. }  

AVOID TASK.WAIT OR TAST.RESULT WITH ASYNCHRONOUS PROGRAMMING

While working with Asynchronous Programming, I recommend you avoid the use of Task.Wait and Task.Result and try to use await because of the following reasons:

  1. These block the thread until the task is completed and wait for the completion of the task. Wait blocks the thread synchronously until the task completion.
  2. Wait and Task.Result both wrap any type of exception in AggregateException and create complexity while doing exception handling. If you use the await instead of Task.Wait and Task.Result then you don’t have to worry about exception handling.
  3. Sometimes, these both block the current thread and create DEADLOCKS 
  4. Wait and Task.Result only can be used if parallel task execution is going on. We recommend that you don’t use it with Async programming.

Let's understand a good and bad example of Task.Wait as follows.

  1. // Good Performance  
  2. Task task = DoWork();   
  3. await task;   
  4.   
  5. // Bad Performance  
  6. Task task = DoWork();   
  7. task.Wait();   

Let's understand a good and bad example of Task.Result as follows.

  1. // Good Performance on UI  
  2. Task<string> task = GetEmployeeName();   
  3. txtEmployeeName.Text = await task;   
  4.   
  5. // Bad Performance on UI  
  6. Task<string> task = GetEmployeeName();   
  7. txtEmployeeName.Text = task.Result;   

Know more about Best Practices for Asynchronous Programming.

PERFORM I/O OPERATIONS ASYNCHRONOUSLY

While performing I/O operations, you should perform them asynchronously so it won't affect other processes. I/O operations mean, doing some execution with a file like uploading or retrieving files. It could be anything like image uploading, file uploading or anything else. If you try to accomplish it in a synchronous manner then it blocks the main thread and stops the other background execution till the I/O does not complete. So, as per the performance point of view, you should always use the Asynchronous execution for I/O operations.

We have lots of Async methods available for I/O operations like ReadAsync, WriteAsync, FlushAysnc etc. Here is one simple example, of how we can create a copy of one file asynchronously.

  1. public async void CreateCopyOfFile()  
  2. {  
  3.     string dir = @"c:\Mukesh\files\";  
  4.   
  5.     using (StreamReader objStreamReader= File.OpenText(dir + "test.txt"))  
  6.     {  
  7.         using (StreamWriter objStreamWriter= File.CreateText(dir+ "copy_test.txt"))  
  8.         {  
  9.             await CopyFileToTarget(objStreamReader, objStreamWriter);  
  10.         }  
  11.     }  
  12. }  
  13.   
  14. public async Task CopyFileToTarget(StreamReader objStreamReader, StreamWriter objStreamWriter)   
  15. {   
  16.     int num;   
  17.     char[] buffer = new char[0x1000];   
  18.   
  19.     while ((num= await objStreamReader.ReadAsync(buffer, 0, buffer.Length)) != 0)   
  20.     {  
  21.         await objStreamWriter.WriteAsync(buffer, 0, num);  
  22.     }   
  23. }  

ALWAYS USE CACHE

We can increase the performance of the application if we can reduce the number of requests to the server every time. It does not mean that you will not make the call to the server, it only means that you will not make the call to the server EVERYTIME. The first time, you will make the call to the server and get the response and this response is stored somewhere for the future for some time (there will be some expiry) and next time when you will make the call for the same response then first you will check if you have already got data in the first request and stored somewhere and if it is yes then you will use the stored data rather than making the call to the server.

Keeping the data in that location from where we can get it frequently without making the call to the server is a good practice. Here, we can use the Cache. Caching the content helps us to reduce server calls again and again and it helps us to increase the performance of the application. We can do the cache at any point of a location like on client-side caching, server-side caching or client/server-side caching.

We have different kinds of caching available in Asp.Net Core like we can do the caching In-Memory or we can use Response caching or we can use Distributed Caching. More about Caching in ASP.NET Core

  1. public async Task<IActionResult> GetCacheData()  
  2. {  
  3.     var cacheEntry = await  
  4.         _cache.GetOrCreateAsync(CacheKeys.Entry, entry =>  
  5.     {  
  6.         entry.SlidingExpiration = TimeSpan.FromSeconds(120);  
  7.         return Task.FromResult(DateTime.Now);  
  8.     });  
  9.   
  10.     return View("Cache", cacheEntry);  
  11. }  

OPTIMIZE DATA ACCESS

We can also improve the performance of an application by optimizing data access logic and database tables, and queries. As we all know, most of the applications use some kind of data source and every time we fetch data from a database, it affects the application performance. If your database load is slow, the entire application will be slow. Here are some tips:

  1. Reduce the number of HTTP calls, which means you should always try to reduce the number of network round trips.
  2. Try to get all the data in one go. This means rather than making multiple calls to the server, just make one or two calls which can bring all the required data.
  3. Frequently set the cache on data which is not being changed.
  4. Don’t try to get the data in advance which is not required, it will increase the load on the response and your application will load slower.

OPTIMIZE CUSTOM CODE

Besides business logic and data access code, there may be some custom code in the application. Make sure this code is also optimized. Here are some tips:

  1. Custom logging, authentication or some custom handler code which executes on every request should be optimized.
  2. Don’t perform custom long-running custom execution in the business logic layer or middleware, it basically blocks the request to go to the server and application takes much time to get the data. You should have optimized code for this, either at the client side or database side.
  3. Always check that a long-running task should be performed asynchronously without affecting other processes.
  4. You can take real-time client-server communication example as SignalR which works asynchronously.

ENTITY FRAMEWORK CORE QUERY OPTIMIZATION

As we all know, EF Core is an ORM for .NET developers which helps us to play with database object without writing much code as usual. It helps us to use a database using Models. Data access logic code can play a vital role in performances. If your code is not optimized than your application will not perform well.

But if you write your data access logic in optimal ways in EF Core then definitely it improves the performance of the application. Here we have some of the techniques which will increase the performance.

  1. Use No Tracking while getting the data which is the only read-only purpose. It improves performance.
  2. Try to filter the data at the database side, don’t fetch the whole data using query and then filter at your end. You can use some function available in EF Core like Where, Select which help you to filter the data at the database side.
  3. Retrieve the only required number of records which is necessary using the help of Take and Skip. You can take one example of paging where you can implement Take and Skip while clicking on the page number.

    Let's take one example and try to understand how we can optimize the EF Core query using Select and AsNoTracking.
    1. public async Task<PaginatedList<Post>> GetPagedPendingPosts(int pageIndex, int pageSize, List<Category> allowedCategories)  
    2. {  
    3.     var allowedCatIds = allowedCategories.Select(x => x.Id);  
    4.     var query = _context.Post  
    5.         .Include(x => x.Topic.Category)  
    6.         .Include(x => x.User)  
    7.         .Where(x => x.Pending == true && allowedCatIds.Contains(x.Topic.Category.Id))  
    8.         .OrderBy(x => x.DateCreated);  
    9.   
    10.     return await PaginatedList<Post>.CreateAsync(query.AsNoTracking(), pageIndex, pageSize);  
    11. }  

SOME OTHER TIPS

Here we have some other performance improvement stuff which can be implemented in Asp.Net Core application.

  1. Write optimized and test code. You can also use code samples from expert coders including product documentation. The code written by the product team, such as C# team is usually optimized, modern, and follows best practices.
  2. Use optimized and well tested APIs and libraries. For example, in some cases, ADO.NET may be a better choice than Entity Framework or other ORM libraries.
  3. If you have larger file sizes or downloads, you may want to think about using a compression algorithm. There are several built-in compression libraries such as Gzip and Brotli.
    1. public void ConfigureServices(IServiceCollection services)  
    2. {  
    3.     services.AddResponseCompression();  
    4.   
    5.     services.Configure<GzipCompressionProviderOptions>(options =>  
    6.     {  
    7.         options.Level = CompressionLevel.Fastest;  
    8.     });  
    9. }  

BONUS TIPS (CLIENT SIDE)

I would like to share some client-side performance tips. If you are creating a Website using ASP.Net Core MVC, here are some tips:

  • Bundling and Minification
    Use of bundling and minification reduces the number of server trips.  Try to load client-side assets such as styles, JS/CSS in one go. You can first minify your files using minification and then bundle those in one file which will load faster and reduces the number of HTTP requests.
  • Load JavaScript at Last
    You should always try to load your JavaScript files in the end unless they are needed before that. If you do that, your website will render faster and the user won't wait to see the content. 
  • Shrink Images
    Make sure to reduce the size of images by using compression techniques.
  • Use CDN
    If you've only a few styles and JS files, then it's ok to load from your server. For larger static files, try to use a CDN. CDNs are usually available in multiple locations and files are served from a local server. Loading files from a local server can improve website performance.

Conclusion

Today, we learned how to improve the performance of an ASP.NETCore application.

I hope you found this blog useful. If you have any questions or comments, please post them here in the comments section. Thank you!