| | | 1 | | using Microsoft.AspNetCore.Authorization; |
| | | 2 | | using Microsoft.AspNetCore.Diagnostics; |
| | | 3 | | using Microsoft.AspNetCore.Mvc; |
| | | 4 | | using ProjectTemplate.Web.Constants; |
| | | 5 | | using ProjectTemplate.Web.Diagnostics; |
| | | 6 | | using ProjectTemplate.Web.Models; |
| | | 7 | | |
| | | 8 | | namespace ProjectTemplate.Web.Controllers; |
| | | 9 | | |
| | | 10 | | /// <summary> |
| | | 11 | | /// Controller for handling the home page and error routes. |
| | | 12 | | /// </summary> |
| | | 13 | | /// <param name="logger">The logger instance for the controller.</param> |
| | 29 | 14 | | public partial class HomeController(ILogger<HomeController> logger) : Controller |
| | | 15 | | { |
| | 29 | 16 | | private readonly ILogger<HomeController> _logger = logger; |
| | | 17 | | |
| | | 18 | | /// <summary> |
| | | 19 | | /// Displays an error page for a request routed here by the error-handling pipeline. The response status |
| | | 20 | | /// code comes from the pipeline rather than the route value, and a request that did not arrive through |
| | | 21 | | /// the pipeline is answered with 404 instead of the error page. |
| | | 22 | | /// </summary> |
| | | 23 | | /// <remarks> |
| | | 24 | | /// This route is anonymous by necessity, since it serves errors for unauthenticated requests. Trusting its |
| | | 25 | | /// route value would let any caller choose the response status code — including values outside the valid |
| | | 26 | | /// range, such as <c>/Home/Error/0</c> or <c>/Home/Error/999</c> — and emit a warning-level log entry per |
| | | 27 | | /// request. The presence of <see cref="IStatusCodeReExecuteFeature" /> or |
| | | 28 | | /// <see cref="IExceptionHandlerPathFeature" /> is what distinguishes a re-executed request from a direct one. |
| | | 29 | | /// </remarks> |
| | | 30 | | /// <param name="statusCode">Status code supplied by the status-code re-execute route. Ignored for direct requests.< |
| | | 31 | | /// <returns>The error view for a pipeline-routed request; otherwise a 404 result.</returns> |
| | | 32 | | [AllowAnonymous] |
| | | 33 | | [Route("Home/Error/{statusCode:int?}")] |
| | | 34 | | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] |
| | | 35 | | public IActionResult Error(int? statusCode = null) |
| | | 36 | | { |
| | 29 | 37 | | IExceptionHandlerPathFeature? exceptionFeature = HttpContext.Features.Get<IExceptionHandlerPathFeature>(); |
| | 29 | 38 | | IStatusCodeReExecuteFeature? statusCodeFeature = HttpContext.Features.Get<IStatusCodeReExecuteFeature>(); |
| | | 39 | | |
| | | 40 | | // A request that reaches this route directly carries neither feature. It is answered as not found, so |
| | | 41 | | // the route value cannot select the response status and cannot generate error-page log entries. |
| | 29 | 42 | | if (exceptionFeature is null && statusCodeFeature is null) |
| | | 43 | | { |
| | 6 | 44 | | return NotFound(); |
| | | 45 | | } |
| | | 46 | | |
| | 23 | 47 | | int resolvedStatusCode = ResolveStatusCode(statusCodeFeature, statusCode); |
| | | 48 | | |
| | 23 | 49 | | Response.StatusCode = resolvedStatusCode; |
| | | 50 | | |
| | 23 | 51 | | string originalPath = |
| | 23 | 52 | | exceptionFeature?.Path ?? |
| | 23 | 53 | | statusCodeFeature?.OriginalPath ?? |
| | 23 | 54 | | HttpContext.Request.Path.ToString(); |
| | | 55 | | |
| | 23 | 56 | | string? remoteIpAddress = RequestLoggingPrivacy.GetLoggableRemoteIpAddress(HttpContext); |
| | | 57 | | |
| | 23 | 58 | | string requestId = HttpContext.TraceIdentifier; |
| | | 59 | | |
| | 23 | 60 | | if (exceptionFeature?.Error is not null) |
| | | 61 | | { |
| | 1 | 62 | | LogUnhandledExceptionRoutedToErrorPage( |
| | 1 | 63 | | _logger, |
| | 1 | 64 | | exceptionFeature.Error, |
| | 1 | 65 | | resolvedStatusCode, |
| | 1 | 66 | | originalPath, |
| | 1 | 67 | | remoteIpAddress, |
| | 1 | 68 | | requestId); |
| | | 69 | | } |
| | | 70 | | else |
| | | 71 | | { |
| | 22 | 72 | | LogStatusCodePageRoutedToErrorPage( |
| | 22 | 73 | | _logger, |
| | 22 | 74 | | resolvedStatusCode, |
| | 22 | 75 | | originalPath, |
| | 22 | 76 | | remoteIpAddress, |
| | 22 | 77 | | requestId); |
| | | 78 | | } |
| | | 79 | | |
| | 23 | 80 | | return View(new ErrorViewModel |
| | 23 | 81 | | { |
| | 23 | 82 | | StatusCode = resolvedStatusCode, |
| | 23 | 83 | | RequestId = requestId |
| | 23 | 84 | | }); |
| | | 85 | | } |
| | | 86 | | |
| | | 87 | | /// <summary> |
| | | 88 | | /// Resolves the status code to return, preferring the value the pipeline recorded over the route value. |
| | | 89 | | /// </summary> |
| | | 90 | | /// <param name="statusCodeFeature">The status-code re-execute feature, when the request was re-executed.</param> |
| | | 91 | | /// <param name="routeStatusCode">The status code bound from the route.</param> |
| | | 92 | | /// <returns>A status code inside the range the error page serves.</returns> |
| | | 93 | | private static int ResolveStatusCode(IStatusCodeReExecuteFeature? statusCodeFeature, int? routeStatusCode) |
| | | 94 | | { |
| | 23 | 95 | | int resolvedStatusCode = statusCodeFeature?.OriginalStatusCode |
| | 23 | 96 | | ?? routeStatusCode |
| | 23 | 97 | | ?? StatusCodes.Status500InternalServerError; |
| | | 98 | | |
| | | 99 | | // Status-code pages re-execute for client and server errors only. Anything outside that range means the |
| | | 100 | | // value did not come from the pipeline, so it is not written to the response. |
| | 23 | 101 | | return resolvedStatusCode is < StatusCodes.Status400BadRequest or > 599 |
| | 23 | 102 | | ? StatusCodes.Status500InternalServerError |
| | 23 | 103 | | : resolvedStatusCode; |
| | | 104 | | } |
| | | 105 | | |
| | | 106 | | [LoggerMessage( |
| | | 107 | | EventId = ApplicationLogEventIds.UnhandledExceptionRoutedToErrorPage, |
| | | 108 | | Level = LogLevel.Error, |
| | | 109 | | Message = "Unhandled exception routed to error page. StatusCode: {StatusCode}; OriginalPath: {OriginalPath}; Rem |
| | | 110 | | private static partial void LogUnhandledExceptionRoutedToErrorPage( |
| | | 111 | | ILogger logger, |
| | | 112 | | Exception exception, |
| | | 113 | | int statusCode, |
| | | 114 | | string originalPath, |
| | | 115 | | string? remoteIpAddress, |
| | | 116 | | string traceIdentifier); |
| | | 117 | | |
| | | 118 | | [LoggerMessage( |
| | | 119 | | EventId = ApplicationLogEventIds.StatusCodePageRoutedToErrorPage, |
| | | 120 | | Level = LogLevel.Warning, |
| | | 121 | | Message = "Status code page routed to error page. StatusCode: {StatusCode}; OriginalPath: {OriginalPath}; Remote |
| | | 122 | | private static partial void LogStatusCodePageRoutedToErrorPage( |
| | | 123 | | ILogger logger, |
| | | 124 | | int statusCode, |
| | | 125 | | string originalPath, |
| | | 126 | | string? remoteIpAddress, |
| | | 127 | | string traceIdentifier); |
| | | 128 | | } |