2

I am developing a web app using MVC4 with Autofac. I have a global exception filter in which I'm injecting a logger service, so I'm initializing it in App_Start like this:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(DependencyResolver.Current.GetService<IExceptionFilter>());
    }

This is the general layout of the filter: public class ErrorHandlerAttribute : HandleErrorAttribute { private readonly ILoggerService logger;

    public ErrorHandlerAttribute(ILoggerService logger)
    {
        this.logger = logger;
    }

    public override void OnException(ExceptionContext filterContext)
    {
        //dostuff
    }

    public void LogError(ExceptionContext context)
    {
        try
        {
            logger.Error(context.Exception.Message, context.Exception);
        }
        catch (Exception) { }
    }
}

If I weren't using Autofac, I would've had something like this:

public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        filters.Add(new ErrorHandlerAttribute());

        filters.Add(new ErrorHandlerAttribute
        {
            View = "UnauthorizedException",
            ExceptionType = typeof(UnauthorizedAccessException)
        });

        filters.Add(new ErrorHandlerAttribute
        {
            View = "PageNotFound",
            ExceptionType = typeof(NotImplementedException)
        });
    }

ErrorHandlerAttribute is my custom exception filter, derived from MVC's HandleErrorAttribute.

I would like to be able to keep the ability to redirect to custom error pages, while using Autofac's injection and a single filter (since I built it so it can handle any exception). Unfortunately, I can't seem to find any way to do this, despite scouring the web and other forums for possible solutions. I've tried a lot of configuration changes, different registrations, collection resolving etc.

The way I would like it to work would be similar to this:

    builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>().InstancePerHttpRequest();
    builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>()
        .WithProperties(new List<NamedParameter>() { new NamedParameter("ExceptionType", typeof(UnauthorizedAccessException)), new NamedParameter("View", "UnauthorizedAccess") })
        .InstancePerHttpRequest();
    builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>()
        .WithProperties(new List<NamedParameter>() { new NamedParameter("ExceptionType", typeof(NotImplementedException)), new NamedParameter("View", "UnderConstruction") })
        .InstancePerHttpRequest();
    builder.RegisterFilterProvider();

    public static void RegisterGlobalFilters(GlobalFilterCollection filters)
    {
        DependencyResolver.Current.GetServices<IExceptionFilter>().ForEach(f => filters.Add(f));
    }

Surprisingly, this compiles and runs, but all 3 IExceptionFilter instances are normal, defaulted ErrorHandlerAttribute (with View="Error" and ExceptionType=typeof(object)).

I am aware of the fact that Autofac takes the last registration of a service as default, and I have tried commenting two out of three registrations, as well as using PreserveExistingDefaults, still all my exception filters come with default values.

Have I misunderstood the WithProperties extension method or is there another similar way to implement what I want?

Edit 1:

Thanks for Alex's suggestion, I solved it by using NamedPropertyParameter and switching the order of the statements:

builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>()
    .WithProperties(new List<NamedPropertyParameter> { new NamedPropertyParameter("ExceptionType", typeof(UnauthorizedAccessException)), new NamedPropertyParameter("View", "UnauthorizedAccess") })
    .InstancePerHttpRequest();
builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>()
    .WithProperties(new List<NamedPropertyParameter> { new NamedPropertyParameter("ExceptionType", typeof(NotImplementedException)), new NamedPropertyParameter("View", "UnderConstruction") })
    .InstancePerHttpRequest();
builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>().InstancePerHttpRequest();
valentin
  • 667
  • 2
  • 12
  • 19

1 Answers1

5

You need to use NamedPropertyParameter instead of NamedParameter.

builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>()
    .SingleInstance();
builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>()
    .WithProperties(new List<NamedPropertyParameter> { new NamedPropertyParameter("ExceptionType", typeof(UnauthorizedAccessException)), new NamedPropertyParameter("View", "UnauthorizedAccess") })
    .SingleInstance();
builder.RegisterType<ErrorHandlerAttribute>().As<IExceptionFilter>()
    .WithProperties(new List<NamedPropertyParameter> { new NamedPropertyParameter("ExceptionType", typeof(NotImplementedException)), new NamedPropertyParameter("View", "UnderConstruction") })
    .SingleInstance();

You also may as well register the global filters as SingleInstance because they are resolved and then added directly to the filter collection. MVC will not request an instance of these filters per HTTP request. It will just use the instances you added to the collection.

Alex Meyer-Gleaves
  • 3,821
  • 21
  • 11
  • Hi Alex, thank you for the answer. I had tried the .SingleInstance() before, and it always fails at the global.asax phase with a DependencyResolutionException. It passes the runtime check only for InstancePerHttpRequest or InstancePerLifetimeScope. This brings me one step closer to solving the problem, but it's still not enough. See Edit 1. – valentin Jul 05 '13 at 09:25
  • No need for an edit, it seems. This works as long as you switch the order of the registrations in Autofac's configuration, such that the most generic filter registration goes last (though still with InstancePerHttpRequest). Since this is basically what I wanted, I'm marking the answer as accepted. Thank you, Alex. – valentin Jul 05 '13 at 09:37