Skip to content

Commit 65f6414

Browse files
committed
Merge branch 'improvement/ErrorController-static-files' into develop
Customer error pages for HTTP status codes are intended to provide a friendly experience for human audiences. This requires additional processing and bandwidth, however, as the `ErrorController` must find the appropriate `Topic`, generate the corresponding view model, execute the view, and return the markup. That's a lot of overhead for content that _doesn't_ have a human audience. Most notably, web resources—such as scripts, style sheets, images, fonts, &c.—that return a 404 error won't typically be seen by human audiences, outside of developers, since the errors are swallowed by the browser, or simply result in loss of functionality (such as a broken image). In these cases, it doesn't make sense to process and deliver a custom HTTP error. To facilitate this, this update introduces a new `includeStaticFiles` parameter to both the `UseTopicErrors()` extension method as well as the `ErrorController`'s `HttpAsync()` action. This isn't the only way to configure a route to the error controller, but it's the one that exposes custom control over how it behaves, and will be the preferred option for customers requiring that level of flexibility. This is an optional parameter, and defaults to `true`—meaning static files will return the normal error page experience. While that's an intuitive default, however, we expect most implementers will want to disable this default, and thus the `Host` boilerplate code has been updated to include this configuration option. This satisfies Feature #101.
2 parents e1abb1d + 6fbe387 commit 65f6414

6 files changed

Lines changed: 26 additions & 8 deletions

File tree

