SimpleInjector Creates Controller Multiple Times and Creates Unnecessary Controller - c#

I noticed a strange behavior when using SimpleInjector with ASP.NET Web APIs, so I reduced it to its basics. Steps to reproduce:
create ASP.NET Core's default Web API project. I used .NET Core 3.1, which is out of support now but I don't think that has anything to do with the issue. You will have a single controller: WeatherForecastController
create another API controller descending from ControllerBase with a default ctor. I have the default Index() method in it but changed the return value to string.
put a breakpoint in the ctor of both controllers
run the app, and call the weather/get endpoint: the breakpoint shows that the weather controller is created once, as expected. The other controller's code is untouched, also as expected.
add these SimpleInjector nuget packages to the project:
SimpleInjector: I add it directly because this is what the documentation suggests
SimpleInjector.Integration.AspNetCore.Mvc.Core
initialize SI in Startup.cs as follows
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSimpleInjector(container, options =>
{
options
.AddAspNetCore()
.AddControllerActivation();
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.ApplicationServices.UseSimpleInjector(container);
// the rest of the default code
}
// instantiated in ctor in my code but I do it here for brevity
private readonly Container container = new Container();
run the app, and call the weather/get endpoint: the breakpoints show that the second controller is created once, and the weather controller is created twice. Then the weather/get endpoint is called.
The issue is the above unexpected controller creation. The same thing happens in my actual project as in this reproduction test project. The unnecessary controller creation concerns me. Why is that, and how can it be avoided? This surely cannot be normal but I cannot imagine what I could have mistaken in this simple configuration.
EDIT: this strange behavior occurs only for the first weather/get call

Simple Injector contains an auto verification feature which will, when the first component is resolved, trigger verification and construct all types known to Simple Injector as a verification measure. That means that at the first call, its expected for the controller to be created once. Every following request, the controller will be created just once.
When the other controller is created just once, it could mean that it isn't created by Simple Injector. You can inspect this by placing a break point in the constructor and inspecting the stack trace. Why this is the case is unclear to me from the code you posted.
More information about verification and auto-verification can be found here.

Related

How to override DI registration from other container in ASP.NET Core integration test

I have the following registration in asp.net core startup.cs file:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<UserService>().As<IUserService>();
}
This is to configure the Autofac container. And I have another integration test project where I have a CustomWebApplicationFactory class and I'm trying to replace the implementation of IUserService interface.
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.AddSingleton<IUserService, TestUsersService>();
});
}
It seems not work when I debug the test project and the implementation of IUserService is still UserService.
I tried registering UserService directly in Startup.ConfigureServices method with ASP.NET Core built-in IServiceCollection and it worked when debugging:
services.AddSingleton<IUserService, UserService>();
So, how can I fix the problem when I use Autofac as the IoC container and the integration test project will work properly as I expect?
You are likely running into an order of operations problem. In general, last in wins. That goes for Autofac and for the base Microsoft DI container.
Assuming you've read through the docs on Autofac ASP.NET Core integration you'll see that when ConfigureContainer is in place the order of operations is roughly:
WebHost specific ConfigureServices
Startup class ConfigureServices
Startup class ConfigureContainer
When adding ConfigureTestServices in place, it looks like (though I haven't stepped through) it runs after the WebHost and Startup class ConfigureServices... but it's still running before ConfigureContainer.
This would be easy enough to test - create a service interface with three different implementations. Register a different implementation at each level. Resolve the interface in a controller. Which one did you get? That was the last one to run. Now remove that registration from the app and try again. What's the next one you get? That's the second to the last one. And so on.
Autofac takes a pre-built IServicesCollection and cycles through it, adding that to the native Autofac container. Once that's happened, it doesn't matter if you modify the collection. Autofac has no control over the order of the execution of the startup mechanism in ASP.NET Core; it just knows that ASP.NET Core says, "Here's the final service collection to go ahead and import!" If that's not happening at the right stage, you'll have to do one of two things:
Move the registration you need to override out of ConfigureContainer and into one of the ConfigureServices methods, using the Microsoft registration language instead of native Autofac.
Perform the override some other way like using an ASPNETCORE_ENVIRONMENT setting of Test and providing a ConfigureTestContainer method. (Examples of environment specific registration methods are in the docs.)
When using ContainerBuilder like this:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<UserService>().As<IUserService>();
}
You must use ConfigureTestContainer rather than ConfigureTestServices as follows:
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestContainer<ContainerBuilder>(containerBuilder =>
{
containerBuilder.RegisterType<TestUsersService>().As<IUserService>();
});
}
This is executed after the call to ConfigureContainer and will correctly override IUserService with TestUsersService
For the ones coming from google I'd like to add to Michael's excellent answer that ConfigureTestContainer does not work with the generic host recommended over the web host by Microsoft as of .NET Core 3.0. There is however a workaround proposed by Alistair Evans from the Autofac team. Unfortunately, it relies on the deprecated IStartupConfigureContainerFilter that will probably be removed in .NET 5.0.
This means that currently in .NET 5.0 there is no way to mock dependencies injected by an external DI container in integration tests when using the generic host.
Luckily, David Fowler from the ASP.NET team is looking into the issue.

