Structuring Dependency Injection In ASP.NET Core The Right Way

Sdílet
Vložit
  • čas přidán 6. 09. 2024

Komentáře • 133

  • @MilanJovanovicTech
    @MilanJovanovicTech  Před rokem +4

    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

  • @peleacezar6915
    @peleacezar6915 Před rokem +9

    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 😃

  • @MohammadZare90
    @MohammadZare90 Před rokem +10

    Great approach. I think it's good to add an order property to specify which dependency should be injected first...

    • @Rob_III
      @Rob_III Před rokem +3

      I would even consider an attribute (like a ServiceinstallerAttribute) for more configuration options.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      When did you need to order of registration?

    • @MarcusKaseder
      @MarcusKaseder Před rokem +3

      @@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.

    • @aghoghobernard
      @aghoghobernard Před rokem

      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

    • @b000ta
      @b000ta Před 5 měsíci

      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?

  • @montanomariano
    @montanomariano Před rokem +3

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      Would you try out the second approach I suggested?

    • @montanomariano
      @montanomariano Před rokem

      @@MilanJovanovicTech Yes, I think I would. Probably in solutions with complex Startup settings and large amount of projects.

  • @Rob_III
    @Rob_III Před rokem +3

    @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!

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem +1

      Of course, each extension method could have been in its own file. 😅

  • @this-is-bioman
    @this-is-bioman Před rokem +2

    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.

  •  Před rokem +1

    Thanks for the video
    when you use Extension methode , you can avoid returning IServiceCollection no need

  • @Isr5d
    @Isr5d Před 8 měsíci +1

    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 ;).

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před 8 měsíci +1

      I used something similar for my Modular Monolith. Like an "IModuleInstaller"

  • @pw.70
    @pw.70 Před 6 měsíci

    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.

  • @tonycaesar7934
    @tonycaesar7934 Před rokem +12

    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?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem +1

      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.

    • @nothingisreal6345
      @nothingisreal6345 Před rokem +4

      Avoid reflection. It is an indirection hard to resolve and uncessary.

  • @Swzvtlbngfd
    @Swzvtlbngfd Před rokem +1

    Very helpful in understanding how to organize ever growing dependencies

  • @dcuccia
    @dcuccia Před rokem +1

    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
      @MilanJovanovicTech  Před rokem

      That would be super interesting to explore! I'll see what I can make of it 😁

    • @dcuccia
      @dcuccia Před rokem

      @@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.

  • @stef2528
    @stef2528 Před 2 měsíci

    Awesome! Exactly what I was looking for. Thank you Milan.

  • @vikasjoshi4708
    @vikasjoshi4708 Před rokem +1

    Thanks for introducing super compact and maintainable way of doing better DI @Milan

  • @lazykoder
    @lazykoder Před rokem +1

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem +1

      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

  • @nage7447
    @nage7447 Před rokem +1

    Love aproach with reflection, doing like that for a some time already )

  • @MrFunkyCabbage
    @MrFunkyCabbage Před rokem +1

    YOu actually touched on some good points here, especially when it came to the service installers side of things... Super video! thanks.

  • @rhlee063
    @rhlee063 Před rokem +1

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem +1

      I have to be slow to make this video longer, that's how I earn my daily bread 😅

    • @rhlee063
      @rhlee063 Před rokem

      @@MilanJovanovicTech you did really well and appreciate your content

  • @mrsajjad30
    @mrsajjad30 Před rokem +1

    Great lesson. Thank you for sharing these gems for free.

  • @shakib4845
    @shakib4845 Před rokem +8

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem +4

      Application startup time is usually the least of my concerns

    • @krccmsitp2884
      @krccmsitp2884 Před rokem +1

      The source generator idea sounds quite interesting. Would like to see a sample implementation.

    • @shakib4845
      @shakib4845 Před rokem +1

      @@MilanJovanovicTech when the project grows up and specialy if you use auto-scaling system like kubernates, stratup time concern gets bolder.

    • @danijelzg001
      @danijelzg001 Před rokem +3

      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...

    • @shakib4845
      @shakib4845 Před rokem +1

      @@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

  • @emwagner
    @emwagner Před rokem +1

    You're a programming genius, Milan!

  • @nevernerd
    @nevernerd Před rokem +1

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      Doesn't it make sense for each microservice to handle it's own registrations?

  • @anthonybianue9206
    @anthonybianue9206 Před rokem +1

    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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      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.

  • @OleksiiKorniienko
    @OleksiiKorniienko Před rokem +2

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem +1

      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.

  • @jacobstamm
    @jacobstamm Před 11 měsíci +1

    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
      @MilanJovanovicTech  Před 11 měsíci

      You can easily solve this by passing in the specific assembly to scan for IServiceInstaller implementations

    • @jacobstamm
      @jacobstamm Před 11 měsíci

      @@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?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před 11 měsíci +1

      @@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.

  • @riccarrasquilla379
    @riccarrasquilla379 Před 7 měsíci +1

    thanks for the video

  • @mahi.tshooter
    @mahi.tshooter Před rokem +1

    Thanks @Milan, it was a great resourceful video.

  • @InshuMussu
    @InshuMussu Před 11 měsíci +1

    Very nice approach, your all videos are awesome and unique.

  • @antonmartyniuk
    @antonmartyniuk Před rokem +1

    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

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      Agreed. That would be awesome if something can be done with source generators

  • @paikesitics
    @paikesitics Před rokem +1

    Nice! I'll apply your approach. Thanks.

  • @sanampakuwal
    @sanampakuwal Před rokem +1

    I use the 2nd approach!

  • @MarcusKaseder
    @MarcusKaseder Před rokem +2

    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
      @MilanJovanovicTech  Před rokem +2

      Agreed, there's more than just configuring services. How do you apply that analyzer for Startup.cs size?

    • @MarcusKaseder
      @MarcusKaseder Před rokem

      @@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 :)

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      @@MarcusKaseder Very interesting rule!

  • @avecesar
    @avecesar Před rokem +1

    Nice and useful. Thank you.

  • @andreybiryulin7207
    @andreybiryulin7207 Před rokem +1

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      You can register distributed cache with both AddFeatureX and AddFeatureY using TryAddSingleton for example

    • @timur2887
      @timur2887 Před 6 měsíci

      @@MilanJovanovicTech btw, TryAdd method iterates through all services each call. I wonder why it doesn't still use a dictionary to locate a service...

  • @CSharp_Dev2022
    @CSharp_Dev2022 Před rokem +1

    Great Tutorial, Can you make a tutorial about unit testing and Mocking testing specifically

    • @MrBan001
      @MrBan001 Před rokem

      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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      Yes!

  • @javifrancia
    @javifrancia Před rokem +1

    hi milan! thank you very much for your videos

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      So I can do method chaining like:
      AddApplication().AddInfrastructure().AddPersistence()

  • @spaarkytv6526
    @spaarkytv6526 Před 4 měsíci

    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?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před 4 měsíci +1

      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

  • @Krilllind
    @Krilllind Před rokem +1

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      We can measure it, I'm sure it's not a significant performance impact.

    • @Krilllind
      @Krilllind Před rokem +1

      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.

  • @CarmenSantiNova
    @CarmenSantiNova Před rokem +1

    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!?

  •  Před 5 měsíci

    What about puting those extestion methods into separate projects that they register? So AddInfractructure to Infrastructure project?

  • @kamarchand
    @kamarchand Před rokem +1

    Awesome

  • @RajeshAravapalliAZ09
    @RajeshAravapalliAZ09 Před rokem +1

    No no to reflection. First approach with multiple extension files - may be span across different projects is what i am doing.

  • @ProtectedClassTest
    @ProtectedClassTest Před rokem +1

    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

  • @kostiantynshyshkovskyi630

    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
      @MilanJovanovicTech  Před rokem

      That's assuming you want to register the implementations, but then the question is how do you resolve them before building the service provider

  • @techpc5453
    @techpc5453 Před rokem +1

  • @calinmarian2553
    @calinmarian2553 Před rokem +1

    Second method seems cool, but it can be cases when order if instantiation must be preserved

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      When is that important for services?

    • @calinmarian2553
      @calinmarian2553 Před rokem

      @@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.

    • @krccmsitp2884
      @krccmsitp2884 Před rokem +1

      @@calinmarian2553 Maybe you could extend the IServiceInstaller with an "int Order { get; }" property?

  • @ryoman76
    @ryoman76 Před rokem +1

    Great

  • @CyborgT800
    @CyborgT800 Před rokem +1

    Do you have GitHub code repo with the code sample shown in the video?

  • @andreymetkiy9052
    @andreymetkiy9052 Před rokem +1

    Why don't you make the method return void when creating methods? It should work

  • @kurtnelle
    @kurtnelle Před rokem +1

    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?

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      I have a disdain for partial classes

    • @kurtnelle
      @kurtnelle Před rokem

      @@MilanJovanovicTech I'm not surprised. But partial classes are what allow code behind.

  • @b0ysb3
    @b0ysb3 Před 5 měsíci +1

    Looks alot like nopcommerce approach, thank you for sharing tho.

  • @nothingisreal6345
    @nothingisreal6345 Před rokem +2

    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.

    • @MilanJovanovicTech
      @MilanJovanovicTech  Před rokem

      There are so many libraries out there that rely on reflection, saying that it's a bad idea is not so straightforward.

  • @Caldaron
    @Caldaron Před rokem +1

    fuck yeah

  • @timur2887
    @timur2887 Před 6 měsíci +1

    yet another enumeration through all of the types in an assembly with a filter...