We have already learned a bit about error handling in ASP.Net Core in the Error Handling with the try/catch pattern and Exceptions sections in module 24, Create the Update Page, where we catch a DbUpdateException and handle the error ourselves in a try/catch block.
And we learned how to log errors in module 27, Logging & Configuration.
Bubble Up
We always have the option to handle any foreseen errors like this ourselves as in the Vehicle Update example.
But, most of the time, for unhandled errors, we just let errors bubble up.
When an error (exception) occurs in a program, if the current piece of code doesn’t handle it, the error is passed (bubbled up) to the next higher level in the call stack.
This continues until:
- Some higher-level code catches the error, or
- It reaches the top level (like the runtime), which usually terminates the program.
Example in C#
void Level1()
{
Level2();
}
void Level2()
{
Level3();
}
void Level3()
{
throw new Exception("Something went wrong!");
}
Level3()throws the exception.- Since
Level3()has notry/catch, the error bubbles up toLevel2(). - If
Level2()also doesn’t catch it, it bubbles up again toLevel1(). - If
Level1()doesn’t catch it either, the exception bubbles all the way to the runtime → crash.
Why bubbling up is useful
- Separation of concerns: Low-level code doesn’t need to know how to handle every possible failure.
- Centralized handling: You can catch errors in one place (like middleware in ASP.NET Core).
- Flexibility: If a lower method can’t decide what to do, it lets the caller decide.
The Developer Exception Page
The Developer Exception Page displays detailed information about unhandled request exceptions. It captures exceptions from the HTTP pipeline and generates error responses.
ASP.Net Core v6.0 and up enable the developer exception page by default when running in the Development environment.
So we no longer need to add a manual UseDeveloperExceptionPage statement in the middleware setup in Program.cs.
A look at the Developer Exception Page
To see the Developer Exception Page in action let’s create a controller and view with a button to throw a simple sample error.
Create a controller called ErrorTestController and fill it with the code shown below.
FredsCars\Controllers\ErrorTestController.cs
using Microsoft.AspNetCore.Mvc;
namespace FredsCars.Controllers
{
public class ErrorTestController : Controller
{
public IActionResult Index()
{
return View();
}
public void ThrowSimpleError()
{
throw new Exception("My simple error.");
}
}
}
In the code above the Index method will return a view with an anchor button on it we can use to throw an error. The anchor button will call the ThrowSimpleError method which will throw an exception.
Next, create a View for the Index method. As a shortcut, you can right click on the Index method and select Add View.

In the Add New Scaffolded Item dialogue, select the Razor View – Empty template and click the Add button.

In the Add New Item – FredsCars dialoque, leave the default name as Index.cshtml and click the Add button.

Modify the ErrorTest/Index View with the following code.
FredsCars\Views\ErrorTest\Index.cshtml
@model VehiclesListViewModel
@{
ViewData["Title"] = "Error Test";
}
<div class="container-fluid mt-3">
<h3 class="text-center bg-primary-subtle py-2"
style="border: 1px solid black;">
Error Tests
</h3>
</div>
<hr />
<div class="container">
<a asp-action="ThrowSimpleError"
asp-route-id="24"
asp-route-name="Sam"
class="btn btn-danger">
Simple Error
</a>
</div>
In the code above we have an anchor tag helper button that points to the ThrowSimpleError method in the ErrorTest controller with two route values. The id route value will be a URL segment because it matches id in the default route we setup in Program.cs using the WebApplication.MapDefaultControllerRoute method.
/*** Add endpoints for controller actions and
the default route ***/
app.MapDefaultControllerRoute();

The name route value will be in a querystring because name does not match any URL segment name in the default route.
Now navigate your browser to:
https://localhost:40443/ErrorTest and click the Simple Error button.

The button click calls the ThrowSimpleError method in the ErrorTest controller which throws an error with the following line.
throw new Exception("My simple error.");
The above line throws an exception with the message, “My simple error.”.
The error bubbles all the way up, the application crashes, and the Developer Exception Page is shown with the following URL (the id, 24, as the last URL segment, and name, Sam, in the querystring).
https://localhost:40443/ErrorTest/ThrowSimpleError/24?name=Sam
The Developer Exception Page contains five tabs.
- Stack
- Query
- Cookies
- Headers
- Routing
On the Stack tab, we can see the Exception message, the file name and line number the error was triggered at, and the stack trace.
- Filename: ErrorTestController.cs
- Line number: 14

