Something that I have never liked about the reflection based scanning is there is not clarity on how the dependencies are being loaded and there is a lot of implicit "magic" that needs to be explained before somebody who is not used to it works in a given code base. Also, this is on top of the DI container itself, which already has a lot of "sorcery" on it. That happened in a project I worked on, where we have to explain a person that just got onboard how the dependencies were being loaded and the conventions followed. What you get on "cleanness" you lose it in readability, and I believe (and studies have stated) the second is actually preferable.
The cleanest approach (?) weather you use assembly scanning or call installers one by one is to have the installer file inside the assembly it is installing. This way only type that Startup.cs containing assembly needs to have access to is the installer itself (at least for registration purposes) - everything else can be internal and invisible to the Startup.cs assembly. The type can also be used as assembly marker. This approach also facilitates having less dependencies for intagration tests as you can just pick up the Installer/Module thingy and register one module if you like.
Something to consider, if you have multiple Add*From*() methods that trickle down like in your example, consider one method that takes an IEnumerable and then create convenience methods that generate the IEnumerable for different reflection schemes. Similar code, but refactors and separates the assembly traversal code to one module that only does reflection and another that only does Dependency Injection. This also allows the user to write their own reflection traversal if needed.
Using this idea, a nice reusable fluent reflection module could be written that might look like ClassesInSameNamespaceAs(typeof(IAPIAssemblyMarker)).ThatImplementInterface(IMyInterface).ToEnumerable()
Hey Nick, a suggestion, please link the videos that you talk about that are prerequisites to the concept you are talking about, thanks for the great content!
Assembly scanning can be very convenient, however the drawback is the performance hit due to reflection. Source generation can help to solve that, however they are quite complex. So a middle ground could be to use assembly scanning in a unit test to verify all types are accounted for.
It’s only the application’s startup that will be a few microseconds slower. There is no real drawback and to be honest I’d rather have my startup be a few microseconds slower than my compilation a few seconds for the source generators
Is it really that hard to write an extension on IServiceCollection to do the registration somewhere else? I think the benefit of clear, visible dependency registration for the sake of other developers understanding what's going on quickly and easily greatly outweighs reducing the code by a few lines. On the one hand, don't jam everything in the Startup file. On the other hand, if your service registrations are starting to look like spaghetti you may want to rethink the size of the service itself, unless you're into the monolithic life :)
Nick, do you know is it possible to do some dynamic assembly discovery/loading to implement pluggable architecture in net core / 5+? In net framework was AppDomain and ability to scan folders for assemblies that are not referenced directly and not loaded with application to load them as plugins, is there any alternative in net core, maybe manipulations with deps.json?
@Nick - Out of context question - Is any big company architecture use net core for its microservice architecture and any resources for that i need to check the complex problem
How does this work for services/assemblies that need to be created in a specific order? A common example would be services.AddAuthorization() and services.AddAuthentication()
Hello, Guys, I am using repository patterns for building WebApps and API project for a couple of years and I need to create one API project for large enterprise so should consider clean architecture pattern or stick to the repository pattern as I am not much familiar with the clean architecture design Also, it would be really helpful if you can provide advantages of clean architecture over a repository pattern designed and if there are any other patterns that you think is better than both of them then you can share that to Thank you
I don't get it. Why not use simple extension methods for registering groups of services? Less code, less reflection, less project dependencys, but much cleaner and convenient. Like written about in the Microsoft docs for AspNet Core dependency injection.
Totally agree - this is what I've always done and it's clean, clear and concise. You can see what exactly is being registered, how it's scoped, plainly and easily
Tricks/tools like this may have the "cool" factor (and believe me, I love the cool factor), but I'll never reach for a tool if it relies heavily on reflection. In my opinion, it's just unnecessary overhead.
@@Velociapcior I was speaking in a general sense. I've worked on many projects where libraries such as Spring DI increased the application startup time to over a minute (in a large application). Reflection can sometimes cripple an application and degrade the UX. Never underestimate how a single architectural decision can ruin the experience for your users. Just like conversations, first impressions count.
@@acuddlycactus I do agree with you that using reflection in business process is a very bad idea. Nevertheless starting application is not a business process. And if application starts for over a minute, it's just to big
Strongly disagree about the assembly marker interface type, because you're just cluttering your assembly with unneeded public types. Yes, you could of course make it internal, but still it seems forced. Just use any type of the assembly, as you did before. The whole problem could be prevented though, if C# had the possibility of specifying an assembly by name, just like the type...
Marker interfaces are frowned upon in official microsoft docs too and are explicitly called an anti pattern. Link: docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1040
For assembly scanning I prefer specifying some base type that is relevant to whole assembly's purpose. I use marker interfaces only if some classes really implement them, to be later inspected by reflection, it makes more sense than assembly level marker interface.
Something that I have never liked about the reflection based scanning is there is not clarity on how the dependencies are being loaded and there is a lot of implicit "magic" that needs to be explained before somebody who is not used to it works in a given code base. Also, this is on top of the DI container itself, which already has a lot of "sorcery" on it. That happened in a project I worked on, where we have to explain a person that just got onboard how the dependencies were being loaded and the conventions followed. What you get on "cleanness" you lose it in readability, and I believe (and studies have stated) the second is actually preferable.
+1
I very much dislike classes magically being hooked up just because they exist and use some attribute.
Hey Nick, thanks for the awesome videos. Are you planning or releasing a couple of videos about DDD?
The cleanest approach (?) weather you use assembly scanning or call installers one by one is to have the installer file inside the assembly it is installing.
This way only type that Startup.cs containing assembly needs to have access to is the installer itself (at least for registration purposes) - everything else can be internal and invisible to the Startup.cs assembly. The type can also be used as assembly marker.
This approach also facilitates having less dependencies for intagration tests as you can just pick up the Installer/Module thingy and register one module if you like.
Awesome! Today i was just trying to make my services registering more cleaner :) now i now the path!!!
Something to consider, if you have multiple Add*From*() methods that trickle down like in your example, consider one method that takes an IEnumerable and then create convenience methods that generate the IEnumerable for different reflection schemes. Similar code, but refactors and separates the assembly traversal code to one module that only does reflection and another that only does Dependency Injection. This also allows the user to write their own reflection traversal if needed.
There is such a method in the video.
Using this idea, a nice reusable fluent reflection module could be written that might look like ClassesInSameNamespaceAs(typeof(IAPIAssemblyMarker)).ThatImplementInterface(IMyInterface).ToEnumerable()
@@aughey I see what you mean now. yeah that's possible, but at that point you should be using Scrutor that already does all that for you.
Hey Nick, a suggestion, please link the videos that you talk about that are prerequisites to the concept you are talking about, thanks for the great content!
Nice. I remember doing this with Castle Windsor but couldnt figure it out for MS DI framework.
Assembly scanning can be very convenient, however the drawback is the performance hit due to reflection. Source generation can help to solve that, however they are quite complex. So a middle ground could be to use assembly scanning in a unit test to verify all types are accounted for.
It’s only the application’s startup that will be a few microseconds slower. There is no real drawback and to be honest I’d rather have my startup be a few microseconds slower than my compilation a few seconds for the source generators
Note that you should also often filter out generic types in your assembly scanning
Awesome stuff Nick. My startup.cs extensions were getting ugly!
Is it really that hard to write an extension on IServiceCollection to do the registration somewhere else? I think the benefit of clear, visible dependency registration for the sake of other developers understanding what's going on quickly and easily greatly outweighs reducing the code by a few lines. On the one hand, don't jam everything in the Startup file. On the other hand, if your service registrations are starting to look like spaghetti you may want to rethink the size of the service itself, unless you're into the monolithic life :)
It's not. If you watched the full video, this is the exact advice I give at the end.
@@nickchapsas Nick, it appears I've made a fool of myself.
Hey Nick, I have a little off-topic question. What plugins do you use for Rider? What do you think is a must have?
Greetings from Germany
Nick, do you know is it possible to do some dynamic assembly discovery/loading to implement pluggable architecture in net core / 5+? In net framework was AppDomain and ability to scan folders for assemblies that are not referenced directly and not loaded with application to load them as plugins, is there any alternative in net core, maybe manipulations with deps.json?
"All those packages, popular ones", I don't think I've heard a single one of them so that's a good start for me..
Thank you for this mate!
@Nick - Out of context question - Is any big company architecture use net core for its microservice architecture and any resources for that i need to check the complex problem
.NET 6 have a new cleaner mechanism for startup.
How does this work for services/assemblies that need to be created in a specific order? A common example would be services.AddAuthorization() and services.AddAuthentication()
That's why the IInstaller has an Order property. You prioritize the different calls in the right order
Hello, Guys, I am using repository patterns for building WebApps and API project for a couple of years and I need to create one API project for large enterprise so should consider clean architecture pattern or stick to the repository pattern as I am not much familiar with the clean architecture design
Also, it would be really helpful if you can provide advantages of clean architecture over a repository pattern designed and if there are any other patterns that you think is better than both of them then you can share that to
Thank you
Would be interesting to see if you could make this source generator-based.
You could technically could there is absolutely no point except from some microseconds less during startup
@@nickchapsas which is why I said "interesting". There are always trade-offs and measuring would show if the effort is worth it or not.
I don't get it. Why not use simple extension methods for registering groups of services? Less code, less reflection, less project dependencys, but much cleaner and convenient. Like written about in the Microsoft docs for AspNet Core dependency injection.
Totally agree - this is what I've always done and it's clean, clear and concise. You can see what exactly is being registered, how it's scoped, plainly and easily
hey Nick, non-related question, what's your opinion about Kotlin?
Really good language
Microsoft could have an assembly scanning for EF Mapping Fluent API classes, I always use a method that I built in every project.
Is this basically Scrutor?
Scrutor is a wrapper around assembly scanning
Yet another abstraction for abstraction
60% of .NET works on reflection!
Tricks/tools like this may have the "cool" factor (and believe me, I love the cool factor), but I'll never reach for a tool if it relies heavily on reflection. In my opinion, it's just unnecessary overhead.
but this code only executes at the start of application and these are only few methods it's not a lot of overhead
@@Velociapcior I was speaking in a general sense. I've worked on many projects where libraries such as Spring DI increased the application startup time to over a minute (in a large application). Reflection can sometimes cripple an application and degrade the UX. Never underestimate how a single architectural decision can ruin the experience for your users. Just like conversations, first impressions count.
@@acuddlycactus I do agree with you that using reflection in business process is a very bad idea. Nevertheless starting application is not a business process. And if application starts for over a minute, it's just to big
Strongly disagree about the assembly marker interface type, because you're just cluttering your assembly with unneeded public types. Yes, you could of course make it internal, but still it seems forced. Just use any type of the assembly, as you did before. The whole problem could be prevented though, if C# had the possibility of specifying an assembly by name, just like the type...
You shouldn't make it internal because you might wanna call the marker for a project that is a dependency to the main project.
Marker interfaces are frowned upon in official microsoft docs too and are explicitly called an anti pattern.
Link: docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1040
For assembly scanning I prefer specifying some base type that is relevant to whole assembly's purpose. I use marker interfaces only if some classes really implement them, to be later inspected by reflection, it makes more sense than assembly level marker interface.
I'm always first!