PROWARE technologies
PROWARE technologies

Handle "View Was Not Found" Server Errors in ASP.NET Core

View not found errors are easy to handle.

NOTE: this example uses ASP.NET Core 3.1 but should be backwards compatible and hopefully forwards compatible, too.

Create a new ASP.NET Core Web Application Project or open an existing one.

Modify the C# file named Startup.cs. Add UseStatusCodePagesWithReExecute as in the following code. This single line of code is key to making this error handling function properly.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace ProjectName
{
	public class Startup
	{
		public Startup(IConfiguration configuration)
		{
			Configuration = configuration;
		}

		public IConfiguration Configuration { get; }

		// This method gets called by the runtime. Use this method to add services to the container.
		public void ConfigureServices(IServiceCollection services)
		{
			services.AddControllersWithViews();
		}

		// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
		public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
		{
			if (env.IsDevelopment())
			{
				app.UseDeveloperExceptionPage();
			}
			else
			{
				app.UseStatusCodePagesWithReExecute("/Home/Error/{0}"); // NOTE: THIS LINE IS NEW

				app.UseExceptionHandler("/Home/Error");
			}
			app.UseStaticFiles();

			app.UseRouting();

			app.UseAuthorization();

			app.UseEndpoints(endpoints =>
			{
				endpoints.MapControllerRoute(
					name: "default",
					pattern: "{controller=Home}/{action=Index}/{id?}");
			});
		}
	}
}

Modify ErrorViewModel.cs as follows. Most of this code file is modified.

using System;

namespace ProjectName.Models
{
	public class ErrorViewModel
	{
		public string RequestId { get; set; }
		public string Path { get; }
		public int StatusCode { get; }
		public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
		public ErrorViewModel(string path = null)
		{
			StatusCode = 500;
			Path = path;
		}
		public ErrorViewModel(int statusCode, string path = null)
		{
			StatusCode = statusCode;
			Path = path;
		}
	}
}

Modify the Home controller. The modifications are all noted.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using ProjectName.Models;

namespace ProjectName.Controllers
{
	public class HomeController : Controller
	{
		private readonly ILogger<HomeController> _logger;

		public HomeController(ILogger<HomeController> logger)
		{
			_logger = logger;
		}

		public IActionResult Index(string id) // NOTE: THIS LINE MODIFIED
		{
			return View(viewName: id); // NOTE: THIS LINE MODIFIED
		}

		public IActionResult Privacy()
		{
			return View();
		}

		// NOTE: THE FOLLOWING 15 LINES OF CODE ARE ADDED/MODIFIED
		[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] // MUST NOT ALLOW CACHING
		public IActionResult Error()
		{
			var ehpf = HttpContext.Features.Get<Microsoft.AspNetCore.Diagnostics.IExceptionHandlerPathFeature>();
			if (ehpf.Error.Source == "Microsoft.AspNetCore.Mvc.ViewFeatures" && (ehpf.Error.HResult == -2146233079 || ehpf.Error.Message.Contains("was not found")))
				return View(new ErrorViewModel(404, ehpf.Path));
			return View(new ErrorViewModel(ehpf.Path) { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
		}

		[Route("Home/Error/{id}")]
		[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] // MUST NOT ALLOW CACHING
		public IActionResult Error(int id)
		{
			return View(new ErrorViewModel(id));
		}
	}
}

Modify Error.cshtml as follows.

@model ErrorViewModel
@{
    Context.Response.StatusCode = Model.StatusCode;
    switch (Context.Response.StatusCode)
    {
        case 404:
            ViewData["Title"] = "404: Resource Not Found";
            break;
        default: // CAN HANDLE OTHER STATUS CODES
            ViewData["Title"] = Context.Response.StatusCode + ": Error";
            break;
    }
}
<h2>@ViewData["Title"]</h2>

@if (Model.ShowRequestId)
{
    <p><strong>Request ID:</strong> <code>@Model.RequestId</code></p>
}
<pre>@Model.Path</pre>

Modify launchSettings.json to use Production settings. Only two lines of code need to be modified here, from "Development" to "Production".

{
  "iisSettings": {
    "windowsAuthentication": false, 
    "anonymousAuthentication": true, 
    "iisExpress": {
      "applicationUrl": "http://localhost:56789",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    },
    "ProjectName": {
      "commandName": "Project",
      "launchBrowser": true,
      "applicationUrl": "http://localhost:5000",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Production"
      }
    }
  }
}

Modify Index.cshtml as follows.

@{
    ViewData["Title"] = "Home Page";
}

<div class="text-center">
    <h1 class="display-4"><a href="/Home/Index/BogusView">Open "BogusView"</a></h1>
</div>

Now, run the project and try to load a view that does not exist, such as "BogusView" which a link is provided for on the home page. A 404 resource not found error should be shown instead of a 500 internal server error.

Coding Video

https://youtu.be/Ip_1L7Ow-XY