On the Query tab, we can see our querystring name and value (name = Sam).

We haven’t used any cookies in our application, but, on the Cookies tab we can see some ASP.Net Core Antiforgery cookies from form submissions.

The Headers tab shows all of the headers sent along with the HTTP request.

On the Routing tab we can see useful information about the endpoint (display name and route pattern), and the value of our id route value (24).

Custom Exception Handler page
The Developer Exception Page is great during development and gives us a lot of useful information on unhandled exceptions. But, in production, we clearly don’t want to show all of this information to users because of obvious security concerns.
In production when an unhandled exception occurs we want to log it and send the user to a custom exception page.
To create a custom exception handler page, the first thing we need to do is call the WebApplication.UseExceptionHandler method. Modify Program.cs with the following changes.
FredsCars\Program.cs
... existing code ...
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
... existing code ...
In the code above, we are instructing ASP.Net Core to go to the Error method in the Home controller when an unhandled exception occurs in non development environments via the UseExceptionHandler method. We are also telling ASP.Net Core to always use Https with the WebApplication.UseHsts method . So, even when the user’s browser requests a URL through http it gets redirected to the https address of the web application.
In order to test the application in the production environment we need to make a change in launchSettings.json. Change the ASPNETCORE_ENVIRONMENT to production now for the https profile.
FredsCars\Properties\launchSettings.json
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:40443;http://localhost:40080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
}
Make sure you make the change for the https profile and not the http profile.
We also need to temporarily add a connection string to the development FredsCarsMvc localdb database in appsettings.production.json. We can copy the connection string from Secrets.json. The production database will eventually be in a full version of SQL Server rather than a local db version on our own private machine or located in Azure. We can change the connection string here at that point.
Make the following change to appsettings.production.json.
FredsCars\appsettings.production.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"FredsCarsMvcConnection": "Server=(localdb)\\MSSQLLocalDB;Database=FredsCarsMvc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Next, we need to create the Error method in the Home controller that app.UseExceptionHandler("/Home/Error")
points to. Make the changes to the home controller shown below.
FredsCars\Controllers\HomeController.cs
using FredsCars.Models.Repositories;
using FredsCars.Models.ViewModels;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace FredsCars.Controllers
{
public class HomeController : Controller
{
... existing code ...
public ViewResult Error()
{
return View();
}
}
}
The new Error method in the code above will simply return the Home controller’s Error view when UseExceptionHandler redirects the request there.
Next, create a view called Error.cshtml in the Views/Home folder and modify its code with that shown below.
FredsCars\Views\Home\Error.cshtml
@{
ViewData["Title"] = "Error Test";
}
<div class="container-fluid mt-3">
<h1 class="text-center bg-primary-subtle py-2"
style="border: 1px solid black;">
Oops!
</h1>
</div>
<h3 class="text-center"
style="margin-top:30px">Something happened
</h3>
The Error view above will show a blue banner in a div tag like most of our other pages with a h1 heading tag containing the literal text, “Oops!”. And underneath of it there is an h3 heading tag centered displaying the text, “Something happened”.
We need to make one more change to get Serilog to log unhandled exceptions to the SQL Server database. Make the change shown below to appsettings.production.json.
FredsCars\appsettings.production.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"MinimumLevel": {
"Override": {
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "Error"
}
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"FredsCarsMvcConnection": "Server=(localdb)\\MSSQLLocalDB;Database=FredsCarsMvc;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}
Now once again navigate to the ErrorTest page:
https://localhost:40443/ErrorTest
and click the “Simple Error” button.

The Simple Error button throws our sample test error from the ThrowSimpleError method in the ErrorTest controller. The UseExceptionHandler method in the middleware setup of Program.cs redirects the request when the exception is thrown to the Error method of the Home controller which returns the Error View.

The UseExceptionHandler also automatically logs the exception for us to all logging providers and Serilog sinks.
Error logged to console.
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
An unhandled exception has occurred while executing the request.
System.Exception: My simple error.
at FredsCars.Controllers.ErrorTestController.ThrowSimpleError() in C:\Development\FredsCars\MVC\Module29\FredsCars\FredsCars\Controllers\ErrorTestController.cs:line 14
at Microsoft.Extensions.Internal.ObjectMethodExecutor.<>c__DisplayClass36_0.<WrapVoidMethod>b__0(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.VoidResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Program.<>c.<<<Main>$>b__0_1>d.MoveNext() in C:\Development\FredsCars\MVC\Module29\FredsCars\FredsCars\Program.cs:line 91
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
Matched route: {controller=Home}/{action=Index}/{id?}
RouteName: default
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint '"FredsCars.Controllers.HomeController.Error (FredsCars)"'
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[102]
Route matched with "{action = \"Error\", controller = \"Home\"}". Executing controller action with signature "Microsoft.AspNetCore.Mvc.ViewResult Error()" on controller "FredsCars.Controllers.HomeController" ("FredsCars").
info: Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor[1]
Executing ViewResult, running view "Error".
info: Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor[4]
Executed ViewResult - view "Error" executed in 66.3496ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker[105]
Executed action "FredsCars.Controllers.HomeController.Error (FredsCars)" in 140.123ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint '"FredsCars.Controllers.HomeController.Error (FredsCars)"'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished "HTTP/2" "GET" "https"://"localhost:40443""""/ErrorTest/ThrowSimpleError/24""?name=Sam" - 500 null "text/html; charset=utf-8" 207.0996ms
You can see the message of the fail/error level log of the error we threw: “My simple error.”, and the stack trace.
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
An unhandled exception has occurred while executing the request.
System.Exception: My simple error. <-- our thrown error
at FredsCars.Controllers.ErrorTestController.ThrowSimpleError() in
Error logged to SQL Server

In the screen shot above in SSMS you can see the row that has the unhandled exception logged. It is the only row where the Exception column is populated with the text, “System.Exception: My simple error.”.
Here is the comlete system exception text that is in that column.
System.Exception: My simple error.
at FredsCars.Controllers.ErrorTestController.ThrowSimpleError() in C:\Development\FredsCars\MVC\Module29\FredsCars\FredsCars\Controllers\ErrorTestController.cs:line 14
at Microsoft.Extensions.Internal.ObjectMethodExecutor.<>c__DisplayClass36_0.<WrapVoidMethod>b__0(Object target, Object[] parameters)
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.VoidResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Program.<>c.<<<Main>$>b__0_1>d.MoveNext() in C:\Development\FredsCars\MVC\Module29\FredsCars\FredsCars\Program.cs:line 91
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddlewareImpl.<Invoke>g__Awaited|10_0(ExceptionHandlerMiddlewareImpl middleware, HttpContext context, Task task)
Access the excepton with IExceptionHandlerPathFeature
We can access an exception with the IExceptionHandlerPathFeature interface. Once we have access we can inspect the exception in debug mode or log any information about the exception we find useful.
Make the changes to the home controller shown below.
FredsCars\Controllers\HomeController.cs
using FredsCars.Models.Repositories;
using FredsCars.Models.ViewModels;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace FredsCars.Controllers
{
public class HomeController : Controller
{
... existing code ...
public ViewResult Error()
{
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
return View();
}
}
}
Now set a break point at the return statement after we capture the error with IExceptionHandlerPathFeature in the Home controller Error method as shown below.

If the application is running, stop it by typing Ctrl-C in the command or console window it is running in. Then run the project in debug mode from the VS IDE by typing Ctrl-F5 or clicking the green arrow in the top toolbar.

Again navigate to https://localhost:40443/ErrorTest and click the Simple Error button.
When the exception is thrown in debug mode the application crashes, the offending line is highlighted in yellow, and a User-Unhandled dialogue is shown with details about the exception. Click the green arrow again either in the dialogue or in the top toolbar to carry on. You may have to click it twice.

Now the application execution stops at the break point we set in the home controller. Right click on the exceptionHandlerPathFeature variable and select QuickWatch or place your cursor on the variable and type Shift-F9 to open the QuickWatch window and inspect the error.


Now we could log information about the exception from the Home controller by injecting an ILogger<T> into the Error method like the following. Do not type in the following code. It is just a sample. We already have plenty of sufficient logging.
public ViewResult Error(ILogger<HomeController> logger)
{
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
logger.LogError(exceptionHandlerPathFeature.Error
.Message);
logger.LogError(exceptionHandlerPathFeature.Path);
return View();
}
HTTP Status Codes
Whenever our application returns a web page, it returns with it an HTTP Status code.
An HTTP status code is a three-digit number that a web server sends back in response to a client’s request (such as a web browser or API call). It indicates the result of the request—whether it was successful, caused a redirection, failed due to a client error, or failed because of a server error.
To see this in action, navigate your browser to https://localhost:40443 and open the web development tools by typing F-12 or right click anywhere on the page and select inspect from the context menu.
In the Web Dev Tools panel, click the Network tab and then click the Clear network log icon to clear the network logs. Then refresh the page.

If the application is working correctly it should return a status code of [200 – OK].

A status code of [200 – OK] basically means that the server successfully processed the request.
If you click on the localhost request on the left under Name, you can see more information about the request under the Headers tab. You can see:
- The Request URL: https://localhost:40443/
- The Request Method: GET
- The Status Code: 200 OK

Now, navigate to https://localhost:40443/ErrorTest and again in the web dev tools panel click on the Network tab, clear the logs, and click the Simple Error button.
This time we get a status code of [500 – Internal Server Error]

A 500 type status code means there was some type of error processing the request on the web server. This happens in this example because we purposely throw an error in the ErrorTest controller.
public void ThrowSimpleError()
{
throw new Exception("My simple error.");
}
Now point your browser to a URL endpoint that does not exist in the application for example, https://localhost:40443/badpage. The response for this will be a status code of [404- Not Found]

But, for a 404, instead of the response being wrapped in our web application layout like the 500 error response was, a bland generic text response is what we see as shown above.
By default, an ASP.NET Core app doesn’t provide a status code page for HTTP error status codes, such as 404 – Not Found. When the app sets an HTTP 400-599 error status code that doesn’t have a body, it returns the status code and an empty response body like we just saw with [404 – Not Found] in the above example.
Our 500 response is returned with a body in our web app layout because of the UseExceptionHandler middleware we added to our pipeline in Program.cs.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
But, UseExceptionHandler, doesn’t cover 400 errors.
UseStatusCodePagesWithRedirects
To enable full web page responses rather than text-only responses for common error status codes not covered by UseExceptionHandler, call UseStatusCodePages in Program.cs
Make the following change in Program.cs
FredsCars\Program.cs
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseStatusCodePagesWithRedirects("/Home/Status?code={0}");
// Configure the HTTP request pipeline.
app.Logger.LogDebug("Pipeline configuration starting.");
The code above adds middleware for a Status Codes page to the pipeline. It directs the response to the Status action of the Home controller (which we will write next). The 0 in squiggly brackets “{0}” is a place holder for the status code and will be contained in a querystring of the URL.
Add the Status action to the Home controller as shown below.
FredsCars\Controllers\HomeController.cs
... existing code ...
public ViewResult Error()
{
var exceptionHandlerPathFeature =
HttpContext.Features.Get<IExceptionHandlerPathFeature>();
return View();
}
public ViewResult Status(int code)
{
string statusCode;
switch (code)
{
case 404:
statusCode = "[404-NotFound]";
break;
case 403:
statusCode = "[403-Forbidden]";
break;
default:
statusCode = "[UnKnown Error]";
break;
}
return View("Status", statusCode);
}
}
}
The code above adds an action called Status to the Home controller that takes in as a parameter an int named code which it will receive from the querystring of the URL.
We start by declaring a variable of type string named statusCode. We then use a switch statement to assign the statuCode variable a string.
We have two cases in the switch block which chooses the correct case to execute based on the incoming code parameter value of the Status method.
If the code is 404 the following code is exectuted.
case 404:
statusCode = "[404-NotFound]";
break;
Here statusCode is set to the string, “[404-NotFound]” and the C# break keyword leaves the switch block and picks back up execution with the return statement.
The 403 case behaves exactly as the 404 case but sets statusCode to the string, “[403-Forbidden]“.
The default case is a catch-all. If none of the cases match the incoming code, it assigns statusCode the string, “[UnKnown Error]” and breaks out of the switch block.
The return statement returns the Status view of the home controller (which we will create next) and a string, statusCode which we just set in the switch block, as the model.
return View("Status", statusCode);
Next create a Razor View named Status.cshtml in the Views/Home folder with the Razor code shown below.
FredsCars\Views\Home\Status.cshtml
@model string
@{
ViewData["Title"] = "Status";
}
<div class="container-fluid mt-3">
<h1 class="text-center bg-primary-subtle py-2"
style="border: 1px solid black;">
Status
</h1>
</div>
<h3 class="text-center"
style="margin-top:30px">
@Model
</h3>
In this view we once again have a div element containing a blue header with the text, “Status”, in an h1 tag.
Below it we have an h3 heading tag that contains the model, which is a string containing the text we set in the switch block within the Status action of the Home controller.
Now restart the application and once again point your browser to https://localhost:40443/badpage. Open the web dev tools, go to the Network tab, and clear the logs before executing the page so you can inspect the network logs from the request.
You should see results similar to the following screen shot.

