ASP.NET Core 1.0 Exception Filters and Resource Filters

This article shows how an exception filter and a resource filter can be used in ASP.NET Core 1.0.

Code: https://github.com/damienbod/AspNet5Filters

2016.07.01: Updated to ASP.NET Core 1.0 RTM
2016.05.17: Updated to ASP.NET Core 1.0 RC2 dotnet
2015.11.18: Updated to ASP.NET Core 1.0 RC1
2015.10.16: Updated to ASP.NET Core 1.0 beta8

An exception filter can be implemented using the IExceptionFilter interface or by implementing the abstract class ExceptionFilter. The ExceptionFilter class implements the IAsyncExceptionFilter, IExceptionFilter and the IOrderedFilter interfaces. In a MVC 6 controller, the ExceptionFilter can be used directly, as a ServiceFilter or as a TypeFilter, on the class itself or applied to single methods. The ExceptionFilter can also be added globally in the Startup class.

A custom resource filter is implemented by using the IResourceFilter interface.
The resource filter method OnResourceExecuting is called before all action filters and exception filters and the resource filter OnResourceExecuted method is called after all action filter and exception filters.

Custom Exception Filter

The following code is an example of a custom filter implemented using the abstract class ExceptionFilterAttribute. The filter does not require a default constructor and can be used to add dependencies which will be added using the MVC 6 IoC.

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace AspNet5.Filters.ExceptionFilters
{
    public class CustomOneLoggingExceptionFilter : ExceptionFilterAttribute
    {
        private readonly ILogger _logger;

        public CustomOneLoggingExceptionFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("CustomOneLoggingExceptionFilter");
        }

        public override void OnException(ExceptionContext context)
        {
            _logger.LogInformation("OnException");
            base.OnException(context);
        }

        //public override Task OnExceptionAsync(ExceptionContext context)
        //{
        //    _logger.LogInformation("OnActionExecuting async");
        //    return base.OnExceptionAsync(context);
        //}
    }
}

Custom Resource Filter

A custom filter can also be implemented using the IResourceFilter interface.

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace AspNet5.Filters.ResourceFilters
{
    public class CustomOneResourceFilter : IResourceFilter
    {

        private readonly ILogger _logger;

        public CustomOneResourceFilter(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("CustomOneResourceFilter");
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            _logger.LogInformation("OnResourceExecuted");
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            _logger.LogInformation("OnResourceExecuting");
        }
    }
}

Startup class code configuration

Global filters can be added using the ConfigureServices method in the Startup class. If the custom filters are used as ServiceType filters in the controller classes, the custom filters need to be added to the MVC 6 IoC.

public void ConfigureServices(IServiceCollection services)
{
	var loggerFactory = new LoggerFactory();
	loggerFactory.AddConsole();
	loggerFactory.AddDebug();

	services.AddMvc(
		config =>
			{
				config.Filters.Add(new GlobalFilter(loggerFactory));
				config.Filters.Add(new GlobalLoggingExceptionFilter(loggerFactory));
			});

	services.AddScoped<ConsoleLogActionOneFilter>();
	services.AddScoped<ConsoleLogActionTwoFilter>();
	services.AddScoped<ClassConsoleLogActionBaseFilter>();
	services.AddScoped<ClassConsoleLogActionOneFilter>();

	services.AddScoped<CustomOneLoggingExceptionFilter>();
	services.AddScoped<CustomTwoLoggingExceptionFilter>();
	services.AddScoped<CustomOneResourceFilter>();   
}

Request with Exception with default filters

The TestExceptionController implements the default exception example.

using System;
using System.Collections.Generic;
using AspNet5.Filters.ActionFilters;
using AspNet5.Filters.ExceptionFilters;
using AspNet5.Filters.ResourceFilters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace AspNet5.Controllers
{
    [Route("api/[controller]")]
    [ServiceFilter(typeof(CustomTwoLoggingExceptionFilter))]
    public class TestExceptionController : Controller
    {
        private readonly ILogger _logger;

        public TestExceptionController(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger("TestExceptionController");
        }

        [HttpGet]
        [ServiceFilter(typeof(CustomOneLoggingExceptionFilter))]
        [ServiceFilter(typeof(CustomOneResourceFilter))]
        [ServiceFilter(typeof(ConsoleLogActionTwoFilter))]
        public IEnumerable<string> Get()
        {
            _logger.LogInformation("Executing Http Get before exception");
            throw new Exception("Yes a great exception");
        }

        [HttpGet("getwithorder")]
        [ServiceFilter(typeof(CustomOneLoggingExceptionFilter), Order = -1)]
        [ServiceFilter(typeof(CustomOneResourceFilter))]
        public IEnumerable<string> GetWithOrderedFiltered()
        {
            _logger.LogInformation("Executing Http Get before exception");
            throw new Exception("Yes a great exception");
        }

        [HttpGet("getcustomexception")]
        [ServiceFilter(typeof(CustomOneLoggingExceptionFilter))]
        [ServiceFilter(typeof(CustomOneResourceFilter))]
        public IEnumerable<string> GetWithCustomException()
        {
            _logger.LogInformation("Executing Http Get before exception");
            throw new CustomException("Yes a great exception");
        }

        [HttpGet("getnoexception")]
        [ServiceFilter(typeof(CustomOneLoggingExceptionFilter))]
        [ServiceFilter(typeof(CustomOneResourceFilter))]
        public IEnumerable<string> GetNoException()
        {
            _logger.LogInformation("Executing Http GetNoException");
            return new string[] { "test data one", "test data two" };
        }

    }
}

