Комментарии:
With the IServiceInstaller approach, how do you handle cases where different web apps need different services? As-is, your approach would install everything everywhere.
ОтветитьI've been doing the second options for a while now naming it IDependencyInjectionModule. I really like it because they don't need to live in the same project and I can sub in modules fast especially for tests.
ОтветитьRosyln Code Generator libraries are best for this. Much cleaner than writing abstractions, with no reflection or massive setup files. Add an attribute and it code generates all the service registrations. Some third party registrations still need to exist but your own code becomes a one line registration per assembly. Clean, explicit and fast.
ОтветитьVery nice approach, your all videos are awesome and unique.
Ответитьhi milan! thank you very much for your videos <3
i have a question, why do you choose to return the IServiceCollection in the extension method? is this the same than returning void?
Why don't you make the method return void when creating methods? It should work
ОтветитьAwesome
Ответить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.
Ответить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).
Ответить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
Ответить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.
ОтветитьNo no to reflection. First approach with multiple extension files - may be span across different projects is what i am doing.
ОтветитьYOu actually touched on some good points here, especially when it came to the service installers side of things... Super video! thanks.
Ответить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.
Ответить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
Ответить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.
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
Ответить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'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.
Ответить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.
ОтветитьThanks @Milan, it was a great resourceful video.
ОтветитьNice! I'll apply your approach. Thanks.
ОтветитьGreat lesson. Thank you for sharing these gems for free.
ОтветитьNice and useful. Thank you.
ОтветитьGreat
Ответитьfuck yeah
Ответить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!?
Do you have GitHub code repo with the code sample shown in the video?
Ответить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<IServiceInstaller> and then have foreach to call all of them one by one 🙂 Of course, first of all, we have to register all the implementation)
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.
ОтветитьThanks for introducing super compact and maintainable way of doing better DI @Milan
ОтветитьVery helpful in understanding how to organize ever growing dependencies
ОтветитьThanks for the video
when you use Extension methode , you can avoid returning IServiceCollection no need
You're a programming genius, Milan!
Ответить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 😃
ОтветитьLove aproach with reflection, doing like that for a some time already )
Ответить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.
Second method seems cool, but it can be cases when order if instantiation must be preserved
Ответить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.
Ответить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.
I use the 2nd approach!
ОтветитьGreat approach. I think it's good to add an order property to specify which dependency should be injected first...
Ответить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?
ОтветитьGreat Tutorial, Can you make a tutorial about unit testing and Mocking testing specifically
Ответить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.
Ответить