// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNetCore.Diagnostics; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { public static class StatusCodePagesExtensions { /// /// Adds a StatusCodePages middleware with the given options that checks for responses with status codes /// between 400 and 599 that do not have a body. /// /// /// /// public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, StatusCodePagesOptions options) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } return app.UseMiddleware(Options.Create(options)); } /// /// Adds a StatusCodePages middleware with a default response handler that checks for responses with status codes /// between 400 and 599 that do not have a body. /// /// /// public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseMiddleware(); } /// /// Adds a StatusCodePages middleware with the specified handler that checks for responses with status codes /// between 400 and 599 that do not have a body. /// /// /// /// public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, Func handler) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } return app.UseStatusCodePages(new StatusCodePagesOptions { HandleAsync = handler }); } /// /// Adds a StatusCodePages middleware with the specified response body to send. This may include a '{0}' placeholder for the status code. /// The middleware checks for responses with status codes between 400 and 599 that do not have a body. /// /// /// /// /// public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, string contentType, string bodyFormat) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseStatusCodePages(context => { var body = string.Format(CultureInfo.InvariantCulture, bodyFormat, context.HttpContext.Response.StatusCode); context.HttpContext.Response.ContentType = contentType; return context.HttpContext.Response.WriteAsync(body); }); } /// /// Adds a StatusCodePages middleware to the pipeline. Specifies that responses should be handled by redirecting /// with the given location URL template. This may include a '{0}' placeholder for the status code. URLs starting /// with '~' will have PathBase prepended, where any other URL will be used as is. /// /// /// /// public static IApplicationBuilder UseStatusCodePagesWithRedirects(this IApplicationBuilder app, string locationFormat) { if (app == null) { throw new ArgumentNullException(nameof(app)); } if (locationFormat.StartsWith("~")) { locationFormat = locationFormat.Substring(1); return app.UseStatusCodePages(context => { var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode); context.HttpContext.Response.Redirect(context.HttpContext.Request.PathBase + location); return TaskCache.CompletedTask; }); } else { return app.UseStatusCodePages(context => { var location = string.Format(CultureInfo.InvariantCulture, locationFormat, context.HttpContext.Response.StatusCode); context.HttpContext.Response.Redirect(location); return TaskCache.CompletedTask; }); } } /// /// Adds a StatusCodePages middleware to the pipeline with the specified alternate middleware pipeline to execute /// to generate the response body. /// /// /// /// public static IApplicationBuilder UseStatusCodePages(this IApplicationBuilder app, Action configuration) { if (app == null) { throw new ArgumentNullException(nameof(app)); } var builder = app.New(); configuration(builder); var tangent = builder.Build(); return app.UseStatusCodePages(context => tangent(context.HttpContext)); } /// /// Adds a StatusCodePages middleware to the pipeline. Specifies that the response body should be generated by /// re-executing the request pipeline using an alternate path. This path may contain a '{0}' placeholder of the status code. /// /// /// /// /// public static IApplicationBuilder UseStatusCodePagesWithReExecute( this IApplicationBuilder app, string pathFormat, string queryFormat = null) { if (app == null) { throw new ArgumentNullException(nameof(app)); } return app.UseStatusCodePages(async context => { var newPath = new PathString( string.Format(CultureInfo.InvariantCulture, pathFormat, context.HttpContext.Response.StatusCode)); var formatedQueryString = queryFormat == null ? null : string.Format(CultureInfo.InvariantCulture, queryFormat, context.HttpContext.Response.StatusCode); var newQueryString = queryFormat == null ? QueryString.Empty : new QueryString(formatedQueryString); var originalPath = context.HttpContext.Request.Path; var originalQueryString = context.HttpContext.Request.QueryString; // Store the original paths so the app can check it. context.HttpContext.Features.Set(new StatusCodeReExecuteFeature() { OriginalPathBase = context.HttpContext.Request.PathBase.Value, OriginalPath = originalPath.Value, OriginalQueryString = originalQueryString.HasValue ? originalQueryString.Value : null, }); context.HttpContext.Request.Path = newPath; context.HttpContext.Request.QueryString = newQueryString; try { await context.Next(context.HttpContext); } finally { context.HttpContext.Request.QueryString = originalQueryString; context.HttpContext.Request.Path = originalPath; context.HttpContext.Features.Set(null); } }); } } }