When the HTTP Request is executed in the MVC 6 pipeline, the Resource filters OnResourceExecuting is executed first. Then all the action filter OnResourceExecuting are executed. The method itself is then executed. After this, all the action filter OnResourceExecuted are executed. Then the Exception filters are called. (OnException ). The Resource filter OnResourceExecuted is called at the end.

aspnet5_filters_01[1]

The result can also be viewed in in the console (start the application in the console with dnx web):

aspnet5ExceptionFilters_01

The exception filter closest the the action method is executed first, then the controller class filter, and then the global exception filter. The resource filter is executed at the end.

Request with Exception with handled exception

This example stops executing ExceptionFilters if a CustomException is throw. This is done by setting the Exception property of the ExceptionContext to null. The following OnException methods exception filters are not executed.

public override void OnException(ExceptionContext context)
{
	_logger.LogInformation("OnActionExecuting");
	handleCustomException(context);
	base.OnException(context);
}

private void handleCustomException(ExceptionContext context)
{
	if (context.Exception.GetType() == typeof(CustomException))
	{
		_logger.LogInformation("Handling the custom exception here, will not pass  it on to further exception filters");
		context.Exception = null;
	}
}

The example is implemented in the controller as follows:

[HttpGet("getcustomexception")]
[ServiceFilter(typeof(CustomOneLoggingExceptionFilter))]
[ServiceFilter(typeof(CustomOneResourceFilter))]
public IEnumerable<string> GetWithCustomException()
{
	_logger.LogInformation("Executing Http Get before exception");
	throw new CustomException("Yes a great exception");
}

This can be called in a browser with the URL:
http://localhost:5000/api/testexception/getcustomexception

The global exception filter is never called.

aspnet5ExceptionFilters_02

Request with Exception with handled exception ordered

The Exception filter with the highest Order property value is executed first. We can force that the CustomOneLoggingExceptionFilter is executed last by setting the Order property to -1, less than the default Order value.

[HttpGet("getwithorder")]
[ServiceFilter(typeof(CustomOneLoggingExceptionFilter), Order = -1)]
[ServiceFilter(typeof(CustomOneResourceFilter))]
public IEnumerable<string> GetWithOrderedFiltered()
{
	_logger.LogInformation("Executing Http Get before exception");
	throw new Exception("Yes a great exception");
}

This can be tested in the browser with the URL:
http://localhost:5000/api/testexception/getwithorder

The executed order has been changed:

aspnet5ExceptionFilters_03

Links:

https://github.com/aspnet/Mvc/blob/229724c4eab3bf4fc8390deca9af7e451e5caee7/src/Microsoft.AspNet.Mvc.Core/Filters/ExceptionFilterAttribute.cs

https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Abstractions/Filters/IResourceFilter.cs

https://github.com/aspnet/Mvc/tree/dev/src/Microsoft.AspNet.Mvc.Abstractions/Filters

8 comments

  1. […] ASP.NET 5 Exception Filters and Resource Filters – damienbod takes a look building exception and resource filters in ASP.NET 5 […]

  2. Nice writeup. I noticed the ResourceFilter block in your HTTP sequence diagram has a spelling error and both filter names begin with “Action” instead of “Resource”.

    1. Thanks Tim

      I’ll correct this.

      Greetings Damien

  3. […] ASP.NET 5 Exception Filters and Resource Filters […]

  4. […] Une présentation des ExceptionFilter et ResourceFilter d’ASP.NET 5. […]

  5. Bouazza · · Reply

    Hello,
    What’s the different between this approch and new features in diagnostics https://docs.asp.net/en/latest/fundamentals/diagnostics.html#using-the-error-page-during-development
    including in asp.net 5.
    Thanks!

  6. […] Damien Boden is updating his article on Exception Filters and Resource filters for ASP.NET RC2 […]

  7. […] Damien Bowden is updating his article on Exception Filters and Resource filters for ASP.NET RC2 […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: