Skip to content

Instantly share code, notes, and snippets.

@kcargile
Created August 8, 2014 13:32
Show Gist options
  • Save kcargile/7cf2d69f97ef7b9c549c to your computer and use it in GitHub Desktop.
Save kcargile/7cf2d69f97ef7b9c549c to your computer and use it in GitHub Desktop.
Inject Castle Windsor Dependencies into ASP.NET Web API Filter C#
using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
using Castle.Windsor;
using Algorythmic.Web.API.Filters;
namespace Algorythmic.Web.API.Windsor.Filters
{
/// <summary>
/// Custom filter provider for actions that include injected dependencies.
/// </summary>
public class ConfigurableFilterProvider : IFilterProvider
{
private readonly IWindsorContainer _container;
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurableFilterProvider"/> class.
/// </summary>
public ConfigurableFilterProvider()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurableFilterProvider"/> class.
/// </summary>
/// <param name="container">The container.</param>
internal ConfigurableFilterProvider(IWindsorContainer container)
{
_container = container;
}
/// <summary>
/// Returns an enumeration of filters.
/// </summary>
/// <param name="configuration">The HTTP configuration.</param>
/// <param name="actionDescriptor">The action descriptor.</param>
/// <returns>
/// An enumeration of filters.
/// </returns>
public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
List<FilterInfo> filters = new List<FilterInfo>(ConfigureGlobalFilters());
filters.AddRange(ConfigureLocalFilters(actionDescriptor.GetFilters(), FilterScope.Action));
filters.AddRange(ConfigureLocalFilters(actionDescriptor.ControllerDescriptor.GetFilters(), FilterScope.Controller));
return filters;
}
/// <summary>
/// Configures the local filters.
/// </summary>
/// <param name="filters">The filters.</param>
/// <param name="scope">The scope.</param>
/// <returns>A list of filters or an empty list if none were found.</returns>
private IEnumerable<FilterInfo> ConfigureLocalFilters(IEnumerable<IFilter> filters, FilterScope scope)
{
Debug.Assert(filters != null);
foreach (IFilter filter in filters)
{
if (filter is LogUncaughtExceptionFilterAttribute)
{
yield return new FilterInfo(
(LogUncaughtExceptionFilterAttribute)_container.Resolve(filter.GetType()), scope);
}
else
{
yield return new FilterInfo(filter, scope);
}
}
}
/// <summary>
/// Configures global filters, e.g. those that are registered using config.Filters.Add(..).
/// </summary>
/// <returns>A list of filters or an empty list if none were found.</returns>
private IEnumerable<FilterInfo> ConfigureGlobalFilters()
{
foreach (FilterInfo filter in GlobalConfiguration.Configuration.Filters)
{
if (filter.Instance is LogUncaughtExceptionFilterAttribute)
{
yield return new FilterInfo(
(LogUncaughtExceptionFilterAttribute)_container.Resolve(filter.Instance.GetType()), FilterScope.Global);
}
else
{
yield return filter;
}
}
}
}
}
using System.Linq;
using System.Web.Http;
using System.Web.Http.Dispatcher;
using System.Web.Http.Filters;
using Castle.Facilities.Logging;
using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Castle.Windsor.Installer;
using Algorythmic.Web.API.Windsor.Filters;
namespace Algorythmic.Web.API.Windsor
{
/// <summary>
/// Contains startup functionality for use with the dependency container.
/// </summary>
internal static class ContainerContext
{
internal static readonly IWindsorContainer Container = new WindsorContainer();
/// <summary>
/// Configures the specified container.
/// </summary>
public static IWindsorContainer Configure()
{
Container.Install(FromAssembly.This());
Container.Register(Classes.FromThisAssembly().BasedOn<ApiController>().LifestyleTransient());
Container.AddFacility<LoggingFacility>(f => f.LogUsing(LoggerImplementation.Log4net).WithAppConfig());
// setup injection for API controllers
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new CompositionRoot(Container));
// setup injection for filters
ConfigureFilters();
return Container;
}
/// <summary>
/// Configures the filters by injecting any required dependencies.
/// </summary>
private static void ConfigureFilters()
{
// remove default providers; prevents filters from being executed twice per scope
IFilterProvider defaultActionProvider =
GlobalConfiguration.Configuration.Services.GetFilterProviders()
.First(i => i is ActionDescriptorFilterProvider);
IFilterProvider globalConfigurationProvider =
GlobalConfiguration.Configuration.Services.GetFilterProviders()
.First(i => i is ConfigurationFilterProvider);
GlobalConfiguration.Configuration.Services.Remove(typeof(IFilterProvider), defaultActionProvider);
GlobalConfiguration.Configuration.Services.Remove(typeof(IFilterProvider), globalConfigurationProvider);
// add custom provider
GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider), new ConfigurableFilterProvider(Container));
}
}
}
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using Algorythmic.Web.API.Filters;
namespace Algorythmic.Web.API.Windsor.Installers
{
/// <summary>
/// Installs <see cref="LogUncaughtExceptionFilterAttribute"/> dependencies.
/// </summary>
public class ExceptionFilterInstaller : IWindsorInstaller
{
/// <summary>
/// Performs the installation in the <see cref="T:Castle.Windsor.IWindsorContainer" />.
/// </summary>
/// <param name="container">The container.</param>
/// <param name="store">The configuration store.</param>
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<LogUncaughtExceptionFilterAttribute>().LifestyleTransient());
}
}
}
using System;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http;
using System.Web.Http.Filters;
using Castle.Core.Logging;
namespace Algorythmic.Web.API.Filters
{
/// <summary>
/// Filter for handling otherwise uncaught <see cref="Exception"/> occurences.
/// </summary>
public class LogUncaughtExceptionFilterAttribute : ExceptionFilterAttribute
{
private ILogger _logger = NullLogger.Instance;
/// <summary>
/// Gets or sets the logger.
/// </summary>
/// <value>
/// The logger.
/// </value>
[ExcludeFromCodeCoverage]
public ILogger Logger
{
get { return _logger; }
set { _logger = value; }
}
/// <summary>
/// Gets a value that indicates whether multiple filters are allowed.
/// </summary>
/// <returns>true if multiple filters are allowed; otherwise, false.</returns>
public override bool AllowMultiple
{
get { return false; }
}
/// <summary>
/// Raises the exception event.
/// </summary>
/// <param name="context">The context for the action.</param>
public override void OnException(HttpActionExecutedContext context)
{
Exception ex = context.Exception;
if (ex != null)
{
Logger.Fatal(ex.Message, ex);
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
}
}
@ecthgow
Copy link

ecthgow commented Dec 16, 2014

This helped me a lot! Thanks very much!

@tulbox
Copy link

tulbox commented Jan 16, 2017

Many thanks!

And in case it's not immediately obvious, this does not register the actual attribute. You must still do so by registering the filter via placing the attribute on specific controllers and/or actions or globally via

public static WebApiConfig.Register(HttpConfiguration config) {
    ....
    config.Filters.Add(new LogUncaughtExceptionFilterAttribute());
} 

@danny-bos-developer
Copy link

Hi, nice example. I'm trying to get DI working with ActionFilters in webapi. I opened these files in source but the CompositionRoot cannot be resolved for some reason. Is there a reference missing?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment