Validate your dependencies correctly in .NET

  Рет қаралды 36,439

Nick Chapsas

Nick Chapsas

Жыл бұрын

Check out the ABP Framework here: bit.ly/3UmfRbh
Check out my courses: dometrain.com
Become a Patreon and get source code access: / nickchapsas
Hello everybody I'm Nick and in this video I will show you how you can validate your dependencies that are resolved through your dependency injection container before it's a problem that takes down your application.
This video is sponsored by abp.io.
Don't forget to comment, like and subscribe :)
Social Media:
Follow me on GitHub: bit.ly/ChapsasGitHub
Follow me on Twitter: bit.ly/ChapsasTwitter
Connect on LinkedIn: bit.ly/ChapsasLinkedIn
Keep coding merch: keepcoding.shop
#csharp #dotnet

Пікірлер: 129
@nickchapsas
@nickchapsas Жыл бұрын
To those saying that this simply moves the problem to a different location, allow me to disagree and here is why. The 2 main problems this is trying to solve, is preventing the app from blowing up during runtime because a service wasn't registered and ensuring that it was registered with the correct lifetime. On surface level you might assume that this won't do anything or that you should just scan the assembly and check by convention but you are missing two things. Firstly, with how much registration is done by convention in DI, it is really hard to know by heart what is being registered and how. It might look like something is registered just because you have an AddXXX() call but in reality it wasn't. Devs in the team need to make continuous effort to bake this into their workflow otherwise it won't work. Second, even if a service is registered, are you sure that it was registered with the right lifecycle? When you do AddMediatR do you know which dependency lifetime is used for your handlers and is that what you want it to be? This approach allows you to check for all that. You don't have to use it, but saying that it is simply shifting the problem is very shortsighted. I could make this work purely using assembly scanning to detect unregistered dependencies but its error prone. I'd rather give you something less practical, but reliable and have you adjust it to your needs than give you something that on the surface looks good, but causes more problems than it solves down the road.
@Coen22
@Coen22 Жыл бұрын
This is a good approach, but maybe you could replace the _descriptors list with some reflection code that finds all classes with the [ApiController] attribute and parses each (readonly) field to identify which services are required for execution. Of course, you wouldn't know if you're using the correct implementation of a service, but you would know that each service has an implementation.
@nickchapsas
@nickchapsas Жыл бұрын
@@Coen22 You assume 2 things. First that that all controllers live in your own assembly, which might not be the case (think health check and metrics controllers) and that you will be working with an WebAPI. What about minimal apis? What about background services? What about long running consumers? There are too many variables to reliably scan for
@Coen22
@Coen22 Жыл бұрын
@@nickchapsas No, of course, this would not work with minimal APIs and it would only work within your own assembly. However, using the information about your own assembly you could still automate part of the dependency checking.
@rmichaeldeutsch
@rmichaeldeutsch Жыл бұрын
But how is this better (other than it being faster to run the test) than using that very same WebApplicationFactory, and spinning up an in-memory server and just hitting the controllers with a basic request. Integration/Functional tests would cover this issue and more! I'm struggling to see the benefit of this approach?
@nickchapsas
@nickchapsas Жыл бұрын
@@rmichaeldeutsch Because actually letting the WAF to spin up the application means that your application will actually run, which might mean things like database migrations, connections to the filesystem, networks etc. This approach prevents that by shortcircuiting the app's init.
@HugRunner
@HugRunner Жыл бұрын
This is a pretty good "trick" but as many mentioned, you might forget to add the service in the test, just like you would forget it in the startup. And if you remember to add it to the test, you most likely have added it to the startup as well, so this might be redundant. The same problem is there for things like mappings in AutoMapper. If you forgot to add a map or made something wrong with it, it will fail in runtime.
@abousias
@abousias Жыл бұрын
Well done Nick! I am afraid you can easily forget to register the service for the test and even worse you register it for the app but not for the test. In the past I successfully used a middleware to validate all IoC registrations, but only available in development mode. Which makes it fail in all development workstations as well as integration tests. With all the controllers and services deriving from known base classes or implementing known interfaces and with the power of reflection, we were resolving all instances and failing if we couldn't (maybe there was a problem resolving "open generics" with that but not so sure anymore). As a fail-safe I would prefer that, unless the performance hit is so significant that it bothers fellow developers :)
@nickchapsas
@nickchapsas Жыл бұрын
Middleware can only work if all your entry points are your controllers which eliminate things like background tasks or long running services. The main idea here is here is that if you introduced something like this, then everyone in the team would be aligned to look for it. There is no perfect solution, but I prefer this over something that will fail during runtime or leave registration to complete chance
@abousias
@abousias Жыл бұрын
I agree that this needs to be done in every project using IoC and it is usually an afterthought. This also means for me that if it happens once, it will happen again and forgetting to register you new service to be tested is a risk. For background tasks and other types not involving http requests, I would say do the same. Using a development flag have a validation happen at the startup. If you don't even like the middleware or startup, you can always create a test, using reflection to detect what is supposed to be registered by convention and initialize the service provider to check if you can get an instance (required) or not.
@rxuhwxx4tmly9xvcktcn2znpsx4
@rxuhwxx4tmly9xvcktcn2znpsx4 Жыл бұрын
Doesn't this just shift the problem? Instead of forgetting to register the service you can now forget to add the service to the test. I do think this is better than before but you still have to manually configure validation. Also can you use the same approach for non-ASP DI? I use DI in desktop applications, which always needed some extra tweaks because most DI libs are expected to work with ASP.
@nickchapsas
@nickchapsas Жыл бұрын
There is nothing you can do about that bit mainly because services can be registered in so many ways, like extension methods or reflection, which something like an analyzer wouldn't be able to detect.
@DotNetCookbook
@DotNetCookbook Жыл бұрын
@@nickchapsas true, but how often is that the case? I still think that some kind of static analysis could cover 90-95% of realistic use cases.
@KirillKrylovTo
@KirillKrylovTo Жыл бұрын
@@nickchapsas you can mark services that are intended for injection with a custom attribute and then scan for it in your test, this however would simply push responsibility further down, but now solve the problem.
@diadetediotedio6918
@diadetediotedio6918 Жыл бұрын
​@@nickchapsas I think if an analyzer seeks for specific calls for IServiceCollection it surely can detect statically, after all in the end of the "call stack" there is an AddSingleton, AddScoped or AddTransient
@diadetediotedio6918
@diadetediotedio6918 Жыл бұрын
Yet with reflection it wouldn't work
Жыл бұрын
In my old dotnet4.8 app I just use assembly scan to get all controllers and try to create each of them using a simple service locator. You do that once and can safely forget about it all.
@TristanGoetz
@TristanGoetz Жыл бұрын
Nick, Thank you so much for this video and giving some insights to better ways to check for missing dependency registrations. I think this works great for certain people and use cases, however I can see and partially agree with some of the people saying that if you forgot to register the service in the Startup class, then you are also likely going to forget to list the service in the test as well. I think it really comes down to being extra diligent when pushing your code and testing it before making it into a live system. I believe this solution works on a case by case basis for each developer and company. Really appreciate your videos though. Keep it up. Your videos have helped me want to start my own coding channel.
@nickchapsas
@nickchapsas Жыл бұрын
I 100% agree with your comment. It is not a perfect solution because there can't be a perfect solution, unless you use a 3rd party dependency injection framework which natively supports this sort of validation.
@ilia__k
@ilia__k Жыл бұрын
We create a blank container and populate it using the Configuration method used in business logic. We also build it with additional validation, but then we resolve all of our services from this container. Unless you are using something like MediatR that hides actual dependencies, it is possible to just scan assembly. For instance, working in Azure, we react to all classes containing FunctionNameAttribute, as these are our entry points. Test these and any IRequestHabdlers and you are good to go.
@AlFasGD
@AlFasGD Жыл бұрын
Coming from somebody that knows how to approach general system design, but never having touched web apps in .NET 5+: This barely helps, as it stands. The problem you proposed was that your app blows up when you call the certain endpoint that utilizes the missing service. The solution is to make sure that upon building the service is registered. While the idea is basically what you did, you should *automate* that process, not move your hands in two places with every new controller dependency. Constructors are already bad enough, you make this even worse by having to specify the same dependency's existence in yet another place, with one more mapping. There's so many things that can and do go wrong when human hands are involved. To address this, you would need to construct a solution using reflection. It would specifically detect all the services that are explicitly injected in your controllers, and voila. You don't quite care about their lifetime, and if you do you can circumvent that by specifying the requested lifetime of the service either on the constructor, mapping the parameter type to the lifetime, or the parameter itself, using a custom attribute denoting the lifetime. Overall, that's a nice idea for a custom GitHub project, which is definitely happening as we speak. However, your current solution is not directly usable and would barely benefit anyone. If you forget to add the dependency once, you forget to add it everywhere. It's basically the same as writing tests for the existence of tests, you add more responsibilities to yourself.
@nickchapsas
@nickchapsas Жыл бұрын
You can never reliably cover all cases using reflection. You are assuming that every .NET app is a Web API. Iit is not. What about Blazor components? What about Razor pages? What about BackgroundServices? What about Minimal APIs? What about long running consumers? What about all of the above that leave in references assemblies and references assemblies of references assemblies? Reflection is what I was using before and it proved and unreliable approach even when we thought we covered all the edge cases.
@AlFasGD
@AlFasGD Жыл бұрын
@@nickchapsas The point of such a testing library/tool is to cover the specific framework you're using, so you'd have one for Blazor components, one for Razor pages, etc. Likewise how you have unique packages for NUnit, xUnit, MSTest, while they all share the same core library. You have a reliability insecurity, you address it for your specific domain. If you wanna go further than that do your R&D, or discover what others have come up with. This approach you showed is massively flawed in the sense that you feel that it's automated when it's in fact not quite, and still relies on hands for half the changes you'll make in the domain you're testing.
@cheequsharma7391
@cheequsharma7391 Жыл бұрын
Thanks Nick, This is by far the best we can do to validate. This is to find the missing dependency , do we have any way to avoid captive dependency.!
@Sanabalis
@Sanabalis Жыл бұрын
I like this approach quite a lot, though I dislike manually adding dependencies. So let me propose a couple of possible ideas. 1. Keep the manual input to cover cases where automated scans miss something. 2. Automatically search for all Controllers, and add them to the scan list, also adding controller parameter types and each public method parameter type if it's marked with FromServiceAttribute. 3. For each registered dependency (either manually listed or automatically found), scan the implementation class and add all the controller parameter types to the scan list (hash set instead of normal list)- with simplified logic for resolving a lifetime or ignoring it. 4. Keep scanning and verifying implementations until the entire dependency tree is resolved. 5. Take care of certain strange cases, such as Open Generics. I think I'll try this approach over the next day or two and let you know if it worked and how successful it was. Edited: The implementation is here: github.com/Sanabalis/DependencyValidation
@FrankShearar
@FrankShearar Жыл бұрын
This approach works great: for years now this has been one of the first tests I write for a new service.
@tomdaniel1677
@tomdaniel1677 Жыл бұрын
Any results to share yet? I'm very curious & want to take this approach as well.
@ChristianAltWibbing
@ChristianAltWibbing Жыл бұрын
thank you Nick for this good video. i wrote a test using Reflection to go through the registered classes (which come from our namespace) and then try to call them (after the build) with GetRequiredServices. If a class then fails to resolve, then I get the error you show. This works very well, although much slower.
@jtucker87
@jtucker87 Жыл бұрын
You state the problem is that you forgot to register the class when you added it. This doesn't correct that issue at all. Add a new service and forget to register it and you'll get exactly the same behaviour out of the app.
@nickchapsas
@nickchapsas Жыл бұрын
Not quite. Having a dedicated approach that everyone in the team is aligned to, will catch more issues on pull requests that simply not having it at all. It is not bullet proof and you could use assembly scanning to dynamically build the dictionary, but it’s definitely better than having something blow up during runtime
@jtucker87
@jtucker87 Жыл бұрын
@@nickchapsas Assembly scanning would help. Having the team check this vs checking the Program.cs during the PR doesn't seem any different to me. If they miss a new service being added to the DI they're going to miss it being added to the test too. IMHO Adding that scanning elevates this to a good solution in my eyes. But I'd really like to know how to do it in xUnit. Not a big of fan of having a single set of tests in NUnit just for this.
@nickchapsas
@nickchapsas Жыл бұрын
@@jtucker87 There is actually a huge difference. Registration can happen by convention, so you just can't know what is being registared and how just by looking at the program.cs or startup.cs. Those Add methods can live anywhere, even in assemblies you don't own the code for.
@jtucker87
@jtucker87 Жыл бұрын
@@nickchapsas Thanks for pointing that out. It gives a flat view that maybe gets lost in a more complex system. Something I hadn't thought of even through my teams uses similar conventions. We have '...Setup' extension classes that have a lot of our DI.
@DanielOpitz
@DanielOpitz Жыл бұрын
I use a different approach. I scan all assembly types within my App Namespace and register them into a scope. There is also the Scrutor or the Q101.ServiceCollectionExtensions package that does something very similar.
@F4C31355
@F4C31355 Жыл бұрын
In our team we have a rule to test all code developer produced. In addition to good integration test coverage it leads us to situation when we have never seen issues like that on prod. However, I find this video very useful and I think it is good idea to create separate test case to validate DI depencencies.
@lndcobra
@lndcobra Жыл бұрын
I think time is better spent creating tests that actually call the endpoints. But always good to see options!
@DanielOliveira-jd7xh
@DanielOliveira-jd7xh Жыл бұрын
Great tip! Thanks!
@markfay9649
@markfay9649 Жыл бұрын
I would be interested in seeing your approach to testing Implementation Factory registrations. We have a similar approach (more functional though) using the WAF, but we inject Mock external dependencies, then call the Api using WAF.HttpClient.
@Brandon-Shaw
@Brandon-Shaw Жыл бұрын
That would be very interesting. We make extensive use of factory registration.
@romanciesielski10
@romanciesielski10 Жыл бұрын
I use the same what was introduced in video + test my extensions which add to services
@ig88ds
@ig88ds Жыл бұрын
I have done tests similar to this in the past with .NET Framework and .NET Core back when we had Startup.cs. I would build a service provider using Startup then try to resolve my Controllers. I would do this dynamically by using reflection to find my controllers.
@Rolandtheking
@Rolandtheking Жыл бұрын
We are currently tacking this with the -- castle windsor diagnostics "PotentialLifestyleMismatchesDiagnostic" class. Works wonders, but might not work in this example due to different dependencies etc. This runs in a unit test and is only 2 lines of code, e.g. install the container and run this unit. It tracks lifestyle problems, and missing registrations.
@nickchapsas
@nickchapsas Жыл бұрын
How does Windsor's performance compare to the build in DI container?
@clementdurazzo4774
@clementdurazzo4774 Жыл бұрын
We do basically the same thing but with a service collection called with an extension method that groups all of our internal dependencies. Thought can this method include some external DI like autofac module ? And you still have to maintain that list to validate. How can you be sure that it’s up to date ? We had few updates with reviewer missing this update so test is going well and deployment not so much. It’s especially hard to maintain when you use conditional dependencies like decoration based on environment variable. It would be cool if we could try instantiating each and every type… just to be sure ! Or scan each and every constructor to ensure that’s dependency are defined 🤔. It would be time consuming thought
@ahmeddabas
@ahmeddabas Жыл бұрын
Great vid , can u make a tutorial about bounded context and how communicate between each other pls
@OhhCrapGuy
@OhhCrapGuy Жыл бұрын
This is why I built an Attribute based injection system to handle registering implementations. It allows different builds (Test, Debug, Release) and environments (Dev, QA, Prod) to provide different implementations, and allow the injected type's attribute to specify lifetime (Transient, Scoped, Singleton). It uses reflection to load all types in all assemblies in the Environment.CurrentDirectory on startup, search them for the correct attributes, and add them to the services according to the attribute values. There's a single unit test to make sure ALL interfaces tagged with the InjectableContract attribute have a paired implementation with a InjectableImplementation. (It actually allows abstract and concrete classes to have the InjectableContract attribute, but it writes to StdErr with a warning on startup every time) Because the implementations specify which environments and builds they're allowed to be used in with flagged enums, I can simply iterate through all enumerated values for both and ensure that there's always at least one implementation for every contract for every build for every environment. Each implementation also has a required SimpleName, a string in the Attribute constructor, and a correlated option that it checks the IConfiguration for, so if multiple implementations exist in the current build and environment, it exits and explains that multiple implementations for that Contract exist, their SimpleNames, FullName of their type and the Assembly, and what option in the app settings JSON file (or what system environment variable) needs to be set to specify which implementation is requested. It's made almost all injection issues disappear, while also making the few that remain show up as an error during building the Host, rather than runtime, a HUGE benefit for junior devs who aren't used to the principles of DI.
@OhhCrapGuy
@OhhCrapGuy Жыл бұрын
I built it for work, so it's proprietary code, I can't publish it, but I do plan on reimplementing the whole thing from scratch and making it open source. It's just so much better than all the other frustrating DI registration schemes I've had to deal with.
@andreybiryulin7207
@andreybiryulin7207 Жыл бұрын
It sounds like it doesn’t work in case you have domain project and 2 web apps using it, but want to bind implementations differently. Is that right?
@OhhCrapGuy
@OhhCrapGuy Жыл бұрын
@@andreybiryulin7207 It is optional, if you don't use the attributes for an interface and implementation, you can just add them like you normally would. As for multiple projects, you can just give each implementation a different SimpleName, then it will require you to specify which one you want to use in your appsettings.json file. Or if the implementations are in different assemblies, only include the assembly with the desired implementation in the project references for each project.
@OhhCrapGuy
@OhhCrapGuy Жыл бұрын
@@andreybiryulin7207 In one case, we have 12+ implementations of one interface, so we have to specify which one we want to use in the app settings.
@rade6063
@rade6063 Жыл бұрын
Hey Nick, Will you be posting VOD of your livestream with RawCoding on your podcast chanel, or will it be only for your paid subscribers on main chanel?
@nickchapsas
@nickchapsas Жыл бұрын
It is coming on Saturday.
@jamesbennett5421
@jamesbennett5421 Жыл бұрын
Interesting approach. I’ll skip the “is it moving the problem” concern and just ponder how a mere mortal would come up with this solution on their own. As a pre-Google/pre-YT developer, I’m both amazed and dismayed at the depth of knowledge required nowadays to do something “right”. BZ to Nick though - thanks for sharing your wisdom.
@victor1882
@victor1882 Жыл бұрын
14:34 I'm confused, what about StrongInject?
@_simoncurtis
@_simoncurtis Жыл бұрын
Seems like something that roysln analyzers could help with
@nickchapsas
@nickchapsas Жыл бұрын
There is a lot of registration happening with reflection so an analyzer wouldn't do much here
@_simoncurtis
@_simoncurtis Жыл бұрын
@@nickchapsas of course there are always edge cases that your tests cover. But, roslyn analzyers can do reflection too and could show you in real time if you are missing a dependancy in startup.cs (or program.cs). Would cover 90% (complete guess) of issues. CustomAttributes could be used on another 5% instruct the analyser of the intent of extension methods in external classlibs (if you don't want to scan that too). This leaves 5% of edge cases of which of course your tests do cover too, so I'm not discounting it
@dennycrane2938
@dennycrane2938 Жыл бұрын
I feel DI is static enough to validate without setup; it's basically config in that everything you need to know in order to determine whether or not your container is complete should be available to you at build time. Could you write something that dug through the service collection, separate registered services by scope, then uses reflection to dig through all of the constructors and checks that those services are also registered and with the correctly lifetime; If not, fail. The whole idea that in order to avoid forgetting to remember to register something is to remember to essentially register it somewhere else in order to keep track of the things that may have been forgotten (lol?) is just making me roll my eyes.
@StonkExchange
@StonkExchange Жыл бұрын
You can just scan the assembly for children of ControllerBase using reflection. Then adding those controllers as scoped to the DI containers. Also make sure that you call UseDefaultServiceProvider() and turn on the validations
@StonkExchange
@StonkExchange Жыл бұрын
See my gist as example: frenchgrandpa/c1c7fe346ca785734a5caeeff651163e
@nickchapsas
@nickchapsas Жыл бұрын
You are assuming that I am only building Web APIs. What about Minimal APIs? What about background service? What about long running consumers?
@StonkExchange
@StonkExchange Жыл бұрын
​ @Nick Chapsas Yes you're right. For minimal api's you'd have to do it like you explained. I think you could scan for IHostedService for background services.
@ciach0_
@ciach0_ Жыл бұрын
Great gift for my birthday, thanks Nick!
@dcuccia
@dcuccia Жыл бұрын
Great solution! Could it be turned into a Roslyn Analyzer?
@nickchapsas
@nickchapsas Жыл бұрын
To a degree it can. It won’t be exhaustive but it will be good enough
@diadetediotedio6918
@diadetediotedio6918 Жыл бұрын
I think a pretty plausible solution would be to make a roslyn analyzer for this, but not in the way you might think. Each contract would require an attribute check, and then the analyzer would merely ensure that you set up the dependency somewhere by looking for the specific method call, so long as it existed it would consider it working. Of course, this doesn't cover all possible cases, but many projects don't use features that require extremely advanced dependency injection, so just a analyzer on that would do the trick.
@nickchapsas
@nickchapsas Жыл бұрын
But how do you check for things you can't add attributes on?
@diadetediotedio6918
@diadetediotedio6918 Жыл бұрын
@@nickchapsas This is a good point, for which I would do the following: Build a dependency tree, start with the root being the classes in which you find the attribute. Then check what injections they require in the constructor and add the tree, at the end of the process you will have a list of dependencies directly linked to each other (normally it is expected that you inject the dependencies via the constructor in a normal project and that the DI itself solve for you, so it makes sense to assume that the required dependencies in the constructors are part of the tree). Then you can check for them too. What would you think of this approach?
@sheveksmath702
@sheveksmath702 Жыл бұрын
In the test, could you instead loop through your app's ServiceCollection, calling GetRequiredService for each descriptor using a service provider built from the same collection? Since this throws exceptions when it can't instantiate the service, might be an alternative approach.
@nickchapsas
@nickchapsas Жыл бұрын
You could but it wouldn't matter because you want to verify that they are registered correctly, not that they just exist in there. Lifetime matters and count matters as well, think Mediator handlers for example
@edvardpitka3902
@edvardpitka3902 Жыл бұрын
I actually have a custom type that I add to each test project to make sure all dependencies are wired up correctly. It basically accepts delegate for resolving dependencies, assemblies to check and array of types I want to check. If one has a common way of discovering command and event handlers, then it makes it really easy to pass these types for discovery. For example IRequest, or IHandleCommand etc.
@davecenter2002
@davecenter2002 Жыл бұрын
Of course you could avoid MS DI as much as possible and use SimpleInjector which has a verification mode that will catch issues during your local dev testing or regression test runs prior to release.
@TheMikernet
@TheMikernet 10 ай бұрын
I was looking for this comment, haha. I love SI but its integration with MS DI abstractions is awkward and clunky, which is super unfortunate, given that those are now the standard everywhere...so I stopped using it.
@IanWilkinson-SgtWilko
@IanWilkinson-SgtWilko Жыл бұрын
Hiya, Did I miss the part where you talk about why you're using nunit, not xunit?
@nickchapsas
@nickchapsas Жыл бұрын
Because NUnit's Assert has the Pass method which xUnit doesn't
@RizaMarhaban
@RizaMarhaban Жыл бұрын
I prefer failed in integration test instead of like this. It is good to test it but somehow the app cannot run without the service being registered anyway. We also may forget to test the service registration in the test itself. I always use DevOps to check my tasks for adding such service registration. It is a one-time work most of the time and should be added first things first. However, it is nice to see we can do this in .NET 7 and maybe I may use it to double check with a constraint that this unit test must also be planned in DevOps task, so I did not forget to include in the test as well. 😄
@edvardpitka3902
@edvardpitka3902 Жыл бұрын
In my mind this does nothing other than verifying that services that you added in test, and only them are actually registered with a container. But it does not verify that service can actually be constructed. This also means that you need to provide all services in your test which means that you are doing your registration of services in 2 places. What you need is to register only entry points for your code, such as Controllers, command/event handlers and then resolve them from the container. That will ensure that all services in your dependency graph will be correctly resolved. Also this approach does not work with XUnit, or I cannot find a way to do Assert.Pass. Do you know of a way to shortcircut it in XUnit?
@anrikezeroti4680
@anrikezeroti4680 Жыл бұрын
Hey guys, here's alternative: Scanning controllers dependencies and verifying that they are resolved. Star/Comment if you think it is worth refining, extending and turning into Nuget Package.
@GrigorisFountopoulos
@GrigorisFountopoulos Жыл бұрын
can you give us a proper pagination implementation for a .net rest Api.
@haxi52
@haxi52 Жыл бұрын
It would be nice if there was a linter/code analysis tool that would do this.
@microtech2448
@microtech2448 Жыл бұрын
Don't wanna criticize but if someone has to remember to put services in test project then why one can not put them directly into main application?
@deonvanvuuren1340
@deonvanvuuren1340 Жыл бұрын
Can all this not be avoided by using reflection to register your services accordingly? In theory
@nickchapsas
@nickchapsas Жыл бұрын
In theory yes, in practice no, just because of all the variables you have to take into account.
@deonvanvuuren1340
@deonvanvuuren1340 Жыл бұрын
@@nickchapsas So lets say for example we have thee Base interfaces for our services IScopedService, ISingltonService etc, and apply those to our Contracts then use reflection wouldn't that take a fair amount of variables away?
@nickchapsas
@nickchapsas Жыл бұрын
​@@deonvanvuuren1340 You just swifted the problem again. Not only do I have to remember to register the serivce but also to implement a specific interface. Also what if this class is registered in mutliple ways or multiple times? Also which interface that it implements is the one that is registered in the DI container? There are too many variables
@deonvanvuuren1340
@deonvanvuuren1340 Жыл бұрын
@@nickchapsas Thanks Nick, always enjoy your video's!
@Nihimon
@Nihimon Жыл бұрын
Maintaining the list of services in two places (once where you register them, and again where you validate them) and then just comparing them to make sure there aren't any discrepancies doesn't seem like a good solution to me. Maybe a code analyzer is needed...
@amnesia3490
@amnesia3490 Жыл бұрын
I just asked it to chatGPT and the answer was almost same like what Nick showed, this thing goes crazy. (answer was too basic though..)
@lyrion0815
@lyrion0815 Жыл бұрын
guess, that chatGPT just asked Nick what he would do 🙂
@jimmarck7404
@jimmarck7404 Жыл бұрын
At my work we struggle with this issue all the time, deploying into dev after modifying a controller and suddenly a chunk of apis fail in qa. It would be nice to be able to validate all dependencies without having to add them to the tests (one of us tried but after years of endless cycle of devs, the tests are first to die), but I suppose this is just one of those things one has to deal with. :/
@mustaphaali438
@mustaphaali438 Жыл бұрын
what about addhttpclient?
@David-rz4vc
@David-rz4vc Жыл бұрын
This shouldn't be a problem if we do integration testings as we should?
@nickchapsas
@nickchapsas Жыл бұрын
It generally wouldn't, however in integration tests you are replacing some dependencies so you are not testing 100% what is the actual service that is being resolved in your application. For example, the integration test registration can hide a real app registration
@David-rz4vc
@David-rz4vc Жыл бұрын
@@nickchapsas ohh right, that make sense.
@mihaiga
@mihaiga Жыл бұрын
This could be modified so that the test always fails if new interfaces are detected but not registered in the test collection. That way it is not possible to forget registrations.
@nickchapsas
@nickchapsas Жыл бұрын
You'd have to mix this approach with assembly scanning to build the dictionary dynamically, but it's possible
@mihaiga
@mihaiga Жыл бұрын
@@nickchapsas Thank you for your reply and for the quality content!
@MaximShiryaevT
@MaximShiryaevT Жыл бұрын
It seems nothing can be done to account for all possible problems with DI until it becomes a part of syntax, not runtime, and be resolved at compile time.
@TheMikernet
@TheMikernet 10 ай бұрын
It can be done with a principled approach to DI - look at SimpleInjector's container configuration validation. SI is fantastic but unfortunately doesn't play particularly nicely with MS DI abstractions.
@BenvdStouwe
@BenvdStouwe Жыл бұрын
I've used a comparible technique to achieve this. We used the WebApplicationFactory to setup the whole application but instead of explicitly specifying the classes we want to check we loaded all types from the application assembly, filter the ones that implement IConsumer (MassTransit) or inherit from ControllerBase and try to retrieve them from the DI container provided by the WebApplicationFactory. This way all our application endpoints and their dependencies are always asserted. For this to work we do need the mentioned AddControllersAsServices, which is available as extensionmethod on the IMvcBuilder type, not the IServiceCollection type. You can add it like this: builder.Services.AddControllers().AddControllersAsServices();
@nickchapsas
@nickchapsas Жыл бұрын
Assembly scanning can go further or shorter in terms of how much you need to capture to build the dictionary which is why I didn’t show it. It can work if what you care about is things like mappers, mediatr handlers or consumers but for general purpose you will need some manual labour on top of that
@BenvdStouwe
@BenvdStouwe Жыл бұрын
@@nickchapsas I agree that scanning can be tricky, but I'd rather invest more time to set that up as good as possible than having to rely on a manual list with types. It's working well for us, we mostly use controllers and consumers. If I'd forget to inject a service to the DI container I'd most certainly would forget to add it to the test list.
@BenvdStouwe
@BenvdStouwe Жыл бұрын
I do appreciate the video. It's a nice introduction to the WebApplicationFactory which will make it easier to also add tests using it later.
@nickchapsas
@nickchapsas Жыл бұрын
@@BenvdStouwe The approach in the video worked great for us too. PR reviews would always catch the problem. There isn't a single solution to the problem. In fact, there is no perfect solution to this problem with the current DI container.
@BenvdStouwe
@BenvdStouwe Жыл бұрын
@@nickchapsas I don't want to think about injections when writing code or when I'm reviewing a PR, I want it to "just work". This may come as a shocker, but we also use assembly scanning to inject types that have a specific suffix. It's tricky, but together with the test that assert them based on application entrypoints I have much faith in it. I agree that it that there is no perfect solution for this problem and that you have to pick what works for you and your team.
@Galakyllz
@Galakyllz Жыл бұрын
It's better than nothing, I guess, but eventually people are going to forget to update the test and as the service setup becomes more complicated we'll never know what's missing. Personally, I cannot imagine some of the systems I work on having a properly maintained master test with all of the people who touch them.
@timramone
@timramone Жыл бұрын
Would be better if you just can get dependencies from your controllers somehow. For example go through all of them using reflection and try to validate that all controllers can successfully resolve all theirs dependencies. But it doesn't seem like an easy task for me :( This test is a bit useless because you have to remember of it's existence. So if you do know about the test and you didn't forget to add a line there, you probably didn't forget to add dependency into the service collection either. But if you don't know about the test, than you just get an error message as usual in runtime. I also don't like this test approach because you added some extra work on dependency deletion, especially if you don't know about the test.
@nickchapsas
@nickchapsas Жыл бұрын
Controllers are only a sub category of everything they can use DI. Console apps, minimal APIs, background services and long running consumers are all valid cases that wouldn’t be covered by this
@timramone
@timramone Жыл бұрын
@@nickchapsas seems like it's easier to cover (with some automatic testing) all the things you can use in DI once than to write a test every time you add a dependency in that thing
@MoZaKx
@MoZaKx Жыл бұрын
Real nightmare starts when you add circular dependency. Problem you solve here will be in most cases discovered during development, but circular dependency can go undetected very easy.
@nickchapsas
@nickchapsas Жыл бұрын
Something like this could catch that though, because you'd have to register both things and you'd see it. Maybe you can even add a circular dependency detection step
@velomaayo9424
@velomaayo9424 Жыл бұрын
hello Mr. Nick, I have a doubt in API calling, my REST API is a third party API, I can able call it when I am using the HttpWebRequest, but I can't able to call it when I am using the HttpClient class why this differentiation is coming between those classes, and how can I rectify this things, and also when I am using the .Net core dependency supports in .Net framework, this two classes aren't performing like as Core environment. what is the reason for these kind of differences?
@nage7447
@nage7447 Жыл бұрын
it seems kinda useless, I had problem forgeting to regester an service but you will miss it only if you does not test your endpoint (or what ever you wrote) localy on development, just cant imagin situation when I wrote a code and push it to prod and never run it on my machine more of that with unit test thing you now should remember second place to add the line to not miss it in first place D
@nickchapsas
@nickchapsas Жыл бұрын
It goes beyond simple registration and I should have probably emphasised it more, but the correct lifecycle implementation and also target type also matters. For example imagine you expect something to have a cache decorated service but it wasn’t added. That’s something you can easily miss because the app wouldn’t fail even during runtime but you’d still be missing the service
@TheDiggidee
@TheDiggidee Жыл бұрын
Very long winded way of writing unit tests to assert your container has been built properly
@Azcane
@Azcane Жыл бұрын
Reading the comments, I understand your points. But in my opinion the wording and framing of the problem and also the title ("Validate" instead of "Test" for example) does not fit with your solution. My expectation after watching half the video was different too and I think that's the main issue people have with this video.
@nickchapsas
@nickchapsas Жыл бұрын
I agree. I should have framed the video better
@Schtraider
@Schtraider Жыл бұрын
Beating a dead horse here but this doesn't really the problem.
@cocoscacao6102
@cocoscacao6102 Жыл бұрын
Yes, but what if I want it to blow during runtime on purpose? That way, I can say that I'm researching for a solution, while in reality, I'm watching cat videos....
@nickchapsas
@nickchapsas Жыл бұрын
We are playing Tetris when you’re out there playing 4D chess
@user-lm4vu2nw6l
@user-lm4vu2nw6l 8 ай бұрын
Useless test
@paulkoopmans4620
@paulkoopmans4620 Жыл бұрын
I have been doing it for quite some time, and 7.0 or 8.0 or minimal Api, will not change it, as follows: My libraries, whether in the solution or not, will be "DI ready" / "DI Aware", Like many of the other packages one would use. My libraries will have a ServiceCollectionExtension class with extension method(s) in them that register the classes. Overkill for the demo, but would this be a production app, I would collect the WeatherService and everything related to OpenWeather, into a project. This project would also have the class with an extension method do the registration; public static IServiceCollection AddOpenWeather(this IServiceCollection serviceCollection) { }. This way each of these projects register what "they" need. You make changes and when you require new things to be registered you do it there. The callers never have to be touched. Finally you can even independently test proper registration right there. And even if you only want to do it from the main Api project you still can. In the Api I usually collect all these calls together in a single method RegisterServices(IServiceCollection serviceCollection) { }; , which then can also be called separately. There is no need to new up the whole app then either. For testing I use xUnit and I add the dependency injection package, new up a ServiceCollection and then call the method.
Inject C# In Any .NET App With This Secret Entry Point
15:06
Nick Chapsas
Рет қаралды 51 М.
The C# Feature I Use Instead of Reflection
10:26
Nick Chapsas
Рет қаралды 36 М.
How To Choose Ramen Date Night 🍜
00:58
Jojo Sim
Рет қаралды 53 МЛН
Did you find it?! 🤔✨✍️ #funnyart
00:11
Artistomg
Рет қаралды 88 МЛН
Trágico final :(
01:00
Juan De Dios Pantoja
Рет қаралды 23 МЛН
Let's all try it too‼︎#magic#tenge
00:26
Nonomen ノノメン
Рет қаралды 50 МЛН
Trying to create a Dependency Injection/IoC Container FROM SCRATCH
34:12
The electrical pattern that will keep your .NET services alive
16:47
Compiling MS-DOS 4.0 using DOSbox & Qemu
17:59
Neozeed
Рет қаралды 3,3 М.
"Stop Using Async Await in .NET to Save Threads" | Code Cop #018
14:05
Are you using the Stopwatch efficiently in .NET?
8:36
Nick Chapsas
Рет қаралды 30 М.
How to await ANYTHING in C#
8:47
Nick Chapsas
Рет қаралды 44 М.
How To Choose Ramen Date Night 🍜
00:58
Jojo Sim
Рет қаралды 53 МЛН