Telerik blogs

If you aren’t using all six of these tips for speeding up your ASP.NET Core application, your application isn’t running as fast as it could. And implementing any of these will speed up your app enough that you users will notice.

The two slowest things you can do in data processing are:

  • Issue a call to another computer, especially if you’re using HTTP, the world’s slowest protocol
  • Performing disk I/O

Those are major problems in a business-oriented ASP.NET Core application which is a) based on receiving HTTP calls over the internet, and b) constantly triggering disk I/O by making database requests.

And then it gets worse: As an ASP.NET Core developer, you have no control over either network latency and very little control over database response times. Which mean that, in an ASP.NET Core application, there are two levers you can pull to get an “Oh, that was faster!” response from your users:

  • Faster responses from the web server when the browser requests a new page
  • Faster load times when that page arrives at the browser

And you want to pull both of those levers because there’s very little that users like better than a fast, responsive UI. So here are six tips (and a bonus) to deliver a faster UI for your users.

Tip 1: Don’t Configure More Than You Need

First things first: In your Program.cs file, you should take advantage of ASP.NET Core’s configuration options to slim down your application and only load only the components that your application needs. That lets you start with a lightweight, nimbler application.

Tip 2: Skip Going to the Database

If you can eliminate trips to the database server, you can cut out one of those “two slowest things in data processing.” You can eliminate trips to your database server by using .NET’s MemoryCache object to cache, in memory, the 20% of the data that handles 80% of the requests to your application. The trick here is that cached data does not reflect changes made back at the database after the data was retrieved into the cache, so you have to deal with your cached data going stale.

The simplest way to implement caching is to put all your data access code in methods in a repository class and then have your application call those repository methods when it needs data. In those repository methods, when you fetch any data, add it to the MemoryCache so that the next request doesn’t have to go to the database server.

You have three choices for handling stale data in the cache:

  1. Choose data for your cache that doesn’t change often or changes outside business hours.
  2. Cache the data with some specified timeout, after which the data will be purged from the cache. Purging the data by the end of business day means that, tomorrow, the first request from the application will pick up any data updated overnight.
  3. Add an optional Boolean parameter to your method that triggers clearing the cache. When the cache is empty, your code makes that trip to the database to get the latest data (and refreshes the cache for subsequent requests). Applications can use that parameter when they absolutely must have the latest data.

Here’s a typical example with a controller calling the repository:

public class HomeController
{
  CustomerRepository custRepo = null;
 
 public Home(IMemoryCache cache)
  {   
     custRepo = new CustomerRepository(cache);
  }
 
  public IActionResult Index(string cId, bool force = false)
  {
     Customer cust = custRepo.GetCustomerById(cid, force)
     return View(cust);
  }
}

public class CustomerRepository
{
  private IMemoryCache cache;
  
  public CustomerRepository(IMemoryCache cache)
  {
       this.cache = cache;
  }
  public async Customer GetCustomerById(string cId, bool force = false)
  {
     if (force)
     {
        cache.Remove(cId);
     }
    Customer cust = await cache.GetOrCreateAsync<Customer>(
          cId,
          async e => {
                                    e.cacheEntry.AbsoluteExpirationRelativeToNow = 
                                                                                       TimeSpan.FromMinutes(30);
                                   return await GetCustomerAsync(cId);
                                }
        );
  return cust;
}

If you’re working in a server farm, you don’t want to cache data on individual servers (that can lead to inconsistency if one server in the farm has cached a version of the item that has different values than an updated version of that item on another server in the farm).

Instead, on a server farm, take advantage of ASP.NET Core support for a variety of distributed caching solutions, including Redis, NCache and SQL Server (which uses in-memory OLTP tables to speed data access).

Tip 3: Don’t Send Any More Than You Need to the Client

Be judicious about passing data to the client: The more data you send to the client when a page is first requested, the slower that page’s initial display is going to be. Specifically, if you’ve retrieved a big list of data that you will be displayed in a grid, don’t display all the rows at once. In fact, don’t download any data until after the page displays—that way the user is kept busy reading the page and doesn’t mind (as much) that the data is still on its way.

When you do deliver the data, download just enough so that your user satisfied with the initial display of your grid. There are two scenarios here:

