ASP.NET is a popular web development framework that is widely used for building dynamic and robust web applications. However, as the complexity of an application grows, so does the demand for system resources, which can lead to performance issues. Slow page load times, high CPU and memory usage, and poor scalability are common problems faced by developers when working with ASP.NET. In this article, we'll discuss some of the most effective ways for Boosting ASP.NET Performance: Essential Techniques for Success.
Why Optimize ASP.NET Performance?
We need to optimize the performance of ASP.NET to improve the user experience, reduce resource consumption and increase the scalability of the web applications. Below are some of the benefits of optimizing ASP.NET performance:
Faster loading time and smoother interaction can reduce bounce rates, increase conversions, and improve customer satisfaction.
Reducing the amount of data transferred over the network can save bandwidth costs and improve security.
Using less CPU, memory, and disk resources can lower operational costs and enable more efficient use of hardware.
Scaling up to handle more concurrent requests and larger workloads can improve the availability and reliability of our web applications.
Boosting ASP.NET Performance: Essential Techniques for Success
The article "Boosting ASP.NET Performance: Essential Techniques for Success" shows different techniques which developers can use to optimize the performance of ASP.NET applications. Below are some of the most effective techniques for optimizing ASP.NET performance:
Technique 1: Use caching:
Using caching can reduce the work required to generate content and improve the response time and scalability of our web applications. There are several caching mechanisms in ASP.NET, such as in-memory caching, output caching, and data caching.
You can use caching by storing frequently accessed data or content in memory and serving it from the cache instead of fetching it from the source. This can reduce the work required to generate content and improve the response time and scalability of your web applications.
Use the @OutputCache directive at the top of your ASP.NET page to cache the entire page output for a specified duration.
<%@ OutputCache Duration="60" VaryByParam="none" %>
Use the ResponseCache attribute on your controller actions or Razor Pages handlers to set the HTTP cache headers for caching server responses.
[ResponseCache(Duration = 60)]
public IActionResult Index()
{
return View();
}
Use the Cache Tag Helper or the Distributed Cache Tag Helper to cache portions of your MVC view or Razor Page.
<cache vary-by="@DateTime.Now.ToString("MMddyyyy")" expires-after="@TimeSpan.FromMinutes(1)">
Current Time Inside Cache Tag Helper: @DateTime.Now
</cache>
Use the Response Caching middleware or the Output Caching middleware to cache server responses based on HTTP cache headers or server configuration. For example, you can add the following code in your Program.cs file to enable response caching:
services.AddResponseCaching();
app.UseResponseCaching();
Use the IMemoryCache interface or the IDistributedCache interface to cache any object in memory on a single server or across multiple servers. For example, you can inject an IMemoryCache instance into your controller and use it to store and retrieve data:
public class HomeController : Controller
{
private readonly IMemoryCache _memoryCache;
public HomeController(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
public IActionResult Index()
{
DateTime currentTime;
// Look for cache key.
if (!_memoryCache.TryGetValue(CacheKeys.Entry, out currentTime))
{
// Key not in cache, so get data.
currentTime = DateTime.Now;
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromSeconds(3));
// Save data in cache.
_memoryCache.Set(CacheKeys.Entry, currentTime, cacheEntryOptions);
}
return View(currentTime);
}
}
Technique 2: Optimize images:
You can use various techniques and tools to reduce the size and quality of the images without compromising the user experience. This can improve the loading time and bandwidth consumption of your web applications.
Here are different ways to optimize images:
Enable compression of HTTP content using the gzip or Brotli algorithms to reduce the amount of data transferred over the network. For example, you can add the following code to your Program.cs file to enable response compression:
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
});
app.UseResponseCompression();
Use the Image Optimizer extension for Visual Studio or Azure.ImageOptimizer NuGet package to automatically optimize images using industry-standard algorithms. For example, you can install Azure.ImageOptimizer package into your ASP.NET application and it will optimize images once the app is deployed to Azure App Services with zero code changes.
Use the ImageProcessor library or the Imager library to programmatically resize, crop, rotate, or compress images in your ASP.NET application. For example, you can use the following code to resize an image using Imager:
Image img = Imager.Resize(sourceImage, newWidth, maxHeight);
Technique 3: Disable views state:
ASP.NET uses the view state method to keep page and control values. However, this can add a lot of weight to your pages and increase the size of the HTTP requests and responses.
You can disable view state to optimize ASP.NET performance by using various techniques and tools to prevent the server from storing and sending the state of the controls on the page. This can reduce the amount of data transferred over the network and improve the loading time and scalability of your web applications.
Here are ways to disable the view state:
1. Use the EnableViewState property on your page, control, or master page to disable the view state for the entire page or specific control. For example, you can add the following code in your Page directive to disable the view state for the whole page:
<%@ Page Language="C#" EnableViewState="false" %>
2. Use the ViewStateMode property on your page, control, or master page to disable the view state for the entire page or specific control. For example, you can add the following code in your Page directive to disable the view state for the whole page:
<%@ Page Language="C#" ViewStateMode="Disabled" %>
3. Use the EnableEventValidation property on your page, control, or master page to disable event validation for the entire page or specific control. Event validation is a feature that verifies that events originate from valid sources and prevents malicious users from injecting events. However, it also adds some data to the view state. For example, you can add the following code in your Page directive to disable event validation for the whole page:
<%@ Page Language="C#" EnableEventValidation="false" %>
Technique 4: Use asynchronous calls:
Blocking calls on synchronous methods can result in thread pool starvation and slow responses. You can use asynchronous methods to perform I/O-bound operations without blocking the threads. This can improve the scalability and responsiveness of your application.
Use the async and await keywords to mark methods as task-based asynchronous methods and to asynchronously wait for the result of a task. For example, you can use the following code to make an asynchronous web service call using HttpClient:
public async Task<ActionResult> Index()
{
var client = new HttpClient();
var response = await client.GetAsync("https://example.com/api/values");
var content = await response.Content.ReadAsStringAsync();
return View(content);
}
Use the Task class and related types in the System.Threading.Tasks namespace to create and manipulate tasks. For example, you can use the following code to create a task that performs a long-running calculation:
public async Task<ActionResult> Index()
{
var task = Task.Run(() => Calculate(1000000));
var result = await task;
return View(result);
}
private int Calculate(int n)
{
// Some long-running calculationreturn n * n;
}
Use the Task.WhenAll or Task.WhenAny methods to perform multiple operations in parallel or to wait for any one of them to complete. For example, you can use the following code to make multiple web service calls in parallel and wait for all of them to complete:
public async Task<ActionResult> Index()
{
var client = new HttpClient();
var tasks = new List<Task<string>>();
tasks.Add(client.GetStringAsync("https://example.com/api/values/1"));
tasks.Add(client.GetStringAsync("https://example.com/api/values/2"));
tasks.Add(client.GetStringAsync("https://example.com/api/values/3"));
var results = await Task.WhenAll(tasks);
return View(results);
}
Technique 5: Use response caching middleware:
The response caching middleware can store the responses of HTTP requests in memory or on disk and serve them from the cache for subsequent requests. This can reduce the amount of work required to generate content and improve the response time and scalability of your web applications.
Add the Response Caching Middleware services to the service collection and configure the app to use the middleware with the UseResponseCaching extension method. For example, you can add the following code in your Program.cs file to enable response caching:
services.AddResponseCaching();
app.UseResponseCaching();
Use the ResponseCache attribute on your controller actions or Razor Pages handlers to set the HTTP cache headers for caching server responses. For example, you can use the following code to cache a controller action for 60 seconds:
[ResponseCache(Duration = 60)]
public IActionResult Index()
{
return View();
}
Use the VaryByQueryKeys property on the ResponseCache attribute to cache different versions of a response based on query string parameters. For example, you can use the following code to cache a controller action for 60 seconds for each value of the id parameter:
[ResponseCache(Duration = 60, VaryByQueryKeys = new[] { "id" })]
public IActionResult Index(int id)
{
return View(id);
}
Technique 6: Minimize large object allocations:
Large objects are allocated on the large object heap (LOH), which is not compacted by the garbage collector. This can lead to memory fragmentation and performance degradation. You can avoid allocating large objects by using arrays or collections of smaller sizes, pooling or reusing large objects or using memory-mapped files.
Use the ArrayPool<T> class to pool and reuse large arrays instead of allocating and de-allocating them frequently. For example, you can use the following code to rent and return a large array from the shared pool:
// Rent a large array from the shared pool
var buffer = ArrayPool<byte>.Shared.Rent(100000);
// Use the array for some operation
DoSomething(buffer);
// Return the array to the pool when done
ArrayPool<byte>.Shared.Return(buffer);
Use the Memory<T> or Span<T> structs to represent contiguous regions of memory without allocating any heap objects. For example, you can use the following code to slice a large array into smaller spans without copying any data:
// Create a large array
var array = new byte[100000];
// Slice the array into smaller spans
var span1 = array.AsSpan(0, 10000);
var span2 = array.AsSpan(10000, 20000);
var span3 = array.AsSpan(20000, 30000);
// Use the spans for some operation
DoSomething(span1, span2, span3);
Use the MemoryMarshal class to perform low-level memory operations on Memory<T> or Span<T> instances without allocating any heap objects. For example, you can use the following code to cast a span of bytes into a span of ints without copying any data:
// Create a span of bytes
var bytes = new byte[16];
// Cast the span of bytes into a span of ints
var ints = MemoryMarshal.Cast<byte, int>(bytes);
// Use the span of ints for some operation
DoSomething(ints);
Technique 7: Use JSON serialization:
JSON is a lightweight and widely used data format for web applications. You can use JSON serialization to convert your objects to and from JSON strings. ASP.NET Core provides built-in support for JSON serialization using System.Text.Json, which is faster and more efficient than other libraries.
JSON is a common format for data exchange in web applications and APIs. Here are some code examples of how you can use JSON serialization:
Use the System.Text.Json namespace to serialize and deserialize .NET objects to and from JSON strings using the JsonSerializer class1. For example, you can use the following code to serialize a WeatherForecast object to a JSON string:
var weatherForecast = new WeatherForecast
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
Summary = "Hot"
};
string jsonString = JsonSerializer.Serialize(weatherForecast);
Console.WriteLine(jsonString);
// output:// {"Date":"2019-08-01T00:00:00-07:00","TemperatureCelsius":25,"Summary":"Hot"}
Use the JsonSerializerOptions class to customize the serialization and deserialization behavior, such as naming policies, formatting options, converters, etc. For example, you can use the following code to serialize a WeatherForecast object to a JSON string with camel case property names and indented formatting:
var weatherForecast = new WeatherForecast
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
Summary = "Hot"
};
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
string jsonString = JsonSerializer.Serialize(weatherForecast, options);
Console.WriteLine(jsonString);
// output:// {// "date": "2019-08-01T00:00:00-07:00",// "temperatureCelsius": 25,// "summary": "Hot"// }
Use the HttpClient and HttpContent extension methods in the System.Net.Http.Json namespace to perform HTTP requests and responses with JSON content. For example, you can use the following code to post a WeatherForecast object as JSON content to a web API and get back another WeatherForecast object as JSON content:
var weatherForecast = new WeatherForecast
{
Date = DateTime.Parse("2019-08-01"),
TemperatureCelsius = 25,
Summary = "Hot"
};
var client = new HttpClient();
var response = await client.PostAsJsonAsync("https://example.com/api/weather", weatherForecast);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadFromJsonAsync<WeatherForecast>();
Console.WriteLine(result.Summary);
// output:// Cool
Technique 8: Avoid session and application variables:
Session and application variables are used to store and retrieve user-specific or application-wide data without consuming server memory or causing concurrency issues, but they can also consume a lot of memory and affect scalability. You can use alternative methods such as cookies, query strings, or database storage.
Session and application variables are convenient ways to store data across requests, but they have some drawbacks, such as:
Session and application variables consume server memory, which can affect the scalability and performance of your web applications.
Session and application variables are not thread-safe by default, which can cause race conditions and data corruption if multiple threads access or modify them simultaneously.
Session and application variables are not persistent, which means they can be lost if the server restarts or the application pool recycles.
Session and application variables are not distributed, which means they cannot be shared across multiple servers or web farms without using additional mechanisms such as SQL Server session state or out-of-process session state.
Use cookies to store user-specific data on the client side, which can reduce server memory usage and improve the performance of your web applications. For example, you can use the following code to create a cookie that stores the user name:
var cookie = new HttpCookie("UserName", "Alice");
Response.Cookies.Add(cookie);
Use query strings to pass user-specific data between pages, which can eliminate the need for session variables. For example, you can use the following code to create a hyperlink that passes the user name as a query string parameter:
<a href="Page2.aspx?UserName=Alice">Go to Page 2</a>
Use database tables to store user-specific or application-wide data, which can provide persistence, security, and scalability for your web applications. For example, you can use the following code to store and retrieve the user name from a database table using Entity Framework:
// Store the user name in the database
using (var db = new MyDbContext())
{
var user = new User { Name = "Alice" };
db.Users.Add(user);
db.SaveChanges();
}
// Retrieve the user name from the database
using (var db = new MyDbContext())
{
var user = db.Users.Find(1);
var userName = user.Name;
}
Technique 9: Optimize Data Access:
Data access is one of the most common sources of performance bottlenecks in web applications. data Access is used to interact with data sources efficiently and with minimal overhead. Data access is often one of the most time-consuming and resource-intensive parts of an ASP.NET application.
Use asynchronous programming patterns to avoid blocking the request thread while waiting for a data operation to complete. For example, you can use the following code to asynchronously query a database using Entity Framework Core:
public async Task<ActionResult> Index()
{
using (var db = new MyDbContext())
{
var users = await db.Users.ToListAsync();
return View(users);
}
}
Use caching techniques to store frequently accessed data in memory and avoid unnecessary database queries. For example, you can use the following code to cache a list of users using the IMemoryCache interface:
public async Task<ActionResult> Index()
{
var cacheKey = "Users";
var users = await _memoryCache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
using (var db = new MyDbContext())
{
return await db.Users.ToListAsync();
}
});
return View(users);
}
Use pagination techniques to return large collections of data across multiple smaller pages and reduce network bandwidth and memory usage. For example, you can use the following code to query a database using Entity Framework Core and return a page of 10 users:
public async Task<ActionResult> Index(int pageNumber = 1)
{
var pageSize = 10;
using (var db = new MyDbContext())
{
var users = await db.Users
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return View(users);
}
}
Technique 10: Use String builder instead of String concatenation
Strings are immutable, which means that every time you use the + operator or the String.Concat method to concatenate strings, you create a new string object that requires a new allocation of space. This can cause memory fragmentation and garbage collection overhead. The StringBuilder class provides a mutable string buffer that can be modified without creating new string objects.
Use the StringBuilder constructor to create a new instance of the StringBuilder class and initialize it with a string value. For example, you can use the following code to create a StringBuilder object with the string “Hello”:
var sb = new StringBuilder("Hello");
Use the Append method of the StringBuilder class to append a string or any other object to the end of the current StringBuilder object. For example, you can use the following code to append " World" to the existing StringBuilder object:
sb.Append(" World");
Use the ToString method of the StringBuilder class to return a string that represents the current contents of the StringBuilder object. For example, you can use the following code to display the final string:
Console.WriteLine(sb.ToString());
// output:// Hello World
Technique 11: Use Server.transfer instead of response.redirect to avoid an extra HTTP request.
Server.transfer is used to transfer the execution of the current request to another page on the same server without sending a response back to the browser. Response.Redirect tells the browser to request a different page, which causes an extra round trip between the browser and the server. Server.Transfer preserves the original request context and avoids the network latency and bandwidth consumption associated with Response.Redirect.
Use the Server.Transfer method in your code-behind file to transfer the execution of the current request to another page on the same server. For example, you can use the following code to transfer the execution from Page1.aspx to Page2.aspx:
protected void Button1_Click(object sender, EventArgs e)
{
Server.Transfer("Page2.aspx");
}
Use the preserveForm parameter of the Server.Transfer method to specify whether to preserve the form data from the original page in the transferred page. For example, you can use the following code to transfer the execution from Page1.aspx to Page2.aspx and preserve the form data:
protected void Button1_Click(object sender, EventArgs e)
{
Server.Transfer("Page2.aspx", true);
}
Use the PreviousPage property of the transferred page to access the properties and controls of the original page. For example, you can use the following code in Page2.aspx to display a label value from Page1.aspx:
protected void Page_Load(object sender, EventArgs e)
{
if (PreviousPage != null)
{
Label1.Text = PreviousPage.Label1.Text;
}
}
Conclusion
This article provides different techniques which developers can use to not only improve the performance of their ASP.NET applications but also enhance the user experience and reduce the total cost of ownership.
Comments