I have the following ASP.NET Core integration test using a custom WebApplicationFactory
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint>
where TEntryPoint : class
{
public CustomWebApplicationFactory()
{
this.ClientOptions.AllowAutoRedirect = false;
this.ClientOptions.BaseAddress = new Uri("https://localhost");
}
public ApplicationOptions ApplicationOptions { get; private set; }
public Mock<IClockService> ClockServiceMock { get; private set; }
public void VerifyAllMocks() => Mock.VerifyAll(this.ClockServiceMock);
protected override TestServer CreateServer(IWebHostBuilder builder)
{
this.ClockServiceMock = new Mock<IClockService>(MockBehavior.Strict);
builder
.UseEnvironment("Testing")
.ConfigureTestServices(
services =>
{
services.AddSingleton(this.ClockServiceMock.Object);
});
var testServer = base.CreateServer(builder);
using (var serviceScope = testServer.Host.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
this.ApplicationOptions = serviceProvider.GetRequiredService<IOptions<ApplicationOptions>>().Value;
}
return testServer;
}
}
which looks like it should work but the problem is that the ConfigureTestServices method is never being called, so my mock is never registered with the IoC container. You can find the full source code here.
public class FooControllerTest : IClassFixture<CustomWebApplicationFactory<Startup>>, IDisposable
{
private readonly HttpClient client;
private readonly CustomWebApplicationFactory<Startup> factory;
private readonly Mock<IClockService> clockServiceMock;
public FooControllerTest(CustomWebApplicationFactory<Startup> factory)
{
this.factory = factory;
this.client = factory.CreateClient();
this.clockServiceMock = this.factory.ClockServiceMock;
}
[Fact]
public async Task Delete_FooFound_Returns204NoContent()
{
this.clockServiceMock.SetupGet(x => x.UtcNow).ReturnsAsync(new DateTimeOffset.UtcNow);
var response = await this.client.DeleteAsync("/foo/1");
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}
public void Dispose() => this.factory.VerifyAllMocks();
}
I've blogged about ASP.NET Core Integration Testing & Mocking using Moq. It's not simple and requires some setup but I hope it helps someone out. Here is the basic code you need using ASP.NET Core 3.1:
Startup
The ConfigureServices and Configure methods in your applications Startup class must be virtual. This is so that we can iherit from this class in our tests and replace production versions of certain services with mock versions.
public class Startup
{
private readonly IConfiguration configuration;
private readonly IWebHostingEnvironment webHostingEnvironment;
public Startup(IConfiguration configuration, IWebHostingEnvironment webHostingEnvironment)
{
this.configuration = configuration;
this.webHostingEnvironment = webHostingEnvironment;
}
public virtual void ConfigureServices(IServiceCollection services) =>
...
public virtual void Configure(IApplicationBuilder application) =>
...
}
TestStartup
In your test project, override the Startup class with one that registers the mock and the mock object with IoC.
public class TestStartup : Startup
{
private readonly Mock<IClockService> clockServiceMock;
public TestStartup(IConfiguration configuration, IHostingEnvironment hostingEnvironment)
: base(configuration, hostingEnvironment)
{
this.clockServiceMock = new Mock<IClockService>(MockBehavior.Strict);
}
public override void ConfigureServices(IServiceCollection services)
{
services
.AddSingleton(this.clockServiceMock);
base.ConfigureServices(services);
services
.AddSingleton(this.clockServiceMock.Object);
}
}
CustomWebApplicationFactory
In your test project, write a custom WebApplicationFactory that configures the HttpClient and resolves the mocks from the TestStartup, then exposes them as properties, ready for our integration test to consume them. Note that I'm also changing the environment to Testing and telling it to use the TestStartup class for startup.
Note also that I've implemented IDisposable's `Dispose method to verify all of my strict mocks. This means I don't need to verify any mocks manually myself. Verification of all mock setups happens automatically when xUnit is disposing the test class.
public class CustomWebApplicationFactory<TEntryPoint> : WebApplicationFactory<TEntryPoint>
where TEntryPoint : class
{
public CustomWebApplicationFactory()
{
this.ClientOptions.AllowAutoRedirect = false;
this.ClientOptions.BaseAddress = new Uri("https://localhost");
}
public ApplicationOptions ApplicationOptions { get; private set; }
public Mock<IClockService> ClockServiceMock { get; private set; }
public void VerifyAllMocks() => Mock.VerifyAll(this.ClockServiceMock);
protected override void ConfigureClient(HttpClient client)
{
using (var serviceScope = this.Services.CreateScope())
{
var serviceProvider = serviceScope.ServiceProvider;
this.ApplicationOptions = serviceProvider.GetRequiredService<IOptions<ApplicationOptions>>().Value;
this.ClockServiceMock = serviceProvider.GetRequiredService<Mock<IClockService>>();
}
base.ConfigureClient(client);
}
protected override void ConfigureWebHost(IWebHostBuilder builder) =>
builder
.UseEnvironment("Testing")
.UseStartup<TestStartup>();
protected override void Dispose(bool disposing)
{
if (disposing)
{
this.VerifyAllMocks();
}
base.Dispose(disposing);
}
}
Integration Tests
I'm using xUnit to write my tests. Note that the generic type passed to CustomWebApplicationFactory is Startup and not TestStartup. This generic type is used to find the location of your application project on disk and not to start the application.
I setup a mock in my test and I've implemented IDisposable to verify all mocks for all my tests at the end but you can do this step in the test method itself if you like.
Note also, that I'm not using xUnit's IClassFixture to only boot up the application once as the ASP.NET Core documentation tells you to do. If I did so, I'd have to reset the mocks between each test and also you would only be able to run the integration tests serially one at a time. With the method below, each test is fully isolated and they can be run in parallel. This uses up more CPU and each test takes longer to execute but I think it's worth it.
public class FooControllerTest : CustomWebApplicationFactory<Startup>
{
private readonly HttpClient client;
private readonly Mock<IClockService> clockServiceMock;
public FooControllerTest()
{
this.client = this.CreateClient();
this.clockServiceMock = this.ClockServiceMock;
}
[Fact]
public async Task GetFoo_Default_Returns200OK()
{
this.clockServiceMock.Setup(x => x.UtcNow).ReturnsAsync(new DateTimeOffset(2000, 1, 1));
var response = await this.client.GetAsync("/foo");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
}
xunit.runner.json
I'm using xUnit. We need to turn off shadown copying, so any separate files like appsettings.json are placed in the right place beside the application DLL file. This ensures that our application running in an integration test can still read the appsettings.json file.
{
"shadowCopy": false
}
appsettings.Testing.json
Should you have configuration that you want to change just for your integration tests, you can add a appsettings.Testing.json file into your application. This configuration file will only be read in our integration tests because we set the environment name to 'Testing'.
The best way to handle this is to factor out parts of your Startup that will need to be substituted during test. For example, instead of calling services.AddDbContext<MyContext>(...); directly in ConfigureServices, create a virtual private method like:
protected virtual void ConfigureDatabase(IServiceCollection services)
{
services.AddDbContext<MyContext>(...);
}
Then, in your test project, create a class like TestStartup which derives from your SUT's Startup class. Then, you can override these virtual methods to sub in your test services, mocks, etc.
Finally, just do something like:
builder
.UseEnvironment("Testing")
.UseStartup<TestStartup>();
You should create a fake startup:
public class FakeStartup : Startup
{
public FakeStartup(IConfiguration configuration)
: base(configuration)
{
}
public override void ConfigureServices(IServiceCollection services)
{
base.ConfigureServices(services);
// Your fake go here
//services.AddScoped<IService, FakeService>();
}
}
Then use it with IClassFixture<CustomWebApplicationFactory<FakeStartup>>.
Make sure to make your original ConfigureServices method virtual.
Related
I am working with Asp.Net Core application. I have two classes namely Online and Offline. I have created interface and defined the methods in these two classes. Based on the need I have to connect to anyone of these two classes.
Previously when I worked in Asp.Net MVC, I have used unity container and Service Locator to specify the class name in XML file for invoking the class dynamically (between online and offline).
Now I want to implement the same with Asp.Net core. But I am not sure how to specify the class name outside for method invocation. Kindly help.
Thanks
In .net core dependency injection is in built. You don't need unity or any other any more.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-5.0
You can achieve what you want by using a little tweak.
//// classes
public interface IFileUploadContentProcess
{
IEnumerable<StoreOrder> ProcessUploads(IFormFile file);
}
public class ProcessExcelFiles : IFileUploadContentProcess
{
public IEnumerable<StoreOrder> ProcessUploads(IFormFile file)
{
throw new NotImplementedException();
}
}
public class ProcessCsvFiles : IFileUploadContentProcess
{
public IEnumerable<StoreOrder> ProcessUploads(IFormFile file)
{
throw new NotImplementedException();
}
}
//// register it
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddTransient<IStoreOrderService, StoreOrderService>();
services.AddTransient<ProcessExcelFiles>();
services.AddTransient<ProcessCsvFiles>();
// Add resolvers for different sources here
services.AddTransient<Func<string, IFileUploadContentProcess>>(serviceProvider => key =>
{
return key switch
{
"xlsx" => serviceProvider.GetService<ProcessExcelFiles>(),
_ => serviceProvider.GetService<ProcessCsvFiles>(),
};
});
}
//use it
public class StoreOrderService : IStoreOrderService
{
private readonly Func<string, IFileUploadContentProcess> _fileUploadContentProcess;
public StoreOrderService(Func<string, IFileUploadContentProcess> fileUploadContentProcess)
{
_fileUploadContentProcess = fileUploadContentProcess;
}
public async Task<IEnumerable<StoreOrder>> UploadStoreOrdersAsync(IFormFile file)
{
//// passing csv to process csv type(default), if xlsx, pass xlsx
var records = _fileUploadContentProcess("csv").ProcessUploads(file);
return records;
}
}
After lot of brainstroming, I found the below solution
Create a class for ServiceLocator
public class ServiceLocator
{
private ServiceProvider _currentServiceProvider;
private static ServiceProvider _serviceProvider;
public ServiceLocator(ServiceProvider currentServiceProvider)
{
_currentServiceProvider = currentServiceProvider;
}
public static ServiceLocator Current
{
get
{
return new ServiceLocator(_serviceProvider);
}
}
public static void SetLocatorProvider(ServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public object GetInstance(Type serviceType)
{
return _currentServiceProvider.GetService(serviceType);
}
public TService GetInstance<TService>()
{
return _currentServiceProvider.GetService<TService>();
}
}
Step 2: Create interface and inherit in the classes and define the interface methods
Step 3: Define class name in appSettings.json and read the values in startup.cs
public void ConfigureServices(IServiceCollection services)
{
//reading from appSettings.json
string strClassName = Configuration["DependencyInjection:className"];
if (strClassName == "OnlineData")
services.AddTransient<<<InterfaceName>>, <<OnlineClassName>>>();
if (strClassName == "OfflineData")
services.AddTransient<<<InterfaceName>>, <<OfflineClassName>>>();
}
Step 4: Create object for the dynamic class inside controller/action method
InterfaceNamemyService = ServiceLocator.Current.GetInstance<>();
My goal is to set a username string based on the environment I'll be working on that must be:
an arbitrary string for the development and staging environment
the HttpContext.User.Identity.Name in production.
This is because I have to be able to simulate different kind of users and I achieve this by calling the FindByIdAsync method on my custom implementation of UserIdentity using this username string as a parameter, like this:
public class HomeController : Controller
{
UserManager<AppUser> userManager;
AppUser connectedUser;
public HomeController(UserManager<AppUser> usrMgr, IContextUser ctxUser)
{
connectedUser = usrMgr.FindByNameAsync(ctxUser.ContextUserId).Result;
}
}
I started creating three appsettings.{environment}.json file for the three usual development, staging and production environments; development and staging .json files both have this configuration:
...
"Data": {
...
"ConnectedUser" : "__ADMIN"
}
...
while the production environment configuration file doesn't have this key.
I have created a simple interface
public interface IContextUser
{
public string ContextUserId { get; }
}
and its implementation:
public class ContextUser : IContextUser
{
string contextUser;
IHttpContextAccessor contextAccessor;
public ContextUser(IHttpContextAccessor ctxAccessor, string ctxUser = null)
{
contextUser = ctxUser;
contextAccessor = ctxAccessor;
}
public string ContextUserId => contextUser ?? contextAccessor.HttpContext.User.Identity.Name;
}
Now, I thought of simply configuring the ConfigureServices method in the Startup class:
public void ConfigureServices(IServiceCollection services)
{
// --- add other services --- //
string ctxUser = Configuration["Data:ConnectedUser"];
services.AddSingleton(service => new ContextUser( ??? , ctxUser ));
}
but it needs an IHttpContextAccessor object, that seems unavailable at this stage of the application. How can I solve this issue?
The HttpContextAccessor makes use of a static AsyncLocal<T> property under the covers, which means that any HttpContextAccessor implementation will access the same data. This means you can simply do the following:
services.AddSingleton(c => new ContextUser(new HttpContextAccessor(), ctxUser));
// Don't forget to call this; otherwise the HttpContext property will be
// null on production.
services.AddHttpContextAccessor();
If you find this too implicit, or don't the HttpContextAccessor implementation from breaking in the future, you can also do the following:
var accessor = new HttpContextAccessor();
services.AddSingleton<IHttpContextAccessor>(accessor);
services.AddSingleton(c => new ContextUser(accessor, ctxUser));
Or you can "pull out" the registered instance out of the ServiceCollection class:
services.AddHttpContextAccessor();
var accessor = (IHttpContextAccessor)services.Last(
s => s.ServiceType == typeof(IHttpContextAccessor)).ImplementationInstance;
services.AddSingleton(c => new ContextUser(accessor, ctxUser));
What I find a more pleasant solution, however, especially from a design perspective, is to split the ContextUser class; it currently seems to implement two different solutions. You can split those:
public sealed class HttpContextContextUser : IContextUser
{
private readonly IHttpContextAccessor accessor;
public HttpContextContextUser(IHttpContextAccessor accessor) =>
this.accessor = accessor ?? throw new ArgumentNullException("accessor");
public string ContextUserId => this.accessor.HttpContext.User.Identity.Name;
}
public sealed class FixedContextUser : IContextUser
{
public FixedContextUser(string userId) =>
this.ContextUserId = userId ?? throw new ArgumentNullException("userId");
public string ContextUserId { get; }
}
Now, depending on the environment you're running in, you register either one of them:
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpContextAccessor();
if (this.Configuration.IsProduction())
{
services.AddSingleton<IContextUser, HttpContextContextUser>();
}
else
{
string ctxUser = Configuration["Data:ConnectedUser"];
services.AddSingleton<IContextUser>(new FixedContextUser(ctxUser));
}
}
I really like the pattern where I can configure a service through an option class without having to create it, but I can't find an example of how to write an extension method that allows me to use that same pattern such as the one below that exists for registering a DbContext.
services.AddDbContext<MyDbContext>(options => options.EnableDetailedErrors());
I can see the method signature uses an action method, but I can't seem to find the extension class in GitHub for ASP.NET Core that shows me how to write an extension method using that type of option builder pattern.
For example, take the following service code. How would I write the extension method so that I could configure the options during service registration.
public void ConfigureServices(IServiceCollection services)
{
services.AddMyService(options => options.SomeSetting = true);
}
public interface IMyService
{
void DoSomething();
}
public class MyService : IMyService
{
private readonly MyServiceOptions _options;
public MyService(IOptions<MyServiceOptions> options)
{
_options = options.Value;
}
public void DoSomething()
{
Console.WriteLine(_options.SomeSetting);
}
}
public static class MyServiceExtensions
{
// How would I write this extension method so that I could configure it with options overload
public static IServiceCollection AddMyService(this IServiceCollection services, Action<MyServiceOptions> configure)
{
services.AddSingleton<IMyService, MyService>();
return services;
}
}
ASP.NET Core provides this mechanism with the IConfigureOptions
interface. You implement this interface in a configuration class and
use it to configure the IOptions object in any way you need.
It's as easy as:
public class MyServiceConfiguration : IConfigureOptions<MyServiceOptions>
{
private MyServiceOptions _options;
public MyServiceConfiguration(IOptions<MyServiceOptions> options)
{
_options = options.Value;
}
public void Configure(MyServiceOptions options)
{
options.SomeSetting = _options.SomeSetting;
options.SomeOtherSetting = _options.SomeOtherSetting;
}
}
All that remains is to register this implementation in the DI container.:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyServiceOptions>(options => options.SomeOtherSetting = true);
services.AddSingleton<IMyService, MyService>();
}
With this configuration, when IOptions is injected into your service, the MyServiceOptions object will be configured by the ConfigureMyServiceOptions class.
Be careful! The ConfigureMyServiceOptions object is registered as a singleton,
so it will capture any injected services of scoped or transient lifetimes.
I have a C# Asp.Net Core (1.x) project, implementing a web REST API, and its related integration test project, where before any test there's a setup similar to:
// ...
IWebHostBuilder webHostBuilder = GetWebHostBuilderSimilarToRealOne()
.UseStartup<MyTestStartup>();
TestServer server = new TestServer(webHostBuilder);
server.BaseAddress = new Uri("http://localhost:5000");
HttpClient client = server.CreateClient();
// ...
During tests, the client is used to send HTTP requests to web API (the system under test) and retrieve responses.
Within actual system under test there's some component extracting sender IP address from each request, as in:
HttpContext httpContext = ReceiveHttpContextDuringAuthentication();
// edge cases omitted for brevity
string remoteIpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString()
Now during integration tests this bit of code fails to find an IP address, as RemoteIpAddress is always null.
Is there a way to set that to some known value from within test code? I searched here on SO but could not find anything similar. TA
You can write middleware to set custom IP Address since this property is writable:
public class FakeRemoteIpAddressMiddleware
{
private readonly RequestDelegate next;
private readonly IPAddress fakeIpAddress = IPAddress.Parse("127.168.1.32");
public FakeRemoteIpAddressMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Connection.RemoteIpAddress = fakeIpAddress;
await this.next(httpContext);
}
}
Then you can create StartupStub class like this:
public class StartupStub : Startup
{
public StartupStub(IConfiguration configuration) : base(configuration)
{
}
public override void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
base.Configure(app, env);
}
}
And use it to create a TestServer:
new TestServer(new WebHostBuilder().UseStartup<StartupStub>());
As per this answer in ASP.NET Core, is there any way to set up middleware from Program.cs?
It's also possible to configure the middleware from ConfigureServices, which allows you to create a custom WebApplicationFactory without the need for a StartupStub class:
public class CustomWebApplicationFactory : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return WebHost
.CreateDefaultBuilder<Startup>(new string[0])
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
public class CustomStartupFilter : IStartupFilter
{
public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
{
return app =>
{
app.UseMiddleware<FakeRemoteIpAddressMiddleware>();
next(app);
};
}
}
Using WebHost.CreateDefaultBuilder can mess up with your app configuration.
And there's no need to change Product code just to accommodate for testing, unless absolutely necessary.
The simplest way to add your own middleware, without overriding Startup class methods, is to add the middleware through a IStartupFilterā as suggested by Elliott's answer.
But instead of using WebHost.CreateDefaultBuilder, just use
base.CreateWebHostBuilder().ConfigureServices...
public class CustomWAF : WebApplicationFactory<Startup>
{
protected override IWebHostBuilder CreateWebHostBuilder()
{
return base.CreateWebHostBuilder().ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
}
I used Elliott's answer within an ASP.NET Core 2.2 project. However, updating to ASP.NET 5.0, I had to replace the override of CreateWebHostBuilder with the below override of CreateHostBuilder:
protected override IHostBuilder CreateHostBuilder()
{
return Host
.CreateDefaultBuilder()
.ConfigureWebHostDefaults(builder =>
{
builder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddSingleton<IStartupFilter, CustomStartupFilter>();
});
}
I am trying to use Ninject to inject an EventLogger instance into a custom ExceptionFilterAttribute. Whenever I run the code, the EventLogger instance is null. I have implemented an IFilterProvider to resolve dependencies in a similar manner for my custom AuthorizationFilterAttribute, and that works fine. Any ideas?
Not Working
public class ErrorHandlingAttribute : ExceptionFilterAttribute
{
[Inject]
public IEventLogger EventLogger { get; set; }
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
EventLogger.LogException(actionExecutedContext.Exception);
actionExecutedContext.Response = actionExecutedContext.Request.
CreateResponse(HttpStatusCode.BadRequest,
new ServiceErrorResponseDTO("An unhandled exception occurred while calling " +
actionExecutedContext.Request.RequestUri.ToString() +
". This event has been logged. If you continue to receive this error contact Weichert"));
}
}
Working
public class RequireAuthorizationAttribute : AuthorizationFilterAttribute
{
[Inject]
public IServiceRepository ServiceRepository { get; set; }
public override void OnAuthorization(HttpActionContext actionContext)
{
#region Header Authentication
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
Custom IFilterProvider
public class NinjectWebApiFilterProvider : IFilterProvider
{
private IKernel _kernel;
public NinjectWebApiFilterProvider(IKernel kernel)
{
_kernel = kernel;
}
public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
var controllerFilters = actionDescriptor.ControllerDescriptor.GetFilters().Select(instance => new FilterInfo(instance, FilterScope.Controller));
var actionFilters = actionDescriptor.GetFilters().Select(instance => new FilterInfo(instance, FilterScope.Action));
var filters = controllerFilters.Concat(actionFilters);
foreach(var filter in filters)
{
_kernel.Inject(filter.Instance);
}
return filters;
}
}
NinjectWebCommon CreateKernel Method
private static IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Bind<Func<IKernel>>().ToMethod(ctx => () => new Bootstrapper().Kernel);
kernel.Bind<IHttpModule>().To<HttpApplicationInitializationHttpModule>();
// Ad Ninject support for Web API.
GlobalConfiguration.Configuration.DependencyResolver = new NinjectDependencyResolver(kernel);
GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider),
new NinjectWebApiFilterProvider(kernel));
RegisterServices(kernel);
return kernel;
}
NinjectWebCommon Bindings
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<ServiceDbContext>().To<ServiceDbContext>();
kernel.Bind<IServiceRepository>().To<ServiceRepository>();
kernel.Bind<CareerDevelopmentDbContext>().To<CareerDevelopmentDbContext>();
kernel.Bind<ICareerDevelopmentRepository>().To<CareerDevelopmentRepository>();
kernel.Bind<ICareerDevelopmentService>().To<CareerDevelopmentService>();
kernel.Bind<IEventLogger>().To<ServiceEventLogger>();
kernel.Bind<IFilterProvider>().To<NinjectWebApiFilterProvider>().WithConstructorArgument("kernel", kernel);
}
I had the same problem and was configuring my error handler the same way by adding it to the filter collection in WebApiConfig.cs which meant it wasn't getting handled by the FilterProvider implementation I had added. So I did this instead:
public class LoggingExceptionFilterAttribute : ExceptionFilterAttribute, IExceptionFilter
{
// this is what I wanted injected
private IEmailService emailService;
public LoggingExceptionFilterAttribute(IEmailService service)
{
emailService = service;
}
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
// my implementation here
}
}
Then I registered this in NinjectWebCommon like so:
kernel.Bind<System.Web.Http.Filters.IExceptionFilter>().To<LoggingExceptionFilterAttribute>().InSingletonScope();
And then in WebApiConfig I realized that I could get a hold of the DependencyResolver so I did this:
config.Filters.Add((IFilter)config.DependencyResolver.GetService(typeof(IExceptionFilter)));
Now Ninject handles constructing my exception filter and I can even do constructor injection instead of needing [Inject] attributes and I don't have to add my ExceptionFilterAttribute to every API controller.
Ok, you have to make sure you are binding your custom IFilterProvider as well. As of writing the Ninject.Web.WebApi Nuget package is unstable and would do that automatically for you, if you were using it. Just in the same fashion Ninject.MVC3 does this for your regular controllers.
Just make sure you have this binding, and the replaced DependencyResolver will look for IFilterProvider implementation via your Ninject kernel as well:
kernel.Bind<IFilterProvider>().To<NinjectWebApiFilterProvider>();
Then your NinjectWebApiFilterProvider will kick in and inject dependencies into your filters as per your code.