The badpage request shows it returned a Status Code of 302 Found which is a redirect. In this case it was redirected to “Home/Status?code=404” from the
UseStatusCodePagesWithRedirects
statement in the Program.cs middleware.
app.UseStatusCodePagesWithRedirects("/Home/Status?code={0}");
Click on the badpage request and you can see the redirect location.

UseStatusCodePagesWithReExecute
The problem with UseStatusCodePagesWithRedirects is that it redirects the browser to a different URL to show the Status Code like [404-NotFound] because the original response is a 302 redirect to the Status view of the Home controller. So the user ends up seeing a URL like
https://localhost:40443/Home/Status?code=404
rather than
https://localhost:40443/badpage
along with the page.

We can fix this using the UseStatusCodePagesWithReExecute extension method in Program.cs
Modify Program.cs with the changes shown below.
FredsCars\Program.cs
... existing code ...
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
// app.UseStatusCodePagesWithRedirects("/Home/Status?code={0}");
app.UseStatusCodePagesWithReExecute("/Home/Status", "?code={0}");
// Configure the HTTP request pipeline.
app.Logger.LogDebug("Pipeline configuration starting.");
... existing code ...
In the code we have commented out the line that uses UseStatusCodePagesWithRedirects and replaced it with UseStatusCodePagesWithReExecute which adds a StatusCodePages middleware to the pipeline and specifies that the response of the body should be generated by re-executing the request pipeline using an alternate path. The path contains a ‘{0}‘ placeholder for the status code. We can reuse the same /Home/Status endpoint with the same behavior using the switch block. But the endpoint:"/Home/Status"
and querystring:"?code={0}"
get split into two string paramaters within UseStatusCodePagesWithReExecute.
app.UseStatusCodePagesWithReExecute("/Home/Status", "?code={0}");
Restart the application and navigate to https://localhost:40443/badpage.
Now we get the same web page look with the blue header bar containing the text, “Status”, and the [404-NotFound] message in the main content area of the web page. But, this time the URL stayed as https://localhost:40443/badpage.

And we can see in the web dev tools for the badpage request that this time instead of a 302 Redirect response, a 404 NotFound response was returned as the error was re-executed in the pipeline.
Set Environment back to Development
We started off this module looking at the Developer Exception Page. Then we setup up error handling for a production environment. In doing so we needed to change the ASPNETCORE_ENVIRONMENT variable for the https profile to Production. Let’s go ahead and set it back to Development so we can see the Developer Exception Page once again when we encounter errors during the rest of our development.
Make the following change to launchSettings.json.
FredsCars\Properties\launchSettings.json
... existing code ...
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:40443;http://localhost:40080",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
... existing code ...
We can also remove the connection string to the development localdb SQL Server from appsettings.production.json. Go ahead and do that now. This is what appSettings.production.json should look like now.
FredsCars\appsettings.production.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Serilog": {
"MinimumLevel": {
"Override": {
"Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware": "Error"
}
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
// TODO: Add production connection string here when we deploy.
}
}
What’s Next
In this module we learned all about error handling. We first looked at the Developer Exception page and how it is helpful during development. Then we wired up exception handling for non production environments, particularly production.
In the next module we are going to get into security and administration using ASP.Net Core Identity.