Multitenancy in ASP.NET Core 2.0+

Bakground:
I want to develop a multi-tenant application in ASP.NET Core and have been looking into Ben Fosters Saaskit library which seems to provide good solutions for common problems in multitenancy applications.
Problem:
The SaasKit have a UsePerTenant method which is nice for doing different things per-request depending on current tenant.
My goal is to use the UsePerTenant method combined with different IOptions objects injected via dependency injection. This can be used in the authentication middleware like
AddAuthentication().AddCookie(..).AddOpenIdConnect(...)
Which is configured in the ConfigureServices method in Startup.cs
public class Startup
{
// Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
...
}
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
...
}
}
I can’t make the authentication middleware in ASP.NET 2.0+ use different IOptions objects per-request since the ConfigureServices method in the Startup.cs file only runs once every application startup and the UsePerTenant method should be used in the Configure method which is running for each incoming/outgoing request in the ASP.NET pipeline.
Question:
How to dynamically change cookie and OpenID Connect options in the ConfigureServices method based on current tenant?
I have found a good way to get per tenant options for any type of ASP.NET Core options, including cookie or openID Connect. I have wrapped this up into a framework called Finbuckle.MultiTenant.
It basically boils down to a setup that looks like this:
services.AddMultiTenant().
WithInMemoryStore()).
WithRouteStrategy().
WithPerTenantOptionsConfig<CookieAuthenticationOptions>((o, tenantContext) => o.Cookie.Name += tenantContext.Id);
See my here for more information if you are curious: https://www.finbuckle.com/MultiTenant
The following PR provides a solution for the above question.
https://github.com/saaskit/saaskit/pull/96
The PR have been merged with the "master" branch now.
It wasn't merged yet (November 2018)

Dependency injection failure in ASP.NET Core RC2

