3

I am looking for help we can have a custom Error404 page in ASP.NET 5 MVC6.

The closest I have come is to use

app.UseStatusCodePagesWithReExecute("/Error/{0}");

in the Startup.cs Configure method.

It works fine by calling the "/Error/404" action.

I am looking on ways on how I can send a different 404 response in the event it is a missing JPG/PNG/GIF request.

Any suggestion in the right direction would be of great help.

Vijay Bhatter
  • 53
  • 1
  • 8

3 Answers3

3

If you use the current UseStatusCodePages middleware, it's going to affect every single error. In order to accomplish what you're looking for, you need to create your own Middleware, that needs to be placed right after your default error handling middleware:

//Startup.cs
public void Configure(IApplicationBuilder app)
{
    app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");
    app.UseImageNotFoundMiddlewareWithRedirect("/Home/ImageError");
}

This should get you started: Github - StatusCodePage

Here's an example middleware to simulate maybe what you're looking for:

public class ImageNotFoundMiddleware
{
    private readonly RequestDelegate _next;
    private readonly StatusCodePagesOptions _options;

    public ImageNotFoundMiddleware(RequestDelegate next, IOptions<StatusCodePagesOptions> options)
    {
        _next = next;
        _options = options.Value;
        if (_options.HandleAsync == null)
        {
            throw new ArgumentException("Missing options.HandleAsync implementation.");
        }
    }

    public async Task Invoke(HttpContext context)
    {
        var statusCodeFeature = new StatusCodePagesFeature();
        context.Features.Set<IStatusCodePagesFeature>(statusCodeFeature);

        await _next(context);

        if (!statusCodeFeature.Enabled)
        {
            // Check if the feature is still available because other middleware (such as a web API written in MVC) could
            // have disabled the feature to prevent HTML status code responses from showing up to an API client.
            return;
        }

        // Do nothing if a response body has already been provided or not 404 response
        if (context.Response.HasStarted
            || context.Response.StatusCode != 404
            || context.Response.ContentLength.HasValue
            || !string.IsNullOrEmpty(context.Response.ContentType))
        {
            return;
        }

        // todo => Here is where you'd also add your logic to check for the image 404...
        if (context.Request.Path.Value.EndsWith(".JPG", StringComparison.OrdinalIgnoreCase)
            || context.Request.Path.Value.EndsWith(".PNG", StringComparison.OrdinalIgnoreCase)
            || context.Request.Path.Value.EndsWith(".GIF", StringComparison.OrdinalIgnoreCase)
            )
        {
            var statusCodeContext = new StatusCodeContext(context, _options, _next);
            await _options.HandleAsync(statusCodeContext);
        }
    }
}

// Extension method used to add the middleware to the HTTP request pipeline.
public static class ImageNotFoundMiddlewareExtensions
{
    public static IApplicationBuilder UseImageNotFoundMiddlewareWithRedirect(this IApplicationBuilder app,
        string locationFormat)
    {
        if (app == null)
        {
            throw new ArgumentNullException(nameof(app));
        }

        return app.UseMiddleware<ImageNotFoundMiddleware>(
            Options.Create(
                new StatusCodePagesOptions
                {
                    HandleAsync = context =>
                    {
                        var location = string.Format(
                            CultureInfo.InvariantCulture,
                            locationFormat.StartsWith("~") ? locationFormat.Substring(1) : locationFormat,
                            context.HttpContext.Response.StatusCode);
                        context.HttpContext.Response.Redirect(
                            locationFormat.StartsWith("~")
                                ? context.HttpContext.Request.PathBase + location
                                : location);
                        return Task.FromResult(0);
                    }
                }
            )
        );
    }
}
Ashley Lee
  • 3,810
  • 1
  • 18
  • 26
0

You should be able to use one of the overrides of UseStatusCodePages() methods to achieve this. Use one of these:

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        // app.UseErrorPage(ErrorPageOptions.ShowAll);
        // app.UseStatusCodePages();
        // app.UseStatusCodePages(context => context.HttpContext.Response.SendAsync("Handler, status code: " + context.HttpContext.Response.StatusCode, "text/plain"));
        // app.UseStatusCodePages("text/plain", "Response, status code: {0}");
        // app.UseStatusCodePagesWithRedirects("~/errors/{0}");
        // app.UseStatusCodePagesWithRedirects("/base/errors/{0}");
        // app.UseStatusCodePages(builder => builder.UseWelcomePage());
        // app.UseStatusCodePagesWithReExecute("/errors/{0}");
    }
} 
aguetat
  • 514
  • 4
  • 18
Chrysalis
  • 4,130
  • 2
  • 21
  • 23
0

As an alternative to the custom middleware approach, you can use MapWhen to split the pipeline and handle images separately:

Add the following to the Configure method of Startup.cs (above your non-image middleware):

app.MapWhen(context => context.Request.Path.Value.EndsWith(".png"), appBuilder =>
{
    appBuilder.UseStatusCodePagesWithReExecute("/image-error");
    appBuilder.UseStaticFiles();
});

Obviously, you can change the predicate to match your needs.

Then you can create an action and view to handle the error:

[Route("image-error")]
public IActionResult ImageError(int code)
{
    return View();
}

You can find more information in a post I wrote about conditional middleware

Paul Hiles
  • 9,558
  • 7
  • 51
  • 76