How to improve the performance of ASP.Net Core applications

Take advantage of these best practices to reduce resource consumption and improve the performance of your ASP.Net Core applications

How to improve the performance of ASP.Net Core applications
KTSimage / Getty Images
Current Job Listings

ASP.Net Core is a fast, lightweight, open-source, cross-platform rewrite of the ASP.Net framework that runs on Windows, Linux, and even Mac OS X. Speed is one of the key features of ASP.Net Core, meaning that the ASP.Net Core framework is optimized for better performance. Nevertheless, there are some best practices and strategies you can adopt to improve the performance of your applications that leverage the .Net Core runtime.

The essence of improving application performance is ensuring that you build applications that consume the least amount of resources to produce the desired output. This article presents a discussion of the best practices you can adopt to improve the performance of ASP.Net Core applications.

Inline methods

Inlining methods improves performance as it saves the cost of jumps, passing arguments, and saving and restoring registers. Note that a method that contains a throw statement will not be inlined by the JIT (just-in-time) compiler. To solve this, you can take advantage of a static helper method to contain the throw statement.

Minimize virtual calls

Calls to virtual members are slower because virtual calls require indirection. Devirtualization is a feature of JIT compilers and a devirtualized method can become a candidate for inlining. When the type of an object is known, RyuJIT — the JIT for .Net Core — can devirtualize non-sealed method calls. To avoid virtual calls, you can follow these guidelines:

  • Mark classes or methods as sealed by default
  • Mark overridden methods as sealed as well
  • Use concrete types in lieu of interfaces

Pool HTTP connections

Although HttpClient implements the IDisposable interface, it is advisable to reuse HttpClient instances. The reason is that HttpClient leaves the socket open and in the wait state for a short duration of time even after it has been closed. So, if you try to create new HttpClient instances every time a HttpClient is needed, you might run out of available sockets.

With this in mind, HttpClientFactory was introduced in ASP.Net Core 2.1 to pool HTTP connections. By pooling connections, HttpClientFactory optimizes performance, scalability, and reliability. So, avoid creating and destroying HttpClient instances directly, and use the HttpCLientFactory to retrieve HttpClient instances.

Reduce allocations

The introduction of new types like System.ValueTuple and Span<T> provide new ideas for improving performance. Take advantage of System.ValueTuple to reduce allocations when you are trying to return multiple values from a method. And take advantage of Span<T> to avoid array allocations and data copying.

Cache aggressively

Caching is one of the best ways to improve performance. You should cache aggressively and cache any data that is relatively stale. ASP.Net Core provides support for response caching middleware, which you can use to implement response caching. Response caching is an enhanced form of output caching. It refers to the ability to cache web server responses using cache-related headers in the HTTP response objects. You can learn more by reading my article on using response caching middleware in ASP.Net Core

You can also take advantage of a distributed cache like NCache to cache data that is relatively stale. NCache is an extremely fast and scalable in-memory distributed cache that is easy to configure and use. You can read my article on using NCache in ASP.Net Core

Enable compression

Reducing the size of the response improves the performance of the application because less data is transferred between the server and the client. You can take advantage of response compression in ASP.Net Core to shrink the response and reduce bandwidth requirements. Response compression in ASP.Net Core is available as a middleware component. The following code snippet shows how you can add response compression middleware to the request processing pipeline.

    public void ConfigureServices(IServiceCollection services) 
    { 
        services.AddResponseCompression(); 
        services.Configure<GzipCompressionProviderOptions>
        (options => 
        { 
            options.Level = CompressionLevel.Fastest; 
        }); 
    }

You can also take advantage of ASP.Net Core’s built-in support for bundling and minifying client files or assets to improve performance.

For more information, you can read my article on using response compression in ASP.Net Core

Reduce HTTP requests

Every time the web browser opens a connection to the server, there is TCP/IP overhead. A great optimization tip is reducing the number of HTTP requests. You can also take advantage of HTTP/2 — this new version of HTTP introduces some useful optimizations. You should avoid client-side redirects and cache your web pages to reduce the number of connections made to the web server. If you minify the files and then bundle them, the data will load faster and reduce the number of HTTP requests that are needed to render the web page.

Avoid blocking calls

You should avoid blocking calls as they might lead to performance degradation. If you have too many synchronous blocking calls, you might encounter a thread starvation problem. You shouldn’t block asynchronous execution by using Task.Wait or Task.Result. You should make hot code paths asynchronous — e.g., data access and long running operations should be called asynchronously. You can take advantage of performance profilers like PerfView to examine the thread behavior. For more information, you can read my article on understanding the .Net CLR thread pool

Minimize large object allocations

The .Net Core garbage collector is adept at releasing memory occupied by objects in the managed heap. However, freeing up objects can take up CPU time, especially when the objects are large objects (85 Kbytes or more). Garbage collection happens in four generations — generations 0, 1, and 2 and the large object heap (LOH). The garbage collector works much more frequently in the lower generations than in the higher ones.

Garbage collection of large objects is expensive. To minimize large object allocations, you should take advantage of pool buffers using ArrayPool<T> and cache large objects that are used frequently. You should not acquire locks on common code paths and avoid allocating too many short-lived objects on hot code paths.

Use exceptions only when necessary

It should be noted that throwing and catching exceptions is expensive. Hence, exceptions should be used only when they are needed. You should minimize the use of exceptions in your application and avoid using exception handling to control program flow. You can read my article on best practices for exception handling to learn more. 

Optimize data access

Remember that data access is one of the slowest operations in an application. You should call all data access APIs asynchronously. You should minimize roundtrips to the database and retrieve only the data that is needed. Avoid using projection queries on collections.

If you are using Entity Framework Core for data access, follow the recommended guidelines and practices in Entity Framework Core. If you are reading data that won’t be modified by the application, you should take advantage of no-tracking queries in Entity Framework Core. See my article on best practices for Entity Framework to learn how to improve Entity Framework performance. 

While web application performance tuning is complicated, there are some common techniques and recommended practices you can take advantage of to improve the performance of your ASP.Net Core applications. You can also leverage certain tools like MiniProfiler, Glimpse, Application Insights, and Stackify Retrace to measure application performance, detect the performance bottlenecks, and determine the appropriate remedies to fix them.