.NET 5 Dependency Injection

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey everyone and welcome to another jetbrains webinar today we will be hosting steve collins with a promising talk about.net 5 dependency injection i'll bring him in shortly but first let's cover some practical details so first of all this webinar is being recorded so if you want to watch later or you have to drop off for some reason you can always find the recording later on on this youtube channel and on our blog if you have any questions during the session of course this is interactive it is live so you can ask questions in the chats we'll try to answer them as we go along and at the end of the session we'll also ask steve for um for some answers if there's something that we can't help with and with that let's uh see if we can bring in steve hi steve welcome how are you good how are you i'm fine thank you i'm uh really looking forward to your session here so um i'll i'll not waste too much time and just bring on your screen as well and give the stage to you looking forward brilliant okay thanks thank you martin um good afternoon good evening or good morning depending on where you are watching in the world um thank you for coming along today my click has stopped working that's better right um first things first quick introduction from me before we get going my name's steve collins as you probably guessed by now um i've been probably i've been programming in one for another since the early 1980s but professionally i've been doing this since the early 1990s starting with vb3 right the way through to now using.5 as i've got quite a common name i go by the alias of steve tools code for both my blog and my twitter handle as i've got a lot to pack in in the next hour um if you've got questions pop them in chat um hopefully martin can maybe answer bits as we go um if there's any in-depth questions we'll save them for the end and i'll try and answer them as best i can when we get to the end of the talk so with the introductions out the way let's take a deep dive into dependency injection so before we get going as this is a deep dive into more advanced topics about dependency injection in.net core and net five i've had to make a few assumptions about what you already know about it first is that most of you familiar with using a hose builder of one flavor or another to go and create the container whilst you can also create the container directly from the service collection without a host builder i'm not going to focus on that too much in this talk next assumption is that you're familiar with using a startup class to register your services via the configure services method and then you point the host builder to your startup class using the use startup method so as you can see here we're using the default host builder to set up a web application using the more specific web host builder next there's various types of dependency injections supported by many ioc containers but for now what you need to be aware of is that out the box the dotnet core container that comes with dotnetcore and net5 only supports constructor injection now when i say.net call throughout the rest of this talk treat that interchangeably with dotnetcore.net5.net6 going forward this is what we're focusing on i will be calling out a couple of specifics about.net 5 and cs9 later in the talk if you use an asp.net call you do get to take advantage of one other type of dependency injection and that's method parameter injection as you can see here all you need to do is add the from services attribute to the parameters you want injected from the container into your controller actions this means that if you have many actions on your controller that have different dependencies you can avoid injecting all of them into the constructor this makes your controllers and your action methods more aligned with the single responsibility principle from solid usually if you want to go beyond the basic out the box experience with the microsoft container you'll need to usually look at using other containers but before i cover that let's look at some of the other injection types that aren't supported out the box by the dotnet container firstly we have property injection this is where our class is a parameter-less constructor so dependencies have to be set via properties once the instance has been created once all those properties have been set you usually call them methods something like initialize to then prepare the object for use next we have named or key base for injection this is where you identify the dependency you need based on some key value typically string or enum or some constant lastly while it's not an injection type as such we have the automated service registration by convention which is quite a mouthful to get through in an afternoon but in short the container scans referenced assemblies and pulls out the classes and interfaces that he wants to register based on a set of conventions or rules that you specify when you're setting it up the good news is that i will be showing you some workarounds so that you can implement these without having to necessarily result to using an another container outside of the net container but why are those other styles of injection not supported well the microsoft container is what's known as a conforming container this is a term coined by mark seaman to describe a container that acts as an abstraction over other containers the problem with this is that only the lowest common denominator of functionality is supported so whilst the microsoft container is a container in a standalone in its own right because it allows other containers to hook into a common interface it's fairly basic in what it can do you're probably aware that there's a number of other containers and frameworks out there that have been around from.net framework and have carried forward through to.netcore.net5. in this slide i show the ones that are listed on the microsoft docs website as having been adapted or extended to work with the i service provider interface the downside is that if you want to use functionality that is not directly supported by the microsoft container you end up having to manage your interactions with both containers side by side this means you need to know how and who is resolving your dependencies for you but before we look at the other containers let's look at how you set up the out the box container in the snippet here we have code that you're likely to be familiar with here we're just registering some classes with the service collection and giving them different service lifetimes inside the dedicated startup class now as we've already covered in most applications you'll be using a host builder that initializes the service collection with some default services once it's done that it will create an instance of your startup class and then call the configure services method to add the registrations you have specified as shown here after that it will then call the build service provider method on the service create collection to go and create the container instance based on the service registrations once built the container can then be used by the application to resolve instances of services whenever they're required now if you go back a bit and you're familiar with dot net core 2 you will have seen been used to use building the container inside your configure services method and returning it as an iservice provider instance and that was the way you were able to wire up alternative containers as an example here i'm using autofak to register an artifact module and return a new artifact container in the donate call 2 container this will change with net core 3 as the host builder expects that it will take care of with the calling the build service provider in fact if you try and return a service provider from configure services in core3 or net 5 you'll get a roslin analyzer warning the reason for this is that the provider instance you create is not used to the used by the host builder and you've incurred the expense of creating a container just for it to be thrown away again because the hose builder is going to do it again for you what this also means is that whilst in core 2 there was a small performance boost by implementing the i startup interface when using the web host builder this is no longer really true and when using the generic host model but if you can't create the service provider in your startup class then how do you wire up an alternative provider to do that in core3 and.net 5 there is now a used service provider factory extension method that you add to the hose builder to get the alternative container to resolve the instances for you from the microsoft abstraction whilst that will register the custom container you still need to go and perform any container specific configuration within the configure container method you can do this using the configure container extension method on the host builder but to be honest it's a bit of a pain and it's a lot easier to do that inside your startup class as shown here with an example for autofac whichever way you choose to do this you need to set the parameter to receive the builder instance that is specific to the third party container you are using so that you can use the container's own registration methods so again using autofocus as an example here we it will provide a container builder instance where you can register your modules it's important to note though that as the container is built by the host builder you can't actually access the created container until you get to the configure method that's where you access it via the application services property so having covered how we get the container to be built let's get some consideration to the lifetimes of objects instances that you want the container to create for you i'm sure this is probably familiar territory to you if you've been used to using.net core for a while but i've included to make sure everyone is the same level of understanding before we start delving a bit deeper firstly we have singletons these are created only once and live for the duration of the container's lifetime next we have transients these are our fair weather friends that come and go as and when needed last but not least we have scope lifetimes which stand kind of between the two but i'll cover those in more detail a little bit later the thing to consider with these different lifetimes is that some play well with each other and others should you should try and keep apart so let's break it down a bit more starting with the shortest lift of the lifetimes the transient can accept objects created with any of the three lifetimes moving up through the lifetimes next we have scope lifetimes which because they're slightly longer live than transients shouldn't have transient lifetimes injected into them lastly as the singleton is the longest lived of all that should only have other singletons injected into it and not the other two now i'll try to choose my words carefully when describing these relationships because there's a general misconception that it's not possible to inject incompatible lifetimes into each other that's not strictly true i can't speak for other containers but the microsoft container won't actually stop you if you have a singleton and its constructor requires an instance of something that has been registered with a shorter lifetime the container will happily give you that instance the problem is the instance you get will have its lifetime promoted up to be the same as that of the receiving instance in other words a transient instance will be locked into a singleton are not released until the singleton is disposed of and that doesn't happen until the container is done and dusted this is known as a capture dependency whether transient or scoped instance like the insect in amber shown here is imprisoned in the outer shell which in our case is the singleton but you may want to hit pause there and say hang on when i've done this before i've got an exception that can occur by default it only happens when your asp.net core environment variable is set to development this is done by some hidden magic that lies in the host builder however you can explicitly turn this behavior on by setting the optional validate scopes parameter to true when creating the service provider this does have a performance hit at startup though so the general guidance is not to do in production whilst i haven't got time to cover it here today there's also a validate on build method and that will try and help identify registry identify registration problems where you've got a dependency on some other class that you think has been registered and that hasn't necessarily been so as i say it's fairly limited in what it can and can't identify so i wouldn't wholly rely on that so moving on given that singletons live a long time and can be dangerous when mixing with other lifetimes let's consider why we use singletons in the typical use cases given singletons can be accessed anywhere within an application things get tricky if your application starts using multiple threads typically like your web server application for example therefore singleton should ideally be stateless so you avoid threading complications but sometimes you may well have some kind of state that you do need to share these might be pooled resources in memory queues or some other type where you are in control of a limited resource that multiple things are dependent upon factory classes i'll talk about later but typically these are created once and then their methods are used to create cool to create shorter lived instances of some other dependency when it's required you may have an ambient singleton which hides the complexity of accessing shorter lived objects something like the http act context accessor in asp.net core that uses async local storage to avoid the capture dependency problem the last of the cases shown here is immutable state we don't want to have to recreate a resource object over and over and over again when you know that the data will not change while the application is running so for example you might have some lookup data you only want to read once from a slow repository at startup such as a database a file or something on a slow network service if you really must have state give some thought to letting.net do some of the heavy lifting for you if you've got collections consider using the concurrent collections namespace where some of the thread safety stuff has been done for you for read-only properties that have an underlying field try and make that field read-only so that if someone comes along later and tries to make that property read right you've put some guard rails that makes them think well this is read only why do i really need to make this read right if you go ahead and those properties really do need to be read right you may have to resort to using a thread lock now this is contentious but bear with me so in this slide we can see that we're ensuring all property access is managed through by a thread lock that way we can ensure that the value is only changed by only one thread at any one time now that you're probably two steps ahead of me but the problem with this approach is that by serializing the access to the property you'll get a major performance hit as you are forcing everything that touches that property down a single one at a time alleyway and you end up with threads queuing up or even worse potentially if you've got circular dependencies you could end up with thread deadlocks if that happens you probably need to think about your design because if you've got that kind of thing going on in a singleton you may want to take a step back and think again i don't have a pandas here for this not in this hour anyway i'm just warning that this is a code smell and you probably want to address it lastly consider the impact of having disposables in a singleton whilst this isn't a threading race condition like the ones i've described above the problem here is that if you expose the disposed method and the consumer calls that dispose method everyone else has potentially lost access to the disposed resources which can cause an object disposed exception to be raised elsewhere in the application however you do still need to make sure that the dispose method is there for the container to clean up after itself once it's done with the disposable resource to get around this you may want to consider abstracting the class with an interface that exposes all the members except the disposed method that way given that the service type is an interface and not the concrete class when the instance is served up to the cont from the container you have to put you've put some guard rails in place that can make it difficult for consumers to accidentally call the dispose method because it's just not there on the interface that doesn't stop a caller then casting the instance that they've received through the insta interface back to the concrete type but it's their foot and if they want to shoot it so be it so we covered single turns let's go to the other end of the lifetime scale where we've got transients for these not really a lot to say other than be careful about doing a lot of work in the constructor as you don't really have control over when these instances are created by the container this could have major performance here on your application if they're doing something like getting data from a database each time that they are created rather than explicitly through a determined method call moving on let's look at down the middle and go on to scope lifetimes these need to be approached with caution and handled with care in fact i have seen various blog posts and tweets where people have said that they have banned their use on projects i think that's a little bit drastic but i totally understand the resident reticence even as they can come back and bite you when you least expect it if used incorrectly so hopefully i'll try and clear up a few of the pain points we've already covered how scoped instances should be used with other lifetimes but what exactly is a scope lifetime the commonly quoted answer is they are created by asp.net core for http request response lifetimes but that's not the whole story whilst that is a common use case the scope is not tied to asp you can create your own scopes but that still doesn't answer what's copies so my mind the scope is really there to ensure that objects are created only once within a single unit of work so say you have several classes that all considered to be a single unit work you you think a bit bit like a transaction in a database those classes interact with each other and all depend on receiving an i widget instance in their constructor when created by the container therefore you want to make sure that they all receive the same instance from the container so in that respective scope lifetime is a bit like a singleton and it allows the same instance to be shared between several consuming classes at the same time however outside that unit of work the scope you expect to get a different instance so in that way it's a bit like a transient so to come back to the common use case of a web request say martin and i both make web requests those requests response lot pipelines are individual units of work scopes and therefore the scoped instances should be confined to each pipeline's boundary however as i talked about earlier it is possible to accidentally promote a scope dependency lifetime up to a singleton if you inject it into a singleton's constructor away from the web you might be writing a xamarin or a console app you may want to manually create a scope yourself this is done by calling the create scope method on the i service provider instance this gives you back an i-service scope with a single property of service provider which is an instance of service provider again but this time you've created a boundary that serves up the scope services now that's all a bit convoluted and a bit of a mouthful to explain so let's have a look at a high level diagram at the top you have the root provider which is where you usually get your singletons and transients the create scope method on that then wraps a lot of what i've just talked about into creating a scope provider from which you get your soap services in asp.net call the creation of scopes is taking care of you for for you when a request response lifetime is started the server will create a load of service features but the ones we are interested in is the request service feature now as i've described just now the scope factory is used to create the scope as highlighted here that scope is then used to create the scope service provider which is then accessed from the http context request services property now that was all a bit so high for losing as someone who writes asp controllers all of this is hidden away from you you kind of don't really care or you need to really care about is that when specifying your dependencies in the constructor or action method of your controller or endpoint that's what you're interested in is is it scoped is it singleton is it transient however there is one area in asp.net core where it is worth knowing what's going on under the hood and that's middleware now when we talk about middleware we think about intercepting and handling http requests and responses now because of this you might assume that the middleware is a scoped registration but that's not strictly true as there's two different ways of writing middleware the one most people are familiar with is convention based spit aware and now i've got that shown here with this type of middleware you accept a request delegate in the constructor and then call it from within the invoke async method to move to the next middleware in the pipeline either before or after that delegate call is where you do your own work now in some cases you might be writing terminating middleware where you generate a response and don't really call the delegate unless you can't handle that response yourself this is how something like static file middleware works for example if you have dependencies other than the http context you add these as parameters to the invoke async method and they magically appear when the method is called by the pipeline but how does that magic work when you register middleware it's done in the configure method of the startup class now at this point the container is already baked like i said earlier so you're not adding middleware to the container itself instead what's going on under the bonnet is that what you're doing is inserting a delegate to the infocasing method and what that will be doing is that does the actual work in the pipeline so one calls to the next to the next to the next to the next to do that you need to get a handle to that method and therefore go and create an instance so underneath a single instance of the middleware class is created by the use middleware method when that single instance is created any dependencies are resolved from the root container now as we saw earlier if the constructor is only called once when you inject a transient or scope dependency into the singleton constructor it will then become a capture dependency what you do need to bear in mind is that the pipeline is created before the first request is received so you won't be able to access anything related to http context in the constructor as it just doesn't exist at that point this is while the pipeline's still being built what will happen is you'll get an invalid operation exception throw as it can't be resolved at the time that the class is being instantiated to go and create the pipeline other scope dependencies that aren't part of the pipeline may resolve as i said earlier they may become capture dependencies so how does the request which by nature is scoped get dealt with well the requested moves through each layer of middleware in the pipeline the invo calling the invoke async method through the delegate chain that's done through sort of kind of reflection and delegates not through some interface or abstract contract to do this when the middleware is added to the pipeline a request delegate is created the delegate acts as a wrapper over the vocating method and as you can see the request delegate is passed as a parameter into the middleware constructor to enable the request to be passed down to the next one in the pipeline now you're free to add any scoped or transient dependencies as parameters to the invocation method if you need to use them in order to do some work either before or after the next middleware of the pipeline now in the previous example i demonstrated how dependency injection works with convention based middleware now the problem with the convention-based way of writing middleware is that it can feel a bit clunky and lays a few bear traps for you if you admittedly declare transients or scope dependencies as constructive parameters what is less well known is that there's an interface called i middleware that was introduced with asp.net core 2. what i middleware interface allows you to do is to turn what we just described about convention based middleware completely on its head this time the infocasing method has a fixed signature defined by the i middleware interface whilst we still get the http context passed into it now that request delegate is passing via the method and not via the constructor so given that the method has a fixed signature we have to go back to the constructor to inject our other dependencies what is different this time though is that instead of some hidden magic that uses reflection behind the scenes the middleware instance is now constructed by the di container so unlike the convention based middleware this time we do need to remember to register our middleware with the container in configure services now when we call them use middleware method to register the middleware the method checks to see if our middleware type implements our middleware interface if it doesn't then it assumes that it's convention based middleware but if it does it gets an implementation of the eye middleware factory from the di container which in turn then calls the di container to get an instances of what we need in our constructor now that seems like an unnecessary extra hop to go virafactory because the whole reason of a container is that it is kind of a factory but the reason the factory is there is so that other containers such as autofire can register their own implementation if you go to the github issues the around the di container you'll see comments from the likes of david fowler explaining why this decision was made so those implementations from the other containers can use their own container to resolve the instances directly rather than using the i service provider implementation itself so going down this route right in middleweb becomes much more natural if you write it in the same way as you would any other class that you registered with the eye container if you're interested in finding out a bit more about conventional versus factory-based middleware and how they work with dependency injection i do have a blog post that goes into more detailed comparison describes how the two interact differently with the underlying use method on the application builder class which is the thing that actually does the building of the pipeline for you so earlier i mentioned that you need to be careful with disposables but now i want to dig deep bit deeper into some edge cases if you remember when i was talking about singletons i said that you should hide the dispose method for consumers as the container will take care of it for you that last bit of the statement is true in most cases but as you can see here there are a couple of extension methods where you create the instances yourself and the container will not dispose of these for you if you do create an instance of a disposable class in this way there's a problem namely there's no obvious re-entry point back into configure services method or to the startup class itself to get a reference to that instance to then go and dispose it so why would you need to take that approach the most likely reason is that you need to pass arguments to the constructor of that class and those arguments just cannot be resolved by the container if that is the case you may want to consider using a factory but if you really must do this there is a get out of jail card behind the scenes there's an interface or that is automatically registered but on the host called ihost application lifetime this can be injected into the configure method by adding it to the method parameter list inside the method you can then register a handler for the application shutdown event which is past the object that you want to dispose of now that's fine if you just got one object that you've had to do that and it's a bit clunky it's really messy it gets gets you out of a hole if you've got lots of objects like that rather than setting up all this plumbing my advice would be to wrap the object creation inside your own disposable class you can then register that class as a regular singleton in the usual ways so the container can dispose of it and then inside the eye dispos the dispose method on your class you can then go and dispose all those other objects now that approach doesn't just apply to that example coming back to hiding the dispose method if you cannot change the disposable class as you don't own the source code and it doesn't already have an abstraction that hides dispose method you may want to consider one of the gang of four patents such as facade adapter bridge or proxy these are all variations of the same kind of theme basically writing a wrapper class to handle the creation and disposable of the class instance the last point i want to make is a rare one it's a bit of a code smell sometimes you get a class that's outside your control that's known as a control freak and what that does is it insists on disposing all the instances passed into it then instead of getting the containers to create those instances maybe you want to use a factory so the by using that factory you serve something up that the control freak can dispose of and you then don't get problems with your container next we have some gotchas that can come up and bite you when registering or consuming services from the container consider what happens when the same service type gets registered multiple times against multiple implementations now you may well be doing this deliberately because you want to have several implementations that you want to iterate through these might be maybe a set of validation rules or a collection of processing things that form some kind of enrichment pipeline that you use with the visitor pattern however more than likely it's probably happened by accident so for example you may have a source code merge that's gone wrong and the same line's been duplicated or there may be extension methods registering a different implementation to the one you're registering and you don't know about it whatever the reason there can be unintended consequences because the way the container works is namely the last in wins principle so if you call an extension method after your registration it'll be the extension methods implementation that'll be the one that gets resolved by the container so you need to give some thought as to the order in which you set up the dependencies in configure services now of course if you're writing an extension method especially if you're publishing it to a wider audience via new get the onus is on you to play nicely as we know there needs to be care around lifetimes and things get messy if there's multiple registrations with different lifetimes so you may think you've registered a single turn but something else comes along trump said with the transit or scope registration now maybe that as i mentioned just now you deliberately want multiple implementations but if we want to avoid accidentally registering the same service multiple times there's two ways out the box to do this the first is to use the triad methods where the first registration is kept and subsequent registrations are ignored so we flip the lasting wins principle over to first in wins principle the second approach is to use the try ad innumerable methods where both the service type and the implementation type are taken into account so an example shown here the triad singletons will result in just one registration even though it's been called six times triad in numerables will result in three registrations as the implementation type is also taken into account so that's the gotchas now let's look at some helpful hints and tricks let me know what i've just talked about on its head you may well be deliberately registering multiple implementations that callers do need to iterate over i gave a couple of examples just now about validation rules or enrichment pipelines if you plan to do this the consuming class needs to know that there will be multiple registrations this is done by explicitly requesting an innumerable of the service type to get all those implementations if you don't do this the instance you'll receive will either be the last lasting wins or the first if you use the triads to you just get one instance and you don't really know that there's other ones there if the order of the innumerable is important there needs to be a way of differentiating each instance without that the consumer will have to rely on the order that the registrations were performed now there's lots of ways of handling that one is to create a comparer that implements the icomparable interface for your service type that can then be used with an order buying the link statement in your outside your consumer once you've consumed the i enumerable you could create a dedicated class that implements innumerable interface and the caller requests that but for that to work you would need to inject the innumerable service type into it and then have some ordering logic inside so it's a bit more complex lastly if it's just a handful of things you may decide to register a lambda expression directly inside the container and when it when the consumer calls innumerable that gets served up and it applies your order logic for you go any other way around you may have a single class that has multiple interfaces especially if you've gone down the interface segregation principle route from solid if you register the type for each interface individually each registration will create a distinct instance when ready you probably want the same instance returned especially if it's singleton or scoped if you want to access the same instance through the different interfaces you should register the instance first by its class type then use a lander expression using the get required service method to resolve the main instance from the container and return it via the interface now in some cases you may want to aggregate the interfaces together into a composite interface as shown here this can be a benefit if you don't want to consumers to be able to request the concrete implementation directly if you think back to what we were saying about the eye disposable issue where you don't you want to hide the disposed method so rather than registering the concrete you register it with the composite interface so you've hidden the dispose method next we have open generics where you define a type parameter on the cli on the class type now the place you may be familiar with this from a di perspective at least is the generic i logger t interface where you may be using it in your controller constructors to get an eye logger instance the device that is specified by your type of controller similarly you may be familiar with the eye options interfaces that get registered when you use the configure method to bind our configuration object configuration to objects even if you want to register your own generic type the c-sharp syntax doesn't lend itself to referencing open generics within a generic declaration so instead we have to use the non-generic registration methods where we use the type of keyword to get the service and implementation types without giving a closing type parameter in the case shown here the i do something generic interface and do something generic class are shown with the angle brackets but without the closing type parameter as these will be supplied by the consumer behind the scenes the container makes use of the make generic type method on the type class to go and create the closed generic type for example do something generic of strings bad example but you get where i'm coming from it then uses that type with a bit of reflection magic to create a closed generic instance to return to the caller but do something generic of strings if you haven't moved to.net 5 yet you may well hit a problem though if you've applied a generic constraint to your type such as limiting it to be in a class or maybe implementing a particular interface the problem is that the make generic type inside all this reflection magic was throwing an argument exception prior to net5 luckily that's where jimmy bogard of auto mapper and media to fame has come to the rescue he did a pull request that added a try catch block around the make generic type and swallowed the argument exception this now allows constraint generics to be resolved in dot net five that's great except it took four years for his pull request to be accepted now if you read jimmy's blog you get to understand why there's been very little change in the microsoft container since the first version of dot net call excuse me in short to get a change accepted into the content main container repository you have to make sure that all other container technologies that implement the eye service provider interface don't break which in turns meaning to having to write loads and loads of unit tests but not make sure that what you'll bring into the change not only fixes something or brings something new to the party but then it doesn't break all those other containers as well next i want to talk about a couple of so-called gang of four design patterns that are relevant to the di container world the first is the factory pattern which i've mentioned in parting throughout this whole talk maybe maybe thinking steve surely the whole point of a container is to be a factory so why do i need to write a factory class well sometimes you have to give the container a bit of a helping hand for example you may have a class where some of the constructor parameters aren't known until runtime as they may be captured from say user input in those cases you can't get the container to create the instance for you because the container's got no idea about those user-rented parameters that's not unless you do something really hacky involving async local we're not even going down that road it may be that the instance to be created needs to be disposed of quickly as it's a resource hulk so you want to be able to dispose of the instance as soon as the caller is finished with the resource rather than wait for the container to dispose of it want the cooler to cool dispose on it so lastly at the start of the talk i mentioned that the microsoft container doesn't support property injection but by using a factory we can effectively simulate property injection by getting the container to instantiate the class and then set property values before returning the fully prepared instance so these are just a few cases where the factory pattern comes into its own so as a very quick and very contrived example we have a class here that will register as a singleton next we're going to create a class that consumes that singleton but also requires a name parameter in the constructor now given that the name parameter can't be known by the container as it's going to be provided by runs at runtime by our api controller we can't add it to configure services now you may notice i've used an immutable class here but with cs9 i could have used a record type now we will go and create a factory class first we'll take the singleton and cache it in the field as shown in the blue highlighted constructor here we then have a creation method that will go a new our partial lift thing class using the cache singleton and then a value passed into the name parameter that we've got on our factory method next we add the factory to the configure services as a singleton and finally we have our controller that takes our factory in the constructor later in the get action it then calls the make method to create a new instance of the short live thing now that's very contrived i know but i wanted to give some flavor of using the factory and sorry about the small form on this slide but as you see i had to pack quite a lot into that slide so to summarize factories usually you register the factory as a singleton as most time is stateless the variations of the parameters in the create method as it's a singleton the only other dependencies that should be injected into it are other singletons if you need transients or scope dependencies you can take the i service provider itself as a constructor parameter but proceed with caution direct use of the i service provider is considered a version of the service locator anti-pattern the reason is that the anti for the it's an anti-pattern is that it's kind of tight coupling your con your classes to container interfaces which in most cases you don't really want to do you want your classes to be oblivious to the container technology however the factory pattern classes fall into a group that i personally refer to as servicing classes these are plug-in the gaps in the container functionality that are effectively an extension to the container so with that in mind i think that as long as your factory classes are seen in that context you don't go referencing i service provider wholesale throughout your application i'm fairly relaxed about doing this as it's more about being pragmatic rather than dogmatic if you're worried about the factory classes exposing the anti-pattern and the coupling you could always go and create an abstraction through an interface and then hide the class inside your startup class so you only expose the methods via the interface as a service variation of the factory pattern is the builder pattern where rather than prescribing each dependency that are needed up front and then creating the instance immediately the builder is much more flexible the way i compare the two patterns is to think of the two as ways of ordering drinks in fast food restaurants the factory is like the asking the person to serve you a cola they use a standardized dispenser as we've got on the left here with a fixed set of options to make a standard drink with the builder pattern it's much more nuanced and it's more like the modern disper dispensers you see here on the right where you can say i want a cola but i want a dash of lime flavoring possibly a dash of vanilla and then some cherry gradually composing it up bit by bit until you have the finished drink that you want but with the builder we start out very much like a factory by registering it with the container however this time we register it as a transient as you're going to be mutating the state of the builder once it's been created in its constructor we can take in other dependencies from the container consuming classes then take the builder as a constructor parameter and then start using the methods and properties to build up the instance to be constructed once we've got our configured builder with everything that we want we've set all our properties we can then call the build method to go and create our instance now typically that resulting instance will be immutable because you've done all your work in building it up the last thing i want to mention is that like a factory if the instance you create out of the builder is a an eye disposable this time it's the caller's responsibility who did build to then go and dispose that instance because the container has taken no part in the creation of that object so it doesn't know about it and therefore it won't go and dispose it now so far we've assumed that the factory or builder classes will be using the new keyword to instantiate the class instances but wouldn't it be good if we could take advantage of the container to create the instance for you now in the earlier slides i said the problem was the the container has no knowledge of some of the parameters because they've been supplied at runtime so how can we deal with this luckily that's where the activator utilities class comes in handy those four methods are effectively two methods with generic overloads the get service or create service will try and get the container to create an instance for you but if the type isn't registered you'll do it using reflection the other method and the one that's of interest to us as writers of factories or builder classes is the create instance method what this does is takes an instance of i service provider to get an inroad into the container and then it takes an array of objects it then does the heavy lifting work of taking those various objects and trying to match them up to the order that the constructor requires them on the object now downside to this is that because it's having to do that mix and match there's a slight performance hit but if you've got lots of these things you may want to do that rather than to write lots of boilerplate code of doing the new up yourself so we've got what's the time 10 minutes or so so in this final section i want to talk about plugging in some of the gaps in functionality that the microsoft container is missing i mentioned earlier that the property injection isn't supported out of the box by the microsoft container but you can get around this by using a factory or builder to go and create the instance and set the properties before returning the instance that you've done your properties on through the create or build method another place where this can be useful is if you need to create instances of a struct value type where some of the properties need to come from a container the problem here is that you simply cannot register value types with the microsoft di container as it's just not supported so you need to get all the dependencies and instantiate the value type yourself here you can see i'm attempting to add the struct type registration using add a generic hair transient and here's the rosin analyzer error to tell you that sorry no can do so you could think aha is because i'm using the generic registration i'll try the non-generic version that's great you don't get compile error but all you've done is you've kicked the problem down the road to become a runtime error so how do we get around this we simply create a factory or builder that takes the parameters needed to populate the value type now the example here is contrived and doesn't really offer any benefit of just creating the object in your own code however if some of the properties need to be taken from the container this is where the factory can grab it grab these from the constructor or via the service provider and then put them in via the method parameters which kind of leads us to the new c-sharp nine record types now i'm a big fan of these and that make heavy use of them in a project i'm working on at the moment for those not familiar with these they're reference types like classes but they behave like value types when it comes to things like equality checking they also offer simplified syntax for creating immutable objects now if you want to find out more about them i like the on.net show video that there's a link to here from a di perspective as their reference types you can register them with the container however given that record types are mainly for users entities you're more likely to use the factory pattern for the same reasons i've given in the previous slides about value types if you are interested in finding out more i do have a blog post that shows how to write a record factory it also covers a lot of what i've just talked about with reg factories overall next we have something that no one really talks about with dependency injection in c-sharp we nearly always talk about classes and interfaces being registered with a container but delegates are also a first-class citizen can also be registered now in theory we could register the anonymous delegates from the functi family but these are hard to distinguish from each other because they also share a common generic in um signature so instead we go old school back something that doesn't get much attention these days and that's custom delegates now i go into details on my blog but i'll give you a quick example now i really hate system date time now property it it's non-deterministic by its very nature being real time it's hard to test i don't really like using it in my code so what can i do to get around that what we can do is go and create a delegate function that'll call the system date term now property on our behalf the signature of our delegate in this case is a function with no input parameters but it does have a return type of date time by creating the delegate we are creating an abstraction without all the ceremony of hoof2 first go and write an interface then go and write an implementing class and go and register the class against the interface this take takes all this out it also feeds into the single responsibility principle as the delegate does just one thing if you want multiple date time related functions you may still want to write an interface in the class but if those functions don't share any logic you could just go and create multiple delegates now this is where the custom delegates have the advantage over the funk t family as the delegates can all have the same signature but they're distinguished by the delegate name and under the bonnet each one is effectively a class if you do try and use the funk t family they may all have the same fun function signature and therefore you do fall into the multiple registrations trap that i described earlier a unit testing point of view though the delegates are very easy to mock or go and create your own implementations because at the end of the day they're just functions so you could have your own mock function that returns a very specific static date time that you can use in your assert statements to compare within your unit tests now you may notice i've registered that the delegate as a singleton may be thinking well hang on surely that date time that gets returned is going to be captured the first time it's called that's not the case as we've seen with the middleware pipeline what we're doing is we're capturing the pointer to the to the function not the actual function results so in this very contrived example i consume the delegate in the controller constructor and i cache it in a field i then invoke it the time of the constructor is run and go and cache the result in a field in the get action i then force a three second delay and i then call the delegate act again this time i create a return string with both values as you can see here the result shows that even though the delegate is a singleton the result is not generated until invoked the first implication is in the constructor the second is in the method and hence the three second difference in the shown result a feature that is supported by other containers such as autofag is being able to specify dependency to be resolved by with the container using a string name or a key it's considered a bit of an anti-pattern as it comes back to the idea that your application code shouldn't really know about a the container's mechanics but what if you could do it without your class having to know about the container so this occurred to me a while ago when i had a situation where i had a well had type mappings algorithms determined by a runtime value that was coming out of a database so rather than write a massive hard coded switch statement inside my repository class that would need to be updated each time a new value came along when the database is updated i wanted something that could be injected from the container to just give me the mapping functionality that i needed now that's a bit of a long-winded way to describe here so the link to my blog post gives you a bit of an idea of what's involved but i do have a very contrived example where what we do is we create a common interface and some classes so here we have a temperature converter that takes different scales to kelvin so we've got centigrade fahrenheit rankine and kelvin to kelvin a one-to-one mapping we then register them all with the container as their actual types now following on from what i was saying about using delegates we also have a delegate declared here that takes a string and returns an implement required implementation of the i kelvin mapper now in the code here i've used a simple switch statement based on character value but i could have done something more advanced using dictionaries or possibly custom attributes on the implementing types or maybe pattern matching whichever way you choose to do it the delegate is the secret source as it allows us to lift the logic out the consumer and up into the container the consumer will use the delegate to pass the string value and get back the correct map for instance that it needs in other words we are something along the lines of an abstract factory pattern so now we have a consumer class called get temperature and that has a method to take a scale as a string and a decimal value is the value that we want converted to kelvins now our class at this point has no knowledge of the containers eye service provider and it doesn't have any idea about the implementation of how we get that converter all it knows is that we call the delegate pass it a string we'll get back the appropriate converter that we need to convert that scale into kelvins so that was a somewhat contrived and simplified example of using inline switch statements if you're interested in use in looking into using lookup dictionaries or anything more advanced um frank quadnow has written a blog post that takes my example further and points out some gotchas around using the dictionaries and iservice provider because there's things around closures that you need to be aware of the decorator is another gang of four pattern that can be used to extend the functionality of the class by wrapping it and then re-implementing all the members but where necessary intercepting the incoming parameters and outgoing results to something new a common example is to use the is to add a logger so that every call to the methods input and output gets locked to do this though we still need the original class to be injected into the decorating class along with other dependencies which in our case is a logger in the example code here we have a class called do something that implements the i do something interface first we register the class as a concrete type and not through its interface we then have a decorating class called logging do something that takes the registered original class and also requests an instance of i logger it then re-implements the get hello message method this time with code that intercepts the input and output of do somethings version and writes these out to a log we then go back and register the new logging do something against the i do something interface which is then by used by the consumers so the last missing feature from the microsoft container that is supported by others is registration services by convention and assembly scanning still a long mouthful like i said at the beginning now i'm not personally a big fan of doing this um as i'm a bit of a control freak and i like to know what's exactly what's going on with all my registrations as we saw earlier it is possible for registrations to stamp all over each other and depending on the order that they're registered and that's why i don't really like being out of control and handing over that control to some other automated process however if you have masses of classes to register this can be a laborious task coming to typing add singleton scope etc etc so i can understand why you might be looking for a shortcut now several offerings out there that but probably the most well-known is scooter where you can define rules of criteria for registering classes and interfaces one nice thing i do like in scruto is also the ability to register decorating classes with a much simpler syntax than the hand crank version that i showed just now so we've packed an awful lot into this last hour um i hope it's been helpful i've had to go at quite a fast pace if you've been watching on youtube at 1.5 speed good luck to you um we've gone beyond the basics of dependency injection in dot net core and dot five i think for me the highlights from this talk are things like understanding the different types of injection when you may want to integrate with other containers how the service lifetimes work with each other and avoiding captured dependencies we've looked at different ways of writing middleware and how that determines how they work to with the container in the gotchas section we talked about deliberate multiple registrations and how to avoid accidentally registering services multiple times we've talked about design patterns such as factory builder and things like facades where you're having to wrap things up to hide some of the functionality we've covered then how you integrate those with the container similarly we we've now looked at things like custom delegates which can be used to provide missing functionality without having to resort to using other containers that just about wraps it up from me if you want to hear any more of my ramblings i'm on twitter quite a bit i do have a blog where i've written mainly about dependency injection and configuration though i've started looking at things like source generators just lately back in november i recorded an episode of the dot net call show with jamie taylor which i believe should be coming out in the next few weeks but i'm not 100 sure of that next a few honorable mentions um the microsoft docs page well worth a look at that that's sort of a great place to go and find out information especially if you're migrating from things like core 2 to core 3 or 2.5 andrew log has a brilliant blog that covers all sorts of.net core and asp.net core things and the second edition of his book asp.net core in action has just been published this week purely by coincidence when i checked twitter this morning manning who is published by um have a half price sale of the book today so if you're watching this live you've got until midnight eat us eastern standard time you can go to manning.com d-o-t-d to go get the deal of the day page you can get the asp.net core in action second edition plus a couple of other.net core books last but no means least um mark seaman's blog and the his dependency injection book are well worth getting a look at to get a bigger picture of di patterns anti-patterns it also covers all the non-microsoft containers and how they all compare each other and then there's a chapter towards the end about the microsoft container and with that said my hour is up thank you for coming along to watch my talk i hope it's been of use to you um martin if there's anything in chat i will try i can't promise i've got an answer but i'll try and answer some questions uh absolutely first of all i really want to thank you for this talk i found it amazing i also felt it was a little bit five talks for the price of one sorry i think i would write a notes for that um i've seen people give some feedback on that as well so lots of people saying great talk steve thank you for the insights i didn't know about delegate injection that's cool uh can i get an autograph is one that just win a virtual one yeah so uh yeah i guess job well done thank you there are a couple of questions um and i'll bring them up by the way if anyone watching has more questions feel free to put them in the chat and we'll uh we'll try to answer them as well um let's start with the first one from max t who uh steve could you talk a little bit about how all of this fits in with api level unit testing for example where you would start with a host factory in your tests and then test against http or grpc and change some registrations based on the testy rating okay so a bit outside the scope let's go over this talk but what i try and do is i try and refactor my startup class into such a way that i can use the um a factory around that so that i get i get my um dependency sorry i get my service collection out of my startup class but before it gets called um the build service build service provider i then go and overwrite some of those uh dependencies with my own dependencies um now i think on the i service collection i think i'm right in saying that there's a remove meth method where you can take out something that's already registered and go and replace it with your own some circumstances you could get away with just registering your own after the event but comes back to the lasting wins principle so that even though you unless you've got something that's requesting an innumerable of some dependencies if it's just got a single dependency say i widget you may have registered iwidget as a certain concrete type within your startup class but then if in your unit test if you then take their i service collection register your own unit test i widget and then call build service provider that your widget will overwrite the the widget that was previously there i could probably do a whole talk on that subject alone and it's all right [Laughter] um yeah i was actually thinking because i had a question of my own about create scope but i was thinking maybe in unit tests you could create your own scope and just register whatever you have basically just in principle and then then get started i think it gets a bit tricky with sort of um if you've got apis because you sort of try and take over what the asp.net is trying it and it could get a bit tricky but if you just wanted to test stuff about scopes and that's where inside your unit test um you could use that validate scopes method on the um host builder and also the validate um validation build validate after build i can't remember the exact name of it now but there's you could use those things to go and write unit tests around and also they're limited to the um development is development method so that you could scope those whilst you're just in a debug mode effectively that's uh yeah another good approach i think um yeah one question that i actually had uh was around createscope i've used it before in some cases but did you have any places where you would actually start using create scope and create your own scope for certain things i mean in asp.net world probably not because you've kind of got your scope already about your your whole um request response pipeline that's kind of taking care of you certainly you may want to do it in a console app or a zarin app um where you want to so comes back to this idea of the unit of work so something where you've got a unit of work or something i'd i tend to align it with thinking about things like database transactions where i just want to say i've got this whole thing and i'm dependent on this thing inside so i might have five different um classes or interfaces that are dependent on something and i want that to be the same but i want it to be different on different transactions effectively quite is scope is one of those things that's really hard to try and describe the other thing to watch out with createscope is that it's despite the it's disposable and you need to make sure you dispose your scope if you're doing it manually all right thank you very much um i see one more remark coming in about the manning website and andrew luck's book so thank you for uh for giving the link and that's right it's pure coincidence that that came through on my twitter feed this morning so that's well timed um yeah and with that i think we can probably start wrapping up um if you like this video give us a thumbs up and subscribe to our channel if you want more webinars later on more information about us is on our website of course you can follow us on twitter and you can also follow steve on twitter if you have any questions after watching this probably at 0.6 speed afterwards you can ping steve on twitter and get in touch or get in touch with us as well with that again thank you for joining and find more screencasts on our jetbrainstv channel here on youtube you
Info
Channel: JetBrainsTV
Views: 13,840
Rating: undefined out of 5
Keywords: dotnet, csharp, dependency injection, .NET 5, Rider, service container, resharper, Steve Collins, webinar
Id: 0x2KW-dJDQU
Channel Id: undefined
Length: 68min 43sec (4123 seconds)
Published: Thu Apr 08 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.