OnTopic.AspNetCore.Mvc.Host/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@
5353
/*------------------------------------------------------------------------------------------------------------------------------
5454
| Configure: Error Pages
5555
\-----------------------------------------------------------------------------------------------------------------------------*/
56-
if (!app.Environment.IsDevelopment()) {
5756
app.UseStatusCodePagesWithReExecute("/Error/{0}/");
57+
if (!app.Environment.IsDevelopment()) {
5858
app.UseExceptionHandler("/Error/500/");
5959
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
6060
app.UseHsts();
@@ -77,7 +77,7 @@
7777
app.MapDefaultAreaControllerRoute(); // {area:exists}/{controller}/{action=Index}/{id?}
7878
app.MapTopicAreaRoute(); // {area:exists}/{**path}
7979

80-
app.MapTopicErrors(rootTopic: "Error"); // Error/{statusCode}
80+
app.MapTopicErrors(includeStaticFiles: false); // Error/{statusCode}
8181
app.MapDefaultControllerRoute(); // {controller=Home}/{action=Index}/{id?}
8282
app.MapTopicRoute(rootTopic: "Web"); // Web/{**path}
8383
app.MapTopicRoute(rootTopic: "Error"); // Error/{**path}

OnTopic.AspNetCore.Mvc.IntegrationTests.Host/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public static void Configure(IApplicationBuilder app) {
9595
| Configure: MVC
9696
\-----------------------------------------------------------------------------------------------------------------------*/
9797
app.UseEndpoints(endpoints => {
98-
endpoints.MapTopicErrors();
98+
endpoints.MapTopicErrors(includeStaticFiles: false);
9999
endpoints.MapDefaultAreaControllerRoute();
100100
endpoints.MapDefaultControllerRoute();
101101
endpoints.MapImplicitAreaControllerRoute();

OnTopic.AspNetCore.Mvc.IntegrationTests/ServiceCollectionExtensionsTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public async Task MapTopicSitemap_RespondsToRequest() {
9696
[InlineData("/MissingPage/", HttpStatusCode.NotFound, "400")]
9797
[InlineData("/Web/MissingPage/", HttpStatusCode.NotFound, "400")]
9898
[InlineData("/Web/Container/", HttpStatusCode.Forbidden, "400")]
99+
[InlineData("/Scripts/ECMAScript.js", HttpStatusCode.NotFound, "The resource requested could not found.")]
99100
public async Task UseStatusCodePages_ReturnsExpectedStatusCode(string path, HttpStatusCode statusCode, string expectedContent) {
100101

101102
var client = _factory.CreateClient();
@@ -104,7 +105,6 @@ public async Task UseStatusCodePages_ReturnsExpectedStatusCode(string path, Http
104105
var actualContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
105106

106107
Assert.Equal(statusCode, response.StatusCode);
107-
Assert.Equal("text/html; charset=utf-8", response.Content.Headers.ContentType?.ToString());
108108
Assert.Equal(expectedContent, actualContent);
109109

110110
}

OnTopic.AspNetCore.Mvc/Controllers/ErrorController.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
| Project Topics Library
55
\=============================================================================================================================*/
66
using Microsoft.AspNetCore.Builder;
7+
using Microsoft.AspNetCore.Diagnostics;
78
using OnTopic.Mapping;
89

910
namespace OnTopic.AspNetCore.Mvc.Controllers {
@@ -48,7 +49,17 @@ ITopicMappingService topicMappingService
4849
/// content available.
4950
/// </summary>
5051
/// <returns>A view associated with the requested current <paramref name="statusCode"/>.</returns>
51-
public async virtual Task<IActionResult> HttpAsync([FromRoute(Name="id")] int statusCode) {
52+
public async virtual Task<IActionResult> HttpAsync([FromRoute(Name="id")] int statusCode, bool includeStaticFiles = true) {
53+
54+
/*------------------------------------------------------------------------------------------------------------------------
55+
| Bypass for resources
56+
\-----------------------------------------------------------------------------------------------------------------------*/
57+
if (!includeStaticFiles) {
58+
var feature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
59+
if (feature?.OriginalPath.Contains('.', StringComparison.Ordinal)?? false) {
60+
return Content("The resource requested could not found.");
61+
}
62+
}
5263

5364
/*------------------------------------------------------------------------------------------------------------------------
5465
| Identify base path

OnTopic.AspNetCore.Mvc/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public class Startup {
187187
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
188188
app.UseEndpoints(endpoints => {
189189
endpoints.MapTopicErrors(); // Error/{errorCode}
190-
endpoints.MapTopicErrors("Errors"); // Errors/{errorCode}
190+
endpoints.MapTopicErrors("Errors", false); // Errors/{errorCode}; disables includeStaticFiles
191191
endpoints.MapDefaultControllerRoute(); // Error/Http/{errorCode}
192192
endpoints.MapTopicRoute("Error"); // Error/{path}; e.g., Error/Unauthorized
193193
}

OnTopic.AspNetCore.Mvc/ServiceCollectionExtensions.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,11 +221,18 @@ public static void MapImplicitAreaControllerRoute(this IEndpointRouteBuilder rou
221221
/// StatusCodePagesExtensions.UseStatusCodePages(IApplicationBuilder)"/>, by providing a route for capturing the <c>
222222
/// errorCode</c>.
223223
/// </remarks>
224-
public static ControllerActionEndpointConventionBuilder MapTopicErrors(this IEndpointRouteBuilder routes, string rootTopic = "Error") =>
224+
/// <param name="routes">The <see cref="IEndpointRouteBuilder"/> this route is being added to.</param>
225+
/// <param name="rootTopic">The name of the root topic that the route should be mapped to. Defaults to <c>Error</c>.</param>
226+
/// <param name="includeStaticFiles">Determines if static resources should be covered. Defaults to <c>true</c>.</param>
227+
public static ControllerActionEndpointConventionBuilder MapTopicErrors(
228+
this IEndpointRouteBuilder routes,
229+
string rootTopic = "Error",
230+
bool includeStaticFiles = true
231+
) =>
225232
routes.MapControllerRoute(
226233
name: "TopicError",
227234
pattern: $"{rootTopic}/{{id:int}}/",
228-
defaults: new { controller = "Error", action = "Http", rootTopic }
235+
defaults: new { controller = "Error", action = "Http", rootTopic, includeStaticFiles }
229236
);
230237

231238
/*==========================================================================================================================

0 commit comments

Comments
 (0)