There are a lot of different small points to remember about Asp.Net Core best practices. This post intends to summarize the most important points in a way that makes it easy to preuse quickly. This post does not try to be a comprehensive guide, but a quick reference for many different points that exist on the topic.
Performance Optimization Techniques in Asp.Net Core
- Cache aggressively: ASP.NET Core supports various caching techniques that can help enhance application performance.
- Understand hot code paths: Hot code paths are code paths that are frequently called and where most of the execution time occurs. Hot code paths limit app scale-out and performance.
- Avoid blocking calls: ASP.NET Core apps should be designed to process multiple requests simultaneously. Asynchronous APIs allow a small pool of threads to handle thousands of concurrent requests by not waiting on blocking calls.
- Return large collections across multiple smaller pages: Webpages should not load large amounts of data all at once. When returning a collection of objects, consider whether it could lead to performance issues. Determine if the design could produce poor outcomes like OutOfMemoryException or high memory consumption, thread pool starvation, slow response times, or frequent garbage collection.
Data Management and Access in Asp.Net Core
- Minimize large object allocations: Garbage collection is expensive on large objects (> 85 K bytes). Developers should minimize allocating objects in hot code paths.
- Optimize data access and I/O: Interactions with a data store and other remote services are often the slowest parts of an ASP.NET Core app. Reading and writing data efficiently is critical for good performance. Developers should call all data access APIs asynchronously, retrieve only necessary data, and consider caching frequently accessed data. They should also minimize network round trips, use no-tracking queries in Entity Framework Core when accessing data for read-only purposes, filter and aggregate LINQ queries, and avoid projection queries on collections.
- Frequently called code paths such as middleware components, logging, authorization handlers, and initialization of transient services have a significant impact on performance, and optimization is crucial.
Asynchronous Processing using Asp.Net Core
- Long-running tasks should not be part of ordinary HTTP request processing, and it is better to handle them asynchronously, outside the HTTP request-response process. Real-time communication options, such as SignalR, can be used to communicate with clients asynchronously.
Response Optimization
- Initial load requests can be improved by bundling and minifying the JavaScript, CSS, or image files.
- Compressing the response of an app will reduce the payload size, thus increasing its responsiveness.
Versioning and Error Handling
- Each new release of ASP.NET Core includes performance improvements, so upgrading to the current version is recommended.
- Exceptions should be rare, and their use for controlling normal program flow should be avoided. Logic should be included in the app to detect and handle conditions that would cause an exception.
Development Practices When Using Asp.Net Core
- All I/O in ASP.NET Core is asynchronous, so synchronous read or write on HttpRequest/HttpResponse body should be avoided. Instead, asynchronous overloads should be used to avoid blocking thread pool threads, which can lead to thread pool starvation.
- Frequently called code paths such as middleware components, logging, authorization handlers, and initialization of transient services have a significant impact on performance, and optimization is crucial.
- Long-running tasks should not be part of ordinary HTTP request processing, and it is better to handle them asynchronously, outside the HTTP request-response process. Real-time communication options, such as SignalR, can be used to communicate with clients asynchronously.
- Initial load requests can be improved by bundling and minifying the JavaScript, CSS, or image files.
- Compressing the response of an app will reduce the payload size, thus increasing its responsiveness.
- Each new release of ASP.NET Core includes performance improvements, so upgrading to the current version is recommended.
- Exceptions should be rare, and their use for controlling normal program flow should be avoided. Logic should be included in the app to detect and handle conditions that would cause an exception.
- Use validation attributes on your models to ensure data integrity.
- Use exception filters to handle exceptions globally.
- Use middleware to handle cross-cutting concerns like logging, caching, and authentication.
- Use attribute routing to define your application’s routes.
- Use compression to reduce the size of HTTP responses.
- Use bundling and minification to reduce the number and size of HTTP requests.
- Use CDNs (Content Delivery Networks) to improve the delivery of static assets.
- Use view models to separate the presentation logic from your business logic. This will make your code more maintainable and easier to test.
- Use the async and await keywords to write asynchronous code in your controllers and models. This will improve the performance and scalability of your application.
- Use partial views to reuse common UI components across your application. This will help you avoid code duplication and make your code easier to maintain.
- Use action filters to add cross-cutting concerns to your application, such as logging, caching, or security. This will help you keep your code modular and maintainable.
Best Practices Regarding HttpContext and HttpRequest/Response and IIS and Middlewares
- HttpContext.Request.ReadFormAsync should be used instead of HttpContext.Request.Form because it can lead to thread pool starvation. The form should be read only after it has been read by a call to ReadFormAsync, and the cached form value is being read using HttpContext.Request.Form.
- Do not capture the HttpContext in background threads.
-
- A closure is capturing the HttpContext from the Controller property. This is a bad practice because the work item could:
-
-
- Run outside of the request scope.
- Attempt to read the wrong HttpContext.
-
-
- Instead, copy the data required in the background task during the request.
- Don’t reference anything from the controller.
- Background tasks should be implemented as hosted services.
- Do not capture services injected into the controllers on background threads.
-
- A closure is capturing the DbContext from the Controller action parameter. This is a bad practice because the work item could:
-
-
- Run outside of the request scope.
- Result in an ObjectDisposedException since the ContosoDbContext is scoped to the request.
-
-
- Inject an IServiceScopeFactory to create a scope in the background work item.
- Create a new dependency injection scope in the background thread.
- Don’t reference anything from the controller.
- Don’t capture the ContosoDbContext from the incoming request.
- Do not modify the status code or headers after the response body has started.
-
- ASP.NET Core does not buffer the HTTP response body.
- The headers are sent along with that chunk of the body to the client.
- It’s no longer possible to change response headers.
- Use HttpResponse.OnStarting to set the headers before the response headers are flushed to the client.
- Checking if the response has not started:
-
-
- Provides the ability to append or override headers just in time.
- Doesn’t require knowledge of the next middleware in the pipeline.
-
- Do not call next() if you have already started writing to the response body.
-
- Components only expect to be called if it’s possible for them to handle and manipulate the response.
- Use In-process hosting with IIS.
-
- Using in-process hosting, an ASP.NET Core app runs in the same process as its IIS worker process.
- In-process hosting provides improved performance over out-of-process hosting because requests aren’t proxied over the loopback adapter.
- The loopback adapter is a network interface that returns outgoing network traffic back to the same machine.
- IIS handles process management with the Windows Process Activation Service (WAS).
- Projects default to the in-process hosting model in ASP.NET Core 3.0 and later.
- Remember that HttpRequest.ContentLength is null if the request did not include a message body, and that it’s not safe to assume that the ContentLength property will never be null.
Summary
This post summarizes essential ASP.NET Core best practices to enhance performance, code maintainability, and security.
Key Areas:
- Performance Optimization:
- Utilize caching strategically.
- Identify and optimize hot code paths.
- Employ asynchronous programming.
- Return large datasets in smaller chunks.
- Data Management and Access:
- Minimize large object allocation.
- Optimize data access patterns.
- Consider caching frequently accessed data.
- Asynchronous Processing:
- Handle long-running tasks asynchronously.
- Response Optimization:
- Bundle and minify static assets.
- Compress application responses.
- Development Practices:
- Leverage asynchronous I/O operations.
- Implement proper error handling.
- Utilize dependency injection and middleware.
- Separate presentation logic from business logic.
- Write clean and maintainable code.
- Specific Practices:
- Handle HttpContext and background tasks appropriately.
- Set response headers before starting the response body.
- Consider in-process hosting with IIS for performance benefits.
More Info and Resources:
10 Best Practices to Secure ASP.NET Core MVC Web Applications | Syncfusion Blogs
ASP.NET Core Best Practices | Microsoft Learn
ASP.NET Core application architecture guidance (microsoft.com)
Overview of ASP.NET Core MVC | Microsoft Learn