4

Background: ASP.NET 5 (ASP.NET Core 1.0) MVC 6 application using Dapper and the Repository Pattern

Obviously, like with every other website/app, I'm trying to eliminate most/all of the exceptions that popup in my website.

I implemented an ExceptionFilter in order to catch all unhandled exceptions like this:

public class UnhandledExceptionFilter : ActionFilterAttribute, IExceptionFilter
{
    private readonly IErrorRepo _errorRepo;

    public UnhandledExceptionFilter(IErrorRepo errorRepo)
    {
        _errorRepo = errorRepo;
    }

    public void OnException(ExceptionContext context)
    {
        try
        {
            _errorRepo.SaveException(context.Exception);
        }
        catch { }
    }
}

This works great when the error comes from C# code. But I've purposely put in errors in my razor views (cshtml files) and those are NOT getting caught by this filter.

Is there an additional attribute/interface that I need to inherit in order to catch razor exceptions?

UPDATE:

Here's where the filter attribute is specified, in the startup.cs file in the ConfigureServices method.

    services.AddMvc(options =>
    {
        options.Filters.Add(new UnhandledExceptionFilter(new ErrorRepo(Configuration)));
    });
5
  • 1
    I assume you are using the attribute on an action method? If so, you have to understand how the mvc pipeline works. Views are not rendered until after they are returned by the method (note that you return a View() from the method). Thus, the attribute is no longer in scope when the view is rendered. You need a global ErrorFilter to handle this, which you can do using Will's example Commented Apr 28, 2016 at 18:31
  • @ErikFunkenbusch yes, sorry, see my update to the question... Commented Apr 28, 2016 at 18:42
  • Well, the difference between your example and Will's is that Will is using the ServiceFilterAttribute to allow the DI system to instantiate filter. It should work without the ServiceFilterAttribute, but you might want to remove the ActionFilter interface. Commented Apr 28, 2016 at 18:49
  • Ah, so the actionfilter attribute restricts it to just controller actions.... Commented Apr 28, 2016 at 18:52
  • I don't know, but it's possible that that it's finding the ActionFilter first and not seeing the ExceptionFilter Commented Apr 28, 2016 at 20:03

2 Answers 2

5

The trick to doing this is not in the attribute - it's by adding a middleware provider. Once your middleware is in the pipeline, you'll be able to catch exceptions thrown at any point (so your attribute will no longer be needed).

The Logger

This is the thing that's actually going to log errors. I've copied what I've seen from your IErrorRepo interface, but of course you could modify it to include any of the additional information passed into the Log method below.

public class UnhandledExceptionLogger : ILogger
{
    private readonly IErrorRepo _repo;

    public UnhandledExceptionLogger(IErrorRepo repo)
    {
        _repo = repo;
    }
    public IDisposable BeginScopeImpl(object state) =>
        new NoOpDisposable();

    public bool IsEnabled(LogLevel logLevel) =>
        logLevel == LogLevel.Critical || logLevel == LogLevel.Error;

    public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
    {
        if (IsEnabled(logLevel))
        {
            _repo.SaveException(exception);
        }
    }

    private sealed class NoOpDisposable : IDisposable
    {
        public void Dispose()
        {
        }
    }
}

The Provider

This is the factory that will create the logger. It's only going to be instantiated once, and will create all the loggers when it goes through the pipeline.

public class UnhandledExceptionLoggerProvider : ILoggerProvider
{
    private readonly IErrorRepo _repo;

    public UnhandledExceptionLoggerProvider(IErrorRepo repo)
    {
        _repo = repo;
    }

    public ILogger CreateLogger(string categoryName) =>
        new UnhandledExceptionLogger(_repo);

    public void Dispose()
    {
    }
}

Registering the Provider

Once added to the ILoggerFactory, it will be invoked on each request in the pipeline. Often this is done through a custom extension method, but we've already got a lot of new code so I'll omit that part.

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
    loggerFactory.AddProvider(new UnhandledExceptionLoggerProvider(new ErrorRepo()));
    // the rest
Sign up to request clarification or add additional context in comments.

6 Comments

Cool, I'll work on this tonight, thank you! Looks like I just have to change from my UnhandledExceptionFilter to a ServiceFilterAttribute with a type of UnhandledExceptionFilter
@ganders Yeah, I also added some details about the attribute itself. I realized that mine simply derives from the ExceptionFilterAttribute class.
Hmm, I setup mine exactly the same and it's still not catching razor errors
Any other suggestions?
@ganders Yes, actually! Turns out the attribute wasn't the reason my error logger was working. We don't even need an attribute. I'll update it in a bit.
|
0

I handle all exception (including Razor ones) with the following code:

if (env.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
    app.UseBrowserLink();
}
else
{
    var exceptionHandlingOptions = new ExceptionHandlerOptions()
    {
        ExceptionHandler = ExceptionHandler.OnException // !! this is the key line !!
        // ExceptionHandlingPath = "/Home/Error"
        // according to my tests, the line above is useless when ExceptionHandler is set
        // after OnException completes the user would receive empty page if you don't write to Resonse in handling method
        // alternatively, you may leave ExceptionHandlingPath without assigning ExceptionHandler and call ExceptionHandler.OnException in controller's action instead
        // write me if you need a sample code
    };
    app.UseExceptionHandler(exceptionHandlingOptions);
}

public static class ExceptionHandler
{
    public static async Task OnException(HttpContext context)
    {
        var feature = context.Features.Get<IExceptionHandlerFeature>();

        var exception = feature?.Error;

        if (exception == null) return;

        //TODO: log exception here
    }
}

Do not forget to remove IExceptionFilter as it would handle some of the exceptions as well.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.