-
Notifications
You must be signed in to change notification settings - Fork 152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to use Simple Injector in ASP.NET vNext #41
Comments
When using ASP.NET 5 (vNext), don't try to integrate Simple Injector completely into the ASP.NET pipeline. There is no compelling reason to do so, and it leads to all sorts of complications, because ASP.NET implements the Conforming Container anti-pattern and forces the used DI container into an API that is not compatible with Simple Injector's API. So instead of trying to replace the default This basically means you just have to do a few tiny things in your
The following is an example of how the startup class would typically look like: public class Startup
{
private Container container = new SimpleInjector.Container();
public Startup(IHostingEnvironment env) {
// ASP.NET default stuff here
}
// This method gets called by the runtime.
public void ConfigureServices(IServiceCollection services) {
// ASP.NET default stuff here
services.AddInstance<IControllerActivator>(
new SimpleInjectorControllerActivator(this.container));
}
// Configure is called after ConfigureServices is called.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory fac) {
InitializeContainer(app);
RegisterControllers(app);
this.container.Verify();
// Wrap requests in a execution context scope. This allows
// scoped instances to be resolved from the container.
app.Use(async (context, next) => {
using (this.container.BeginExecutionContextScope()) {
await next();
}
});
// ASP.NET default stuff here
}
private void InitializeContainer(IApplicationBuilder app) {
this.container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
// For instance:
container.Register<IUserRepository, SqlUserRepository>(Lifestyle.Scoped);
}
// In the future, this RegisterControllers method will be moved to an integration package.
private void RegisterControllers(IApplicationBuilder app) {
// Register ASP.NET controllers
var provider = app.ApplicationServices.GetRequiredService<IControllerTypeProvider>();
var controllerTypes = provider.ControllerTypes.Select(t => t.AsType());
foreach (Type type in controllerTypes) {
var registration = Lifestyle.Transient.CreateRegistration(type, container);
container.AddRegistration(type, registration);
registration.SuppressDiagnosticWarning(DiagnosticType.DisposableTransientComponent,
"ASP.NET disposes controllers.");
}
}
}
internal sealed class SimpleInjectorControllerActivator : IControllerActivator {
private readonly Container container;
public SimpleInjectorControllerActivator(Container container) { this.container = container; }
[DebuggerStepThrough]
public object Create(ActionContext context, Type type) => this.container.GetInstance(type);
} For more detailed discussion, please see the following articles: |
Thanks for the beautiful explanation and sample code! There are so many goodies in this answer and the code, I simply love it!! I'm gonna give it a shot and will let you know (although I'm pretty sure this will work)! This is going to be very helpful for folks like me who come looking for ways to migrate to vNext and still keep SimpleInjector as their DI of choice! |
Not exactly. Most of the our framework depends on minimal DI behaviors (we try to avoid the service locator pattern ourselves) that are available everywhere but there's nothing stopping end users from using the container to its full ability.
That's actually a fair point but I don't see why it wouldn't work with the DI system. Is there some feature that Simple Injector doesn't support? |
Hi David, Thank you for showing an interest in this topic, I appreciate it. It is my understanding that application developers, external parties and ASP.NET framework developers all make registrations to what becomes a list of
Without a doubt there are many more of these explicit and implicit specifications that application developers, third party library developers and developers from within Microsoft will come to rely on over time; these are just a few that I noticed while figuring out how the new DI system works. Whether or not your team decides to make some of the implicit requirements explicit is irrelevant; hundreds of components and literally millions of developers will slowly begin to depend on each characteristic of the API. Any change made to any part of the contract (whether it is implicit or explicit) can break existing applications and third party components. Each specification (requirement or restriction) defines a contract of what to expect from anyone who wishes to replace the built-in container with their own. This is what Mark Seemann’s defined as a Conforming Container. According to Mark's definition, the 'conforming container' is not the library that conforms or adapts to the framework, but rather the abstraction or required behavior defined by the framework is called the 'conforming container'. Whether or not there is a central Now here’s the catch: There is no single DI library for .NET (other than vNext's built-in DI library itself) that adheres to all of the implicit and explicit requirements. I’m not just talking about Simple Injector here; there is no library that exactly conforms to the behavior specified in vNext. To give you an idea of what application developers and adapter builders will face, take a look at the following incompatibilities:
One of the most obvious differences between Simple Injector and the majority of DI libraries is, as already mentioned, the way users are expected to register collections. It is quite common for containers to allow users to make multiple distinct registrations for the same service type and the container will stack those registrations up as a collection (just as the vNext API specifies). Having witnessed this as a common source of configuration errors, Simple Injector forces the user to separate registrations of collections from 'normal' one-to-one mappings. The full reasoning for this is described here. This separation of registrations is something that doesn't marry with the model that vNext defines. So it’s not that Simple Injector “doesn’t support” some feature, but it simply has a different view on how a registration API should work and behave. In vNext, the application is supplied with the list of registrations (i.e. an To adhere to the vNext specifications, an ASP.NET vNext adapter for Simple Injector must, among other things, make sure that any group of registrations that form a collection, are correctly registered in the Simple Injector container. Unfortunately, it is impossible for the adapter to confidently detect whether certain registrations are part of a collection or not. Although one might think that the adapter can easily group registrations by their service type, and register them as collection in case there is more than one registration in a group, this will not work. Take ASP.NET's Another idea would be to group the list of registrations by their service type and register each group always as collection (even if there is only one registration in the group), and in case the group contains just one registration, make the one-to-one mapping as well. Problem with this approach is that this would fundamentally change how Simple Injector behaves and this will confuse Simple Injector users. We lose the verification possibilities that separating collections brings and we would therefore lose some of the unique abilities that developers use and love about Simple Injector. And this the main point here: although it would be technically possible to create an adapter for Simple Injector; by doing so the adapter would change the behavior of Simple Injector in such radical way that it becomes a totally different library. It would become a library that loses some of its unique and compelling features. Mark Seemann terms this it stifles innovation. But again, this –not only– holds for Simple Injector, this more or less holds for every DI library. Since no single DI library is 100% compatible with the vNext API, every adapter will have to do some transformation. With each transformation, the behavior of the adapted library is changed. Changed in a way that will confuse its users; changed in a way that will make it lose its uniqueness and usefulness. Your team has asked container builders to create and maintain adapters for the DI system, but this like opening a can of worms. In the end, everybody will lose. But all is not lost! As I described in my previous comment, it is very easy to have the two worlds live side-by-side. Not only is it possible, I would say that it is actually preferable to use your custom (application) container side-by-side with the framework’s container. Mixing framework registrations and application registrations in a single container stems from the idea that application components need to be built-up using framework components. But doing this is a violation of the Dependency Inversion Principle which states that "the abstracts are owned by the upper/policy layers". Forcing application components to directly depend on framework types (even if they are abstractions) violates this principle. Instead of injecting framework types into application components, application components should depend solely on application abstractions. When required, small, specific adapter implementations can act as the gateway to framework types and methods. To conclude, my main grievance is not so much the existence of the vNext DI system itself, it is with how the ASP.NET team suggests that existing DI libraries should be integrated with the new vNext model. It is my opinion that this is the wrong message. Instead of using adapters, application developers would be better advised to keep these things separate. Let vNext use its container internally and the vNext container is there for you to use if you need it (e.g. if you have never used a container before) and it will undoubtedly guide some developers towards the SOLID principles. My hope is that the ASP.NET team will stop suggesting that adapters are needed to integrate with the internal DI system, and instead start showing examples of how easy it is to plug a custom library into the pipeline, just as I demonstrated in my earlier comment. |
I'm not sure application developers will have to deal with anything. I do think adapter creators do though.
This is a great list and yes we do have some default expectations, or rather a "spec" on how we think the container should behave when these registrations are added, but we don't really have a registration API per se, nor do we prevent you from taking advantage of specific container you choose to use. That's one of the things in my mind that a conforming container does and we absolutely do not do that.
Now if you're saying that if we assume anything about how the DI system behaves without being a conforming container then you got us 😄 but that's not how I read it originally.
If that's the case then yes, we have a conforming container. One that expects a base set of behavior and one that also lets you use a specific DI container to it's full ability. Autofac for example actually works pretty cleanly with ASP.NET 5 in general without changes to it. The end user can also use Autofac APIs directly to take advantage of their features. So in this case the "Conforming Container" lets you do more than you can do with the base assumptions, that's a win win IMO.
I think there can be a balance between the 2 worlds. You can absolutely replace the DI system and we should talk about how to do it and advertise the containers that are compatible and "conforming". We can also document well known composition roots so that non conforming containers can be used in very specific and narrow ways (IControllerActivator etc.). Heck, you can even use Pure DI if that floats your boat.
People will make compatible adapters since pretty much every DI library supports the list of features that our "conforming container" requires. There are quirks, but they can be analyzed and ironed out.
I understand this statement but I wholeheartedly disagree with it. To each his own. I'm a huge fan of framework types being injected into user code. |
I'm trying to hook up Simple Injector in a ASP.NET vNext project. Since, Simple Injector already implements
IServiceProvider
, I tried registering it like this:Startup.cs
However, running the app gives me this error:
Not using Simple Injector causes the app to run just fine (although it fails to inject anything but at least it doesn't throw this error).
Not sure what I'm missing.
The text was updated successfully, but these errors were encountered: