Back to: ASP.NET Core Tutorials For Beginners and Professionals
Handling Non-Success HTTP Status Codes in ASP.NET Core MVC
In this article, I will discuss Handling Non-Successful HTTP Status Codes, i.e., the use of UseStatusCodePages, UseStatusCodePagesWithRedirects, and UseStatusCodePagesWithReExecute middleware components in ASP.NET Core MVC Applications with Examples.
When building web applications, handling HTTP status codes that indicate errors (non-success codes, i.e., those not in the 200–299 range) is essential. The most common error is a 404 (Not Found) error. In ASP.NET Core MVC, we can handle these errors centrally using built-in middleware components. In this article, we will discuss what non-success HTTP status codes are and how to handle them, particularly 404 errors, in a centralized manner using ASP.NET Core MVC.
What are Non-Success HTTP Status Codes?
HTTP status codes represent the outcome of an HTTP request. Non-success HTTP status codes are responses returned by a server that indicates a request was not successful. These codes typically fall into the following categories:
- 3xx (Redirection): The client must take additional action to complete the request.
- 4xx (Client Errors): The request contains bad syntax or cannot be fulfilled (e.g., 401 Unauthorized, 404 Not Found, etc.).
- 5xx (Server Errors): The server failed to fulfill a valid request (e.g., 500 Internal Server Error).
In ASP.NET Core MVC, handling these codes effectively ensures users receive helpful feedback instead of generic error messages. For instance, returning a friendly error page when a user navigates to a non-existent page can improve the overall user experience.
Types of 404 Errors in ASP.NET Core MVC
There are two main scenarios in which a 404 error might occur in an ASP.NET Core MVC application:
Type 1: Resource with the Specified ID Does Not Exist
This type of error happens when a valid route is used, but the resource (such as an employee, product, or customer) identified by the given ID does not exist in the database. This is usually a business logic error rather than a routing error.
Example:
Imagine a user trying to view details of a product by its ID. The application receives the ID and queries the database, but no product is found. Instead of showing a generic error page, we can detect this in the controller action and return a custom view that explains the product doesn’t exist. Let us understand this with an example.
Creating a New ASP.NET Core MVC Project:
First, create a new ASP.NET Core Project using the Model-View-Controller template and give the Project name as NonSucessHTTPStatusCodeDemo.
Creating the Product Model:
Then, create a class file named Product.cs within the Models folder and then copy and paste the following code. The Product model will represent the entity we are trying to retrieve. This model simulates a situation where the requested product does not exist.
namespace NonSucessHTTPStatusCodeDemo.Models { // Represents a Product entity with properties for ID, Name, and Price. public class Product { public int Id { get; set; } // Unique identifier for the product public string Name { get; set; } // Name of the product public decimal Price { get; set; } // Price of the product } }
Modifying the Controller:
Next, create a new MVC Empty Controller named ProductsController within the Controllers folder and copy and paste the following code. The Details action checks for the product. If the product is not found, it sets the status to 404 and returns a custom view.
using Microsoft.AspNetCore.Mvc; using NonSucessHTTPStatusCodeDemo.Models; namespace NonSucessHTTPStatusCodeDemo.Controllers { public class ProductsController : Controller { // Simulate a data source with a static list of products. private static List<Product> Products = new List<Product> { new Product { Id = 1, Name = "Ultra Laptop", Price = 1299.99m }, new Product { Id = 2, Name = "Smartphone Pro", Price = 899.99m }, new Product { Id = 3, Name = "Wireless Tablet", Price = 499.99m } }; // GET: /Products/Details/{id} public IActionResult Details(int id) { // Try to fetch the product from the static list var product = Products.FirstOrDefault(p => p.Id == id); if (product == null) { // Set the HTTP status code to 404 (Not Found) Response.StatusCode = 404; // Render a professionally styled error view for missing products. return View("ProductNotFound", id); } // Render the Details view with the found product. return View(product); } } }
Creating Details View:
Create a view named Details.cshtml within the Views/Products folder and copy and paste the following code. This view will be rendered when the Product is found.
@model NonSucessHTTPStatusCodeDemo.Models.Product @{ ViewBag.Title = "Product Details"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div class="container mt-5"> <h2 class="mb-4">Product Details</h2> <div class="card shadow-sm"> <div class="card-header bg-primary text-white"> @Model.Name </div> <div class="card-body"> <p class="card-text"> <strong>Product ID:</strong> @Model.Id </p> <p class="card-text"> <strong>Price:</strong> @Model.Price.ToString("C") </p> </div> </div> </div>
Creating ProductNotFound View:
Next, create a view named ProductNotFound.cshtml within the Views/Products folder and copy and paste the following code. When the ProductId is invalid and not found, we render the following Custom Not Found view.
@model int @{ ViewBag.Title = "Product Not Found"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div class="container mt-5"> <div class="alert alert-warning shadow-sm" role="alert"> <h4 class="alert-heading">Product Not Found</h4> <p>Sorry, no product exists with ID: <strong>@Model</strong>. Please check the product ID and try again.</p> <hr> <a href="@Url.Action("Index", "Home")" class="btn btn-primary">Return Home</a> </div> </div>
When a user navigates to a URL such as /Products/Details/10, the application detects that no product exists for the provided ID and displays the custom error page, as shown in the image below.
Type 2: The Provided URL Does Not Match Any Route
This error occurs when the requested URL does not correspond to any defined route in your application. For example, if the URL /Home/Something is requested and no matching route exists, ASP.NET Core will return a 404 error.
Explanation:
- In this case, the application isn’t aware of what resource the user is trying to access.
- Without a valid route, it’s not possible to provide resource-specific details.
- As a result, the default error handling (or a centrally configured custom error page) informs the user that the page was not found.
Default 404 Error Page:
If no custom handling is set up, navigating to an invalid route like /Home/Something will render the default 404 page provided by ASP.NET Core, as shown in the image below. This is because the URL /Home/Something does not match any routes in our application.
Handling Non-Success HTTP Status Codes in ASP.NET Core MVC:
ASP.NET Core provides three middleware components to handle non-successful HTTP status codes (like 404) and provide a uniform user experience.
- UseStatusCodePages: Returns a simple, generic error message.
- UseStatusCodePagesWithRedirects: Redirects the client’s browser to a custom error URL.
- UseStatusCodePagesWithReExecute: Re-executes the request with a modified path within the same request pipeline.
UseStatusCodePages Middleware
This is the least useful of the three status code middleware components as it will always give a generic error message, and we don’t have the option to customize or provide any user-friendly message.
- This middleware intercepts HTTP responses with non-success status codes.
- It returns a generic text message without any customization.
- Because it provides limited functionality, it is rarely used in production.
How to Configure:
To use it in ASP.NET Core MVC Applications and see what it can do, we need to register it in the HTTP Request Processing pipeline. So, please modify the Program class as follows. Here, we register the UseStatusCodePages middleware conditionally, i.e., this component will exist when the application is not running in the Development environment.
namespace NonSucessHTTPStatusCodeDemo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { // Use the developer exception page for detailed error information during development. app.UseDeveloperExceptionPage(); } else { // In non-development environments, register UseStatusCodePages middleware. app.UseStatusCodePages(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); } } }
This middleware writes a plain text response (e.g., “404 Not Found”) and does not allow for customization of the error message. With UseStatusCodePages Middleware configured, if we navigate to URL /Home/Something, it returns the following simple text response.
Note: To handle these non-success HTTP status codes (other than 200 Status Code) in the production environment and return a custom error view, we need to use either UseStatusCodePagesWithRedirects or UseStatusCodePagesWithReExecute middleware. Let us understand these two Middleware Components with Examples, and then we will see the differences.
UseStatusCodePagesWithRedirects Middleware
This middleware issues an HTTP redirect when a non-success status code is encountered. That means it redirects the user’s browser to a custom error page URL. A new HTTP request is made to load the error page, which allows us to serve a completely different URL for errors. Let us proceed with implementing Custom Error Handling with Redirects.
Step 1: Create an Error Controller
First, create a new MVC Empty Controller named ErrorController within the Controllers folder and then copy and paste the following code. This controller handles error routes.
This controller maps error status codes to custom messages and renders an appropriate error view. Here, within the action method, we check if a 404 error occurs, set the custom error message into the ErrorMessage ViewBag property, and render the NotFound view.
using Microsoft.AspNetCore.Mvc; namespace NonSucessHTTPStatusCodeDemo.Controllers { public class ErrorController : Controller { // This route catches any error code passed in the URL, e.g., /Error/404 [Route("Error/{statusCode}")] public IActionResult HttpStatusCodeHandler(int statusCode) { // Set a proper error message based on the status code switch (statusCode) { case 404: ViewBag.ErrorMessage = "Sorry, the resource you requested could not be found."; break; default: ViewBag.ErrorMessage = "An unexpected error occurred. Please try again later."; break; } // Render NotFound view return View("NotFound"); } } }
Note: The ErrorController uses route parameters to capture the HTTP status code and display an appropriate error message.
Step 2: Create the NotFound View
Next, we need to add the NotFound.cshtml view within the Views/Error folder. Once you add the view, copy and paste the following code.
@{ ViewBag.Title = "Error - Page Not Found"; Layout = "~/Views/Shared/_Layout.cshtml"; } <div class="container mt-5"> <div class="alert alert-danger shadow-sm" role="alert"> <h1 class="display-4">404 - Page Not Found</h1> <p class="lead">@ViewBag.ErrorMessage</p> <hr class="my-4"> <p>The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.</p> <a class="btn btn-primary btn-lg" href="@Url.Action("Index", "Home")" role="button">Return to Home</a> </div> </div>
Step 3: Configure Middleware in Program.cs
Register the UseStatusCodePagesWithRedirects middleware when the environment is not in development. So, please modify the Program.cs class as follows:
namespace NonSucessHTTPStatusCodeDemo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { // Use the developer exception page for detailed error information during development. app.UseDeveloperExceptionPage(); } else { // In non-development environments, Redirect to a custom error page, e.g., /Error/404 app.UseStatusCodePagesWithRedirects("/Error/{0}"); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); } } }
Step-by-Step Request Processing with UseStatusCodePagesWithRedirects
Let us proceed and understand how UseStatusCodePagesWithRedirects works internally:
- Error Detection: A user makes a request (e.g., /Home/Something), and no matching route is found.
- Status Code Interception: The middleware detects the resulting 404 status code.
- Redirect Issued: Instead of sending the 404 response directly, the middleware sends a 302 redirect response to the client with a Location header set to /Error/404 (the placeholder {0} is replaced with the actual status code).
- New Request: The client’s browser automatically follows the redirect and makes a new request to /Error/404.
- Error Handling: The ErrorController processes this request, sets a custom error message, and renders the NotFound view.
Running the Application:
By default, the Environment is Development. First, set the environment to Production within the launchsettings.json file, then run the application and navigate to the URL /Home/Something. You will see the following custom 404 error view: NotFound.cshtml, as expected.
UseStatusCodePagesWithReExecute Middleware
This middleware re-executes the original request pipeline with a modified path instead of issuing a redirect. The URL in the user’s browser remains unchanged, as no new HTTP request is initiated. Since the same request context is maintained, it allows data sharing between the original request and the error handling logic.
To use UseStatusCodePagesWithReExecute middleware, replace app.UseStatusCodePagesWithRedirects(“/Error/{0}”); with app.UseStatusCodePagesWithReExecute(“/Error/{0}”); So, please modify the Program class as follows:
namespace NonSucessHTTPStatusCodeDemo { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); var app = builder.Build(); if (app.Environment.IsDevelopment()) { // Use the developer exception page for detailed error information during development. app.UseDeveloperExceptionPage(); } else { // In non-development environments, Reexecute to a custom error page, e.g., /Error/404 app.UseStatusCodePagesWithReExecute("/Error/{0}"); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run(); } } }
Step-by-Step Request Processing with UseStatusCodePagesWithReExecute
Let us proceed and understand how UseStatusCodePagesWithReExecute works internally:
- Error Detection: A user requests a URL that results in a 404 error (for example, /Home/Something).
- Context Capture: The middleware captures the context of the original request.
- Path Modification: It modifies the request path to /Error/404 (with {0} replaced by the status code).
- Re-Execution: The request is re-executed through the same pipeline. Because it uses the original context, items like route data or query strings can be preserved if needed.
- Error Handling: The error route is processed by the controller (e.g., ErrorController), and the custom error view (NotFound) is rendered without altering the URL shown in the browser.
Running the Application:
Now, re-run the application using the Production or Staging environment and then navigate to /Home/Something URL, and you should see the same custom 404 error view NotFound.cshtml as shown in the below image:
Comparing the Two Approaches, Redirects vs. ReExecute
The obvious question that should come to your mind at this point is the difference between these two middleware components and which one we should be using.
UseStatusCodePagesWithRedirects:
- Behavior: Issues a client-side redirect (HTTP 302), causing a new HTTP request.
- URL Change: The browser’s URL changes to the error page URL.
- Use Cases: This is preferred when you want to completely separate the error handling from the original request or when you want a distinct error page URL. Use this when you want the user’s browser to reflect a new URL for the error page.
UseStatusCodePagesWithReExecute:
- Behavior: Re-executes the request on the server side with a modified path.
- URL Preservation: The original URL remains in the browser’s address bar.
- Use Case: Use this when you want to preserve the original URL, maintain context, or avoid the overhead of an additional round-trip. This is especially useful when error handling involves shared logic with the original request.
So, ASP.NET Core MVC provides flexible middleware components to handle non-success HTTP status codes, especially 404 errors. Choosing between UseStatusCodePages, UseStatusCodePagesWithRedirects, and UseStatusCodePagesWithReExecute depends on the desired user experience, performance considerations, and SEO requirements. For most real-world applications, UseStatusCodePagesWithReExecute is preferred due to its performance benefits and user experience.
In the next article, I will discuss Error Pages Based on Status Codes in ASP.NET Core MVC Applications. In this article, I try to explain Error Pages Based on Status Codes in ASP.NET Core MVC Applications with Examples. I hope you enjoy this Error Pages Based on Status Code in the ASP.NET Core MVC article.