I am unable to start up my asp.net core application after trying to port it to 1.0 RC2 from 1.0 RC1. The startup error I get is this:
Unhandled Exception: System.Exception: Could not resolve a service of type 'Microsoft.Extensions.Configuration.IConfiguration' for the parameter 'configuration' of method 'Configure' on type 'MYCOMPANY.MYTHING.Api.Startup'.
at Microsoft.AspNetCore.Hosting.Startup.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at MYCOMPANY.MYTHING.Api.Program.Main(String[] args)
I can't figure out how this is supposed to work.
The parts I do understand are:
My startup.cs code looks correct to me, in Startup(IHostingEnvironment env) I create a ConfigurationBuilder and set Startup.Configuration equal to builder.Build(). The ConfigureServices method is also reached and Configuration object exists there.
When the host.Run() code is reached in Program.cs, I get a crash.
Configure(app,env,loggerFactory,configuration,respository...) method is NEVER reached.
In RC1 there used to be a line like this:
services.AddInstance<IConfiguration>(Configuration);
In RC2 that doesn't exist anymore in a new application (I am reading a new application which works and trying to compare line by line).
I have been looking for how to make this work, and how to understand dependency injection of the IConfigurationRoot, but I don't see any attribute or code that seems to be responsible for this injection.
Update: It seems that I was using some strange pattern in my RC1 code that is no longer supported in RC2. After I removed some additional parameters from my Configure() method it was once again invoked by the .net core startup code.
It looks like you have already fixed the issue, but the restriction with respect to the configure method is not as hard and fast as you suggest.
The restriction is you must have a public, instance or static method named ConfigureDevelopment, where Development is the environment name, or a method named Configure, which will be used if an environment specific configure method does not exist.
Obviously, you shouldn't need to inject your IConfiguration in to the Configure method as it will be set on your Startup class, but if you need something else injected (and you've configured it in ConfigureServices then you can do so. For example, the following would be perfectly valid.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ISuperSecretClass, SuperSecretClass>();
}
public void Configure(IApplicationBuilder app, ISuperSecretClass instance)
{
//do something with instance
}
For reference, I checked in the StartupLoader source for the Configure restrictions.
If you want to inject Configuration instance you can do:
services.AddSingleton((provider)=>
{
return Configuration;
});

asp net core 1 RC2 AccountController injection

I created a asp.net core rc2 web application with user identity, however i'm confused how the account controller class is getting it's arguments, usermanager, signinmanager? Where are they being passed in from? I follow the call stack and I get external code, what external code is passing in these objects? Help me understand, how these 2 objects are being initialized.
In your Startup.cs you will see a call to this method
services.AddIdentity<ApplicationUser, IdentityRole>()
Afte reading the links on dependency injection suggested by #AndrésRobinet you can actually see where the services are being wired up.
This extension method lives in `IdentityServiceCollectionExtensions - You can then go and look at the source code for this method call (.NET core is on github):
line 67 of the AddIdentity method
services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();
what external code is passing in these objects?
Right-click on External code and click Show External Code - now you can get an idea of what is happening under the hood. the code down to and including the Kestrel webserver is also browsable/downloadable on github
image truncated

Passing logical call context from OWIN pipeline to WebApi controller

I'm trying to pass contextual information on the logical call context (using CallContext.LogicalSetData(CallContextKey, value)) as per Stephen Cleary's post http://blog.stephencleary.com/2013/04/implicit-async-context-asynclocal.html; and inspired by the code in https://github.com/neuecc/OwinRequestScopeContext.
The value will be available through out the OWIN pipeline, but it is not available when the call enters the WebApi controller, the value is not set.
I also noticed that when setting a breakpoint in the controller, I can't see the OWIN pipeline in the call stack. Apparently, ASP.NET is making controller calls on a separate call context.
So,
Why (and how) does ASP.NET isolate the call context from OWIN pipeline to the WebApi controller?
How can I pass contextual data from Pipeline to the controller?
It took me several days to find out why the CallContext is clear in the API controller, until I read this article:
http://www.asp.net/aspnet/overview/owin-and-katana/owin-middleware-in-the-iis-integrated-pipeline
If two middleware runs in different IIS stage, they will have different CallContext.
If you are hosting OWIN on IIS and want the same request context in all middlewares, well, use the old HttpContext.Current instead.
I'm not really sure what you mean by passing contextual data from Pipeline to the controller but maybe if you already use Microsoft.AspNet.Identity, you could leverage the use of IAppBuilder.CreatePerOwinContext in order to register your object to the pipeline.
I use to do something like that when I want to pass my contextual object through Owin Pipeline to WebApi Controllers:
startup.cs
//Registration of a delegate factory
app.CreatePerOwinContext<Foo>(Factory.CreateFoo);
factory.cs
//Contextual Object
public static Foo CreateFoo(IdentityFactoryOptions<Foo> options, IOwinContext context)
{
//Owin Context is available here
}
controller.cs
public FooController()
{
var fooObj= HttpContext.Current.GetOwinContext().Get<Foo>();
}
Hope it helps !

Categories

Resources