  • If you’re implementing paging, download just enough data for the first page.
  • If you’re using a grid with “infinite scroll,” download just enough data to fill the webpage (and a little more).

Progress Telerik UI for ASP.NET Core Grid supports this as does the Blazor Grid.

Tip 4: Download What You Need from Closer to the Browser

If the CSS and JavaScript files your page is using are industry standard technology (e.g., Bootstrap for CSS, React or Angular) then get those files from one of the designated Content Delivery Network (CDN) sites. While a CDN is accessed through a single URL, the nodes supporting that URL are scattered across the world. Unless your clients are concentrated near your data center, it’s pretty much certain that your users are closer to a CDN node than they are to your data server.

When you fetch from a CDN, the worst case is that user’s local CDN node doesn’t have a copy of the requested file (unlikely, but possible). If so, the user’s request will populate the CDN node so that any subsequent request from that user (or any other user local to the CDN) will fetch the file from that node. Plus, if the user’s browser has already loaded that industry-standard file from the CDN during a visit to some other site, your user’s browser will just keep using that downloaded version rather than fetch the file again.

You can also consider moving your own custom files out to a CDN (for example, the Azure Content Delivery Network). After all, does your user in Singapore really need to reach all the way across the world to your server in New Jersey just to download your company logo?

Tip 5: Download Smaller and Fewer Files

Even after moving to a CDN for your industry-standard files, your application may still include a lot of your own JavaScript and CSS files.

You should be minifying those files to eliminate what isn’t required on the browser (spaces, carriage returns, indenting, meaningful variable names—everything that makes your file content readable). Slimming down your files speeds downloads and gets your page up faster.

And, because modern browsers support the GZIP protocol, you should also be zipping your CSS and JavaScript files up into smaller, compressed bundles that will download faster, speeding up your initial page display.

There’s another benefit, besides smaller files, here: In HTTP 1.1, your browser is limited to seven simultaneous requests. Bundling up your CSS files means just two requests (one for your CSS bundle and one for your JavaScript bundle) gets your browser all your CSS and JavaScript files.

The browser will unpack those bundles as they are received and that will eat up some of the time you’ve saved in downloading fewer, smaller files—but you’ll probably still come out ahead. Of course, you’ll have to update your <link> and <script> elements to point to your zipped up files so that the browser uses them.

Obviously, whatever the benefits of bundling and minifying are, there’s some extra work here that you’d prefer not to do by hand. Fortunately, ASP.NET Core is compatible with WebOptimizer, which will both minify and bundle your CSS and JavaScript files at runtime.

Tip 6: Call the Server Less

When you do return a page from your ASP.NET Core application, take advantage of the ResponseCache attribute, especially for frequently requested pages—that avoids having the browser make a trip to your web server.

The ResponseCache attribute, applied to an action method, directs clients and proxies to hold on to pages returned by your application. The browser will then satisfy subsequent requests for those pages from those closer, saved results rather than sending another request your more distant server. You not only give your users a faster response, you reduce the demand on your web server so you can handle more requests, faster.

You’ll want to use the ResponseCache’s VaryByQueryKeys method so that the user gets a version of the page that they want. This example tells clients and proxies to cache (for 10 minutes) each version of the page generated by a different value in the querystring’s country parameter:

[ResponseCache(Duration = 600, VaryByQueryKeys = new string[] { "country" } )]
public IActionResult GetPriceList(string country)
{

Bonus Tip: Use Async

This tip doesn’t really belong in this post because it doesn’t make your ASP.NET Core application run faster—it just helps get your application to execute, even as the number of requests pile up.

Some background: Whenever a request for your site hits your web server, the request is assigned a thread to run on. There’s always a limit to the number of threads in your site’s web pool and, if you get enough requests, that pool will be exhausted. When there are no more threads in the pool, the next request to your site will have to wait until a thread becomes available (i.e., when another, running thread finishes and is returned to the pool).

If a request that’s waiting on a thread waits “too long” (and that time is adjustable—talk to your server administrator), the request will be denied, probably with a 503 “Service Unavailable” message.

The issue is that, in a business application, most of the so-called “running” requests that have taken a thread from the pool probably aren’t running. In a business application, most requests just end up calling a database server or a web service, both of which calls execute on a completely different CPU than your web server. As a result, in a business application, the thread on web server that’s assigned to the request spends much of its time idling, waiting for the request to the database or web service to return.

Using async when making calls to a remote computer effectively tosses the idling request’s thread back into the web server’s thread pool where the thread can be used by some other request. And, yes, when the call to the database or web service does return to the web server, the web server will have to find a thread to continue executing the original request. But, with threads regularly being returned to the pool because you’re using async diligently, that won’t be a problem.

Your Turn

Your users just want to get on with their jobs, and anything other than a fast, responsive UI just gets in their way. Speeding up your application will not only make your user happier, it will increase the odds that people will actually use your application.

But what have I missed? What’s your best tip for speeding up your ASP.NET Core application? Or where have I ignored problems with these tips? Post below and help the next reader.


Peter Vogel
About the Author

Peter Vogel

Peter Vogel is both the author of the Coding Azure series and the instructor for Coding Azure in the Classroom. Peter’s company provides full-stack development from UX design through object modeling to database design. Peter holds multiple certifications in Azure administration, architecture, development and security and is a Microsoft Certified Trainer.

Related Posts

Comments

Comments are disabled in preview mode.