Get the source code for this video for FREE → the-dotnet-weekly.ck.page/clean-di Want to master Clean Architecture? Go here: bit.ly/3PupkOJ Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
The second approach is very elegant and at the same time a less common way to inject your dependencies into the project. Congratulations for another extremely useful material 😃
@@MilanJovanovicTech Some registrations need ordering. Decorators (with Scrutor) for example. Or if you have multiple service registrations without TryAdd. But this would be a bad practice if you rely on something like that. But you can use that approach as "fallback registrations" if Noone registers a service, you use a fallback default implementation. Or if you switch to App and "Use". Middlewares do have order.
I was about to ask how ordering will be factored in. Good a thing I read comments first. That’s why I go with the extension methods. In my worst case I nest the extensions so it doesn’t grow on the program.cs side
Doesn't order property limit the auto registration of the services in cases where we have to re-order when a new service needs to be introduced with higher priority? How will this be handled with order property?
I've been using the extension method approach for some time now. It's very clean. The Automatic installer is a very interesting approach too. I saw something very similar a while back, just before .NET Core dependency injection system, in a great instructor's channel from whom I learned a lot (Tim Corey) with Autofac Nuget Package.
@0:40 "You can see we have close to 100 lines of code to configure our services". @8:10 Now we have 130 lines of code in another file. I would've gone one step further and put unrelated stuff (essentially each extension method in this example) in it's own separate file. Other than that: nice video, keep up the great work! I enjoy your videos very much!
I used to be a fan of reflection mechanics liked this until I had to reverse engineer my own code several months later so now I think the explicit nature of the extension approach is going to cause you much less headaches in future.
The second approach is more like a modular design. if you need to go this route, you may need to implement an int property into your `Installer` interface, to be used for ordering the executions of the installer (e.g. SortOrder,. priority, etc..). This can be also extendable if you are into a modular systems ;).
I really like the reflection method you've used there. The fact that you can use that straight out of the box like that, just by implementing an interface... Excellent. I did have a bug, but that was down to me being stoopid. Top job, chap.
I'm torn between the two options. I have tend toward the first extension strategy in the past. I like the idea of having a different file for each startup type regardless of the strategy. It keeps the code better organized and easier to find startup elements. The reflection approach has a certain sense of magic while still being clean and easy to maintain. Magic for me is bad because it detaches you from what is happening and can be harder to maintain. One catch to the reflection startegy is I may want to selectively switch between different startup extensions to cover different scenarios (mock, test environment, etc.). The reflection strategy makes that a little more difficult but not impossible. Which do you find yourself using more and why?
I use the IServiceInstaller approach in practice. I like that I can name my files properly and I can easily find them via search in VS. I haven't had any issues with it. It's more or less the same thing as with extension methods, just automatically applied.
Nice summary. And a good reminder that I don't need Scrutor for simple tasks like this. One thought - with C#'s static virtual members in interfaces, we can avoid that extra step to instantiate IServiceInstaller objects, just to call a method (which can now be static).
@@MilanJovanovicTech credit to Nick Chapsas in a tutorial he did on bootstrapping Minimal APIs. I think factory patterns are about to get simpler across the board with this feature.
Service.Install seems very clean, as we can use this at each project (class lib) level so services defined in diff project assembly can we added there only, which would be more clean and explicit.
I think a nice way to reduce dependency registrations, is using Interface marking, but not with tools like scrutor. because it will make application start up slow and heavy (because of reflection stuff). Instead we can create a source generator to wisely check the interface marks and inject them using a partial method.
We are talking here about microseconds, miliseconds for net to scan the assembly, not sure what will you get by introducing source generator, make a benchmark dotnet and let us know results...
@@danijelzg001 Basically difference between reflection scanning assembly and explicitly define dependency is included in c# basics and if you want proof for that you can go and read "c# from scratch" documents and startup velocity is one of the hottest high level topics in architectural level of programming. As much as your project gets bigger, your assembly gets bigger and its scanning gets harder. I don't think anyone needs proof and benchmarks for principles. But you by your milliseconds theory just spoiled all computer science when it comes to various algorithms and stuff :D
I've chosen the extension methods approach thusfar. My biggest issue is the solution has about 30 microservice projects and I don't want to recreate all this every time we add a new project, so I'm shoving as much as possible into a class library, which seems to increase complexity a bit.
Lovely video. My only concerns are Jr dev learning curve and functional testing. The reflection part would complicate the test. But I like it. I am a fan of putting the dependencies at the project level itself. So the infrastructure layer would have one etc
I don't usually test my DI code, and I don't think it's worthwhile. Possibly a functional test that crashes if a service is missing, I think Nick covered that topic in a recent video.
Thanks for the video! 2nd approach is interesting. I'd like to mention it. Would it be great to make each layer to be responsible for its dependencies? By doing this we can apply an internal modifier where it is possible and the layer becomes more encapsulated with its own dependencies. Otherwise, we need to keep all implementations public just to register them.
With the IServiceInstaller approach, how do you handle cases where different web apps need different services? As-is, your approach would install everything everywhere.
@@MilanJovanovicTech Maybe I'm missing something, so thanks for bearing with me... but let's say WebApp1 needs both CachingServiceInstaller and InfrastructureServiceInstaller, but WebApp2 only needs CachingServiceInstaller. Are you saying the each web app would need to have duplicate implementations of CachineServiceInstaller?
@@jacobstamm Yes. This implementation doesn't really take into account using it from multiple APIs. You'd need to rethink the solution to make it more generic like that.
Great approach with ServiceInstaller but it also has drawbacks because of reflection. It might be hard to trim your application correctly when using reflection and some apps do require to be self-contained and trimmed. Personally I use extensions methods but write them in different files. The future of DI will be with source generators, so the compiler can check that ServiceA has dependencies on ServiceB and ServiceC and if they are registered in DI. I hope this will be the future of DI which will solve many problems during compile time and it will be way faster than reflection. A quick startup of the app is often critical for such apps as Azure Functions and AWS lambdas
Always nice to see different approaches. I even have an analyzer rule that doesn't allow a big program.cs I usually go with Startup classes (same like your installer). Sometimes with interfaces and sometimes with static methods (no extensions). But the main difference to your solution is my encapsulation. Sometimes it's not enough to just register services and you have to do some app stuff. So, my Startup classes mostly have ConfigureServices(ServiceCollection) and also ConfigureApp(App). In that case AuthAndAuthStartup would have services.AddAuthentication() and app.UseAuthentication() Same with CorsStartup, SwaggerStartup, etc.
@@MilanJovanovicTech Id did some digging and the rule for checking is: CA1506: Avoid excessive class coupling - Can't post link here but you'll find it :)
Biggest concern I have about DI is how to express in code that one bunch of dependencies require some other shared ones, e.g. you have .AddFeatureX() .AddFeatureY() and they both use say IDistributedCache and you want to make sure you didn't forget .AddDistributedCache in your app, but don't want to bind it in either FeatureX or FeatureY, because it might be up to application to bind it to specific implementation and in specific scope maybe. Neither of proposed solutions help with that.
Thank you so much for sharing! I'm learning Clean Architecture and I have a question... applying UseSqlServer to the DbContext doesn't create a dependency? So, the database layer won't be able to change so easily or am I wrong? Is there a more decoupled way to do it while still keeping dependency injection?
I like the reflection one more, but it would introduce a longer startup time, something to think about if cold starts in your projects is important for you. In lambdas for example, reflection approach might not be for you.
Like you said, we can measure it but saying "I'm sure it's not a significant performance impact" is not accurate, performance is contextual. In a lambda web api for example hosted in AWS, cold starts can eat up as much as 400ms and reflection would not make it faster. So always measure before making assumptions.
We've been using a custom built plugin system for many years now and we use (sort of) the approach of your IServiceInstaller. It's all good when it comes to service registration but when it comes to handling middleware it needs coordination (since the middleware is sensitive to ordering). And since all of our plugins essentially know nothing about the other plugins, they cannot coordinate amongst themselves. So we added some meta-information that forms a dependency graph but it didn't solve the problem really. This is where I realized that there is no good solution to the problem of coordinating middleware order. Basically we're stuck with a hard-coded setup. Anyone else been playing around with that kind of setup!?
We've implemented the first approach on project and now I think neither the 1st or 2nd approach is good. Its just adding another layer of complexity for very simple di statements. What I would do now is just create a single extension method and put all the di code there just so to cleanup the main file
About the second one, I reckon we can run it easier. We have one interface and a lot of implementations, and in this case, we can have something like this IEnumarable and then have foreach to call all of them one by one 🙂 Of course, first of all, we have to register all the implementation)
@@MilanJovanovicTech in our solution (clean architecture) we exposed dependency injection.cs class per project (web, application, infrastructure) to group required Injection. Then these are called in the web project (like services.AddApplicationServices(), or services.AddInfrastructure()). I think that we can use the second approach but we must keep track of the order of assemblies.
The reflection approach is a bad idea. Reflection is rule based. Rules must be resolved by other programmers / yourself. It comes in handy when initially creating the project but later when someone else has to maintain it, this person has to resolve that rule base approach, which can be an obstacle. If you write down in a reasonable way - do it. Grouping the service registrations can be an effective way to improve readability and testability (e.g. you can step over a function if you know it works). On the other hand, you need to navigate to more code files. But both approaches don't make the code more robust. So that is something I would consider doing when nothing else is left to do.
Get the source code for this video for FREE → the-dotnet-weekly.ck.page/clean-di
Want to master Clean Architecture? Go here: bit.ly/3PupkOJ
Want to unlock Modular Monoliths? Go here: bit.ly/3SXlzSt
The second approach is very elegant and at the same time a less common way to inject your dependencies into the project. Congratulations for another extremely useful material 😃
Thank you, I'm glad you saw something interesting
Great approach. I think it's good to add an order property to specify which dependency should be injected first...
I would even consider an attribute (like a ServiceinstallerAttribute) for more configuration options.
When did you need to order of registration?
@@MilanJovanovicTech Some registrations need ordering. Decorators (with Scrutor) for example.
Or if you have multiple service registrations without TryAdd. But this would be a bad practice if you rely on something like that. But you can use that approach as "fallback registrations" if Noone registers a service, you use a fallback default implementation.
Or if you switch to App and "Use". Middlewares do have order.
I was about to ask how ordering will be factored in. Good a thing I read comments first. That’s why I go with the extension methods. In my worst case I nest the extensions so it doesn’t grow on the program.cs side
Doesn't order property limit the auto registration of the services in cases where we have to re-order when a new service needs to be introduced with higher priority? How will this be handled with order property?
I've been using the extension method approach for some time now. It's very clean. The Automatic installer is a very interesting approach too. I saw something very similar a while back, just before .NET Core dependency injection system, in a great instructor's channel from whom I learned a lot (Tim Corey) with Autofac Nuget Package.
Would you try out the second approach I suggested?
@@MilanJovanovicTech Yes, I think I would. Probably in solutions with complex Startup settings and large amount of projects.
@0:40 "You can see we have close to 100 lines of code to configure our services". @8:10 Now we have 130 lines of code in another file. I would've gone one step further and put unrelated stuff (essentially each extension method in this example) in it's own separate file.
Other than that: nice video, keep up the great work! I enjoy your videos very much!
Of course, each extension method could have been in its own file. 😅
I used to be a fan of reflection mechanics liked this until I had to reverse engineer my own code several months later so now I think the explicit nature of the extension approach is going to cause you much less headaches in future.
Perhaps, but I don't think this is troublesome to maintain.
Thanks for the video
when you use Extension methode , you can avoid returning IServiceCollection no need
I know, I just like chaining the method calls.
The second approach is more like a modular design. if you need to go this route, you may need to implement an int property into your `Installer` interface, to be used for ordering the executions of the installer (e.g. SortOrder,. priority, etc..). This can be also extendable if you are into a modular systems ;).
I used something similar for my Modular Monolith. Like an "IModuleInstaller"
I really like the reflection method you've used there. The fact that you can use that straight out of the box like that, just by implementing an interface... Excellent. I did have a bug, but that was down to me being stoopid. Top job, chap.
Hey, thanks a lot. Glad this was helpful. 😁
I'm torn between the two options. I have tend toward the first extension strategy in the past. I like the idea of having a different file for each startup type regardless of the strategy. It keeps the code better organized and easier to find startup elements. The reflection approach has a certain sense of magic while still being clean and easy to maintain. Magic for me is bad because it detaches you from what is happening and can be harder to maintain. One catch to the reflection startegy is I may want to selectively switch between different startup extensions to cover different scenarios (mock, test environment, etc.). The reflection strategy makes that a little more difficult but not impossible. Which do you find yourself using more and why?
I use the IServiceInstaller approach in practice. I like that I can name my files properly and I can easily find them via search in VS. I haven't had any issues with it. It's more or less the same thing as with extension methods, just automatically applied.
Avoid reflection. It is an indirection hard to resolve and uncessary.
Very helpful in understanding how to organize ever growing dependencies
Glad it was helpful!
Nice summary. And a good reminder that I don't need Scrutor for simple tasks like this. One thought - with C#'s static virtual members in interfaces, we can avoid that extra step to instantiate IServiceInstaller objects, just to call a method (which can now be static).
That would be super interesting to explore! I'll see what I can make of it 😁
@@MilanJovanovicTech credit to Nick Chapsas in a tutorial he did on bootstrapping Minimal APIs. I think factory patterns are about to get simpler across the board with this feature.
Awesome! Exactly what I was looking for. Thank you Milan.
Most welcome :)
Thanks for introducing super compact and maintainable way of doing better DI @Milan
My pleasure 😊
Service.Install seems very clean, as we can use this at each project (class lib) level so services defined in diff project assembly can we added there only, which would be more clean and explicit.
I'm yet to explore static interface methods to see what can be done with it, may be able to avoid instantiating the service installers
Love aproach with reflection, doing like that for a some time already )
Nice, one of the few!
YOu actually touched on some good points here, especially when it came to the service installers side of things... Super video! thanks.
I appreciate that!
Hi Milan, great video. You may use SHIFT ALT and . (dot) to select next occurrence, so you don't have to manually select and paste.
I have to be slow to make this video longer, that's how I earn my daily bread 😅
@@MilanJovanovicTech you did really well and appreciate your content
Great lesson. Thank you for sharing these gems for free.
Glad you enjoyed it!
I think a nice way to reduce dependency registrations, is using Interface marking, but not with tools like scrutor. because it will make application start up slow and heavy (because of reflection stuff). Instead we can create a source generator to wisely check the interface marks and inject them using a partial method.
Application startup time is usually the least of my concerns
The source generator idea sounds quite interesting. Would like to see a sample implementation.
@@MilanJovanovicTech when the project grows up and specialy if you use auto-scaling system like kubernates, stratup time concern gets bolder.
We are talking here about microseconds, miliseconds for net to scan the assembly, not sure what will you get by introducing source generator, make a benchmark dotnet and let us know results...
@@danijelzg001 Basically difference between reflection scanning assembly and explicitly define dependency is included in c# basics and if you want proof for that you can go and read "c# from scratch" documents and startup velocity is one of the hottest high level topics in architectural level of programming. As much as your project gets bigger, your assembly gets bigger and its scanning gets harder. I don't think anyone needs proof and benchmarks for principles. But you by your milliseconds theory just spoiled all computer science when it comes to various algorithms and stuff :D
You're a programming genius, Milan!
Haha, lovely! Thank you :)
I've chosen the extension methods approach thusfar. My biggest issue is the solution has about 30 microservice projects and I don't want to recreate all this every time we add a new project, so I'm shoving as much as possible into a class library, which seems to increase complexity a bit.
Doesn't it make sense for each microservice to handle it's own registrations?
Lovely video. My only concerns are Jr dev learning curve and functional testing. The reflection part would complicate the test. But I like it. I am a fan of putting the dependencies at the project level itself. So the infrastructure layer would have one etc
I don't usually test my DI code, and I don't think it's worthwhile. Possibly a functional test that crashes if a service is missing, I think Nick covered that topic in a recent video.
Thanks for the video! 2nd approach is interesting.
I'd like to mention it. Would it be great to make each layer to be responsible for its dependencies? By doing this we can apply an internal modifier where it is possible and the layer becomes more encapsulated with its own dependencies. Otherwise, we need to keep all implementations public just to register them.
Yes, we can make the implementations internal and inside each project/layer. Then we can scan for internal files and register it in the same way.
With the IServiceInstaller approach, how do you handle cases where different web apps need different services? As-is, your approach would install everything everywhere.
You can easily solve this by passing in the specific assembly to scan for IServiceInstaller implementations
@@MilanJovanovicTech Maybe I'm missing something, so thanks for bearing with me... but let's say WebApp1 needs both CachingServiceInstaller and InfrastructureServiceInstaller, but WebApp2 only needs CachingServiceInstaller. Are you saying the each web app would need to have duplicate implementations of CachineServiceInstaller?
@@jacobstamm Yes. This implementation doesn't really take into account using it from multiple APIs. You'd need to rethink the solution to make it more generic like that.
thanks for the video
You're welcome
Thanks @Milan, it was a great resourceful video.
Glad it was helpful!
Very nice approach, your all videos are awesome and unique.
Thank you, I'm glad you find them unique!
Great approach with ServiceInstaller but it also has drawbacks because of reflection. It might be hard to trim your application correctly when using reflection and some apps do require to be self-contained and trimmed. Personally I use extensions methods but write them in different files. The future of DI will be with source generators, so the compiler can check that ServiceA has dependencies on ServiceB and ServiceC and if they are registered in DI. I hope this will be the future of DI which will solve many problems during compile time and it will be way faster than reflection. A quick startup of the app is often critical for such apps as Azure Functions and AWS lambdas
Agreed. That would be awesome if something can be done with source generators
Nice! I'll apply your approach. Thanks.
You're welcome
I use the 2nd approach!
Wow, one of the few!
Always nice to see different approaches. I even have an analyzer rule that doesn't allow a big program.cs
I usually go with Startup classes (same like your installer). Sometimes with interfaces and sometimes with static methods (no extensions).
But the main difference to your solution is my encapsulation. Sometimes it's not enough to just register services and you have to do some app stuff.
So, my Startup classes mostly have ConfigureServices(ServiceCollection) and also ConfigureApp(App).
In that case AuthAndAuthStartup would have services.AddAuthentication() and app.UseAuthentication()
Same with CorsStartup, SwaggerStartup, etc.
Agreed, there's more than just configuring services. How do you apply that analyzer for Startup.cs size?
@@MilanJovanovicTech Id did some digging and the rule for checking is: CA1506: Avoid excessive class coupling - Can't post link here but you'll find it :)
@@MarcusKaseder Very interesting rule!
Nice and useful. Thank you.
Glad it was helpful!
Biggest concern I have about DI is how to express in code that one bunch of dependencies require some other shared ones, e.g. you have
.AddFeatureX()
.AddFeatureY()
and they both use say IDistributedCache and you want to make sure you didn't forget .AddDistributedCache in your app, but don't want to bind it in either FeatureX or FeatureY, because it might be up to application to bind it to specific implementation and in specific scope maybe.
Neither of proposed solutions help with that.
You can register distributed cache with both AddFeatureX and AddFeatureY using TryAddSingleton for example
@@MilanJovanovicTech btw, TryAdd method iterates through all services each call. I wonder why it doesn't still use a dictionary to locate a service...
Great Tutorial, Can you make a tutorial about unit testing and Mocking testing specifically
I would also like to see something like this. Maybe something how to create a clean test setup with DI. E.g. using an in memory DB during tests.
Yes!
hi milan! thank you very much for your videos
So I can do method chaining like:
AddApplication().AddInfrastructure().AddPersistence()
Thank you so much for sharing!
I'm learning Clean Architecture and I have a question... applying UseSqlServer to the DbContext doesn't create a dependency? So, the database layer won't be able to change so easily or am I wrong? Is there a more decoupled way to do it while still keeping dependency injection?
This isn't something I'm too concerned with most of the time. I like some ideas of Clean Architecture, but not all of them
I like the reflection one more, but it would introduce a longer startup time, something to think about if cold starts in your projects is important for you. In lambdas for example, reflection approach might not be for you.
We can measure it, I'm sure it's not a significant performance impact.
Like you said, we can measure it but saying "I'm sure it's not a significant performance impact" is not accurate, performance is contextual.
In a lambda web api for example hosted in AWS, cold starts can eat up as much as 400ms and reflection would not make it faster. So always measure before making assumptions.
We've been using a custom built plugin system for many years now and we use (sort of) the approach of your IServiceInstaller.
It's all good when it comes to service registration but when it comes to handling middleware it needs coordination (since the middleware is sensitive to ordering). And since all of our plugins essentially know nothing about the other plugins, they cannot coordinate amongst themselves.
So we added some meta-information that forms a dependency graph but it didn't solve the problem really.
This is where I realized that there is no good solution to the problem of coordinating middleware order. Basically we're stuck with a hard-coded setup.
Anyone else been playing around with that kind of setup!?
I didn't work with something like that, unfortunately
What about puting those extestion methods into separate projects that they register? So AddInfractructure to Infrastructure project?
It's an okay approach
Awesome
Thanks Kevin! :)
No no to reflection. First approach with multiple extension files - may be span across different projects is what i am doing.
That's a solid approach, for sure
We've implemented the first approach on project and now I think neither the 1st or 2nd approach is good. Its just adding another layer of complexity for very simple di statements. What I would do now is just create a single extension method and put all the di code there just so to cleanup the main file
How would putting everything in a single extension method help?
About the second one, I reckon we can run it easier.
We have one interface and a lot of implementations, and in this case, we can have something like this IEnumarable and then have foreach to call all of them one by one 🙂 Of course, first of all, we have to register all the implementation)
That's assuming you want to register the implementations, but then the question is how do you resolve them before building the service provider
🙌
👋
Second method seems cool, but it can be cases when order if instantiation must be preserved
When is that important for services?
@@MilanJovanovicTech in our solution (clean architecture) we exposed dependency injection.cs class per project (web, application, infrastructure) to group required Injection. Then these are called in the web project (like services.AddApplicationServices(), or services.AddInfrastructure()). I think that we can use the second approach but we must keep track of the order of assemblies.
@@calinmarian2553 Maybe you could extend the IServiceInstaller with an "int Order { get; }" property?
Great
Thanks
Do you have GitHub code repo with the code sample shown in the video?
No, I share the code on my Patreon
Why don't you make the method return void when creating methods? It should work
To support chaining of method calls
Why not use public partial class and refactor them as individual functions? That way the class inside of Program.cs is spread amonst 50 or so files?
I have a disdain for partial classes
@@MilanJovanovicTech I'm not surprised. But partial classes are what allow code behind.
Looks alot like nopcommerce approach, thank you for sharing tho.
I've worked with Nop before
The reflection approach is a bad idea. Reflection is rule based. Rules must be resolved by other programmers / yourself. It comes in handy when initially creating the project but later when someone else has to maintain it, this person has to resolve that rule base approach, which can be an obstacle. If you write down in a reasonable way - do it. Grouping the service registrations can be an effective way to improve readability and testability (e.g. you can step over a function if you know it works). On the other hand, you need to navigate to more code files. But both approaches don't make the code more robust. So that is something I would consider doing when nothing else is left to do.
There are so many libraries out there that rely on reflection, saying that it's a bad idea is not so straightforward.
fuck yeah
🔥
yet another enumeration through all of the types in an assembly with a filter...
Once at startup, fine by me