ASP.NET Core Dependency Injection

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
I got a question from a subscriber asking if  I could do something on Dependency Injection.   Now I've mentioned Dependency Injection in various  other videos when we've been dealing with ASP.NET   Core because you can't really do that without  Dependency Injection, but it's always been just   a peripheral thing to make it work - not something  I focused on. So I thought I'd do a few videos   looking at the whole idea of Dependency Injection.  We'll start out in ASP.NET Core, but we'll also   look at it in other environments as well to see  how it can be used in different circumstances.   Now the basic idea behind Dependency Injection  is very closely related to one of the five SOLID   code principles, which is Dependency Inversion,  which we covered in an earlier video. But the   basic idea is, if you've got two components A  and B, you don't want A to be dependent on B,   but equally you don't want B to be dependent on  A. And so you introduce an interface and make   both A and B dependent only on the interface.  And then as long as the interface remains stable,   then you can make whatever changes you like to  A and B and the one won't affect the other. And   having that stable interface is also related  to another of the SOLID code principles - the   Interface Segregation Principle, which we also  talked about. So that's Dependency Inversion.   Dependency Injection is really the practical  implementation of how we're going to make that   work, and it's something that's been around for  a long, long time. There's been lots of libraries   that give you support for it - you could do it for  yourself, but there are libraries to support it. But it   was only really with .NET Core that Microsoft  introduced their own library to do this, and so   out of the box you've just got it available. You  might still want to use one of those third-party   ones - they have more features, possibly, than the  built-in .NET Core ones - but in my circumstances   I've never really found a requirement more  than what we get directly with .NET Core. So   let's take a look at the application I've put  together here, and what I've got to start with   is just a simple SQLite database where I've got  a table containing ratings for books. So we can   see I've just put a small amount of data in there,  but we've got repeated books, so someone's given   'Dr No' a 5; someone else has given 'Dr No' a 2;  someone else has given 'Dr No' a 1. And similarly   for other books as well. And if I just run up the  application, we've got a website and what it does   is just lists that information. So there we've  simply got the data from the database. That's   on the Home page; I've also got this Summary page,  and this doesn't work at the moment. At the moment   it just shows exactly the same thing, but what  it's intended to do is aggregate that data. So   it's intended to produce a single row for 'Dr  No' that has the average of those ratings for   'Dr No' , a single row for 'Emma', single row for  'Persuasion', single row for 'Goldfinger' - and   so just to summarize that information. And this  gives us quite a nice look at the various ways   that we can use dependency injection. Now  as the program stands, all I've done here   is I've got my BookReview entity - so just an  Id and then Title and Rating. And then I've   created a DbContext that just has that list  of BookReviews. And then in Startup, I have   configured that as a DbContext using SQLite in there, and  that's the sort of thing I've done in plenty of   videos before when we've been looking at  Entity Framework Core and how that works.   But already we've actually got some idea of what  we have with Dependency Injection, because this   adding of the DbContext also configures  it for Dependency Injection. Because if we   now look at the controller - so here's  our HomeController - then you can see   that the constructor takes a parameter of that  DbContext and then uses it just to get hold of   the BookReviews, and that really gives us a good  indication of the key problem that in these sorts   of circumstances Dependency Injection is trying  to solve. Because the thing about a controller   is that we as a programmer never get  to actually call that constructor.   We can do all sorts of configuration basically  that says that that constructor should be called;   so we've got down here where we just basically  say we've got a controller we've got an action,   that sort of thing. That's configuring the system  to know, when a request is made from the client,   which controller to create. But the  actual creation is done automatically,   and therefore we don't get access to the point at  which 'new HomeController' is called, and therefore   we don't get to put in that DbContext. And that's  really one of the practical benefits we get   from Dependency Injection, that we simply  configure the behaviour, like we did in Startup,   and the system will add that in there directly.  Now in .NET Framework in ASP.NET we didn't have   Dependency Injection and if you had a situation  then where you had a controller that needed to   have data passed into its constructor, what  you'd actually then have to do is write   your own controller factory. And so you'd  have a factory that was called by the system   that would create your controller. But because  you've written the code, you could say ... and   the constructor takes a particular parameter.  But given that was really the main reason for   it - just to provide constructor parameters - it  seems easier to have this generalized injection   mechanism than to have this more specific factory  mechanism. So that's why we've got this injection   in ASP.NET Core, and so we can see what happens.  We've done the configuration in Startup by   adding that context, and then whenever we create a  controller, if we want to use the context, we just   pass it in as a parameter and it will get passed  in. So if I put a break in there and run it up,   we can see that when it hits that break point -  we can't see where that's been called from, we've   just got external code; it's somewhere inside  the system. But it's provided us with a valid   DbContext and from that we can do things like get  the BookReviews - in this case - and display the   results. So that's our first encounter, really,  when we use it with a DbContext. But something   that I've spoken about before when we were looking  at Entity Framework is the idea that you sometimes   don't want to directly access the DbContext  - you want to use a design pattern known as   Repository. And so I've actually put one in here.  I put this in a folder called 'Services' because   'service' is a general term for something which  can be injected. So that's just the general idea,   even though the purpose of things that  can be injected can be very diverse,   if it can be injected we call it a service.  And so what I've put in here is an   interface called IBookReviewRepository. And the  typical idea of a Repository - it just means   that rather than client code in the controller  having to make specific queries on the DbContext,   we kind of pre-wrap those. So we've got a  property that will just give us back all   the BookRreviews and then we've got another one  that will give us back the BookRreviews for a   particular title. And then the implementation of that  interface is pretty straightforward. It itself   picks up a DbContext and then for 'All', it just  calls BookReviews and then for 'ByTitle' it does   'Where' just to filter by the title. And so what  we can now do is - in our HomeController - forget   about having the DbContext; we're now just going  to have this IBookReviewRepository that we'll call   '_repository'. And we'll take that to be injected and  then we'll just say '_repository = repository;'   and then of course we've now got to in here say  '_repository.All' and put it in there as well.   And so now we've hidden away the DbContext inside  the Repository, and all the controller knows about   is this IBookReviewRepository  itself. If we run that up, however,   we'll get this great big error basically saying  it doesn't know what it's supposed to inject when   we ask for an IBookReviewRepository.  So we're asking for one here,   but we haven't done the necessary configuration,  so the system knows what it needs to provide.   And that, as you might expect, is done  in Startup. So it was in Startup here   where we configured the DbContext itself.  And all we need to do is say 'services'   and then we say '.Add' and I'm going to do  'AddTransient'. Now there's some options here,   and we'll talk about those in a later video, but  to start with 'AddTransient' is the simplest one.   And then we use generic parameters to  basically say if somebody asks for an   IBookReviewRepository - so if somebody asks for  the interface, give them our implementation - so   just the BookReviewRepository. And this is one of  the nice things about injection; it means if we   had multiple different classes implementing that  same interface, it would just be a configuration   change in here to say which one was going to be  used. So we could have a completely different type   of Repository that maybe got the data from a web  service rather than directly from a database. It   would just be a configuration change as long as it  conformed to that interface. But once we've done   that then we can see that that is behaving in the  same way. So now if we put our break point on here   we can see we're getting the Repository passed  in. But also - something to notice - remember   the Repository constructor itself needed the  DbContext. Well remember, that was already   configured for injection - we had that from the  start - and so if we look inside that repository   we can see it has a DbContext. So what's  happened there is, when the dependency injection   discovered that it needed a BookReviewRepository  it attempted to create one. It then discovered   that a BookReviewRepository needs a DbContext and  so it passed that one in there. So that's really   nice; once you've got these things configured  then they just get injected wherever they   may be needed. And so that's given us the whole  thing working all the way through. And in fact we   can look at that in a slightly more customized way  with two of our own services. Rather than using   the DbContext - because remember, we want this  idea of having a summary - and there's various   ways we could do this. We could actually just  put some code directly in the HomeController here   that would calculate that summary information.  And in a simple case that arguably might be a   good thing to do. But it's always better,  if you've got a separate job, to put it   into a separate class. And so let's do that. Let's  have another service and let's do this in the same   way. We're going to have an interface that we'll  call 'IReviewAggregator' and make that 'public'   and all that needs is a simple property which is  going to return an IEnumerable of BookReviews.   And we'll just call that 'Summary', and it's  going to just have a 'get'. And then we'll   also add the implementation of that so; this  one's just going to be called 'ReviewAggregator'   and it's going to implement the IReviewAggregator  interface ... implement the interface and then   what we're going to do here is obviously, to  do a summary we need to get hold of the data,   which we know is stored in the repository. So  this is where we're going to have injection into   an injectable item. So once again here I'm going  to say 'private readonly IBookReviewRepository',   so it's a similar setup every time. Pop in a  constructor, take that as the parameter and   then once again '_repository = repository;' And  what that means is that here I can now get the   data and do the summary. So what I'm going to say  here is '_repository' and then we know it's got   the All or the ByTitle. Well, we'll just use the  All here and then we've got to do our processing.   And so what I can do - some quite neat LINQ code  in here - I'm going to do a 'GroupBy', so sorted   into groups and those groups are going to be based  on the Titles. So we can say 'r => r.Title'. So   that will break it down into groups. So we had ...  in our actual data we had four separate Titles,   though we had several reviews for each  one. So we'll have broken into four groups   and then what we can do is get out of that  this summary. So I'm going to say 'Select',   another lambda so 'g' (for group) '=>'  and then what I'm going to create here   is a 'new BookReview'. So we can use that for  the summary information as well as the individual   items. I'm going to say 'Title == g.Key' - so when  you've done this GroupBy, whatever you grouped it   on - Title - becomes the Key. And then I'm going  to say 'rating ==' and then we can simply say   'g' again, which is the group which is the  collection now of all the items that have   that particular Title. So all the ratings for 'Dr  No', all the ratings for 'Emma', whatever it may   be - so I'm just going to again use LINQ and do  the 'Average' of ... and then we want to pick out of   that the Rating, so pick out Rating on there.  So that will give us simply four BookReviews,   one per Title, with a Rating which is the average  of all of the Ratings for that particular Title.   One other thing I'll do is - just for tidiness  - let's do a 'Math.Round' on that, so that we'll   just round it to two decimal places, just so  it looks nice when we display it. So that's   the functionality, but the key point is we've put  it into another service - another thing that can   be injected. And so to use that we can go through  the same process. So here we're now going to have   an IReviewAggregator that we'll call '_aggregator'  and then we'll pass that in as a second parameter.   You can see if you use a lot of this, you  end up having a large number of parameters   in your constructors, which can be a bit messy.  In the next video we'll look at a neat way to get   around that if it's proving to be a problem. But  we'll keep it as the simple version for now. So   there we've got our _aggregator, and that just  means now in the summary, rather than having   '_repository.All', which was giving us the wrong  result, we now just do '_aggregator.Summary'. And   the nice thing is remember, both All and Summary  just give us back an IEnumerable of BookReviews,   so we can use the same view. We've got this  Index view which just does a table displaying   the information. So we don't have to have a  new view to get that working. But remember,   the final thing we've got to do is go back to  Startup, because we've also got to register that   second service. So here we're going to have our  IReviewAggregator and the implementation of it.   And so now when we run that one up we can see  on the main page we're still getting that, but   if we go to the Summary you can see it's grouped  it into those four different books that we happen   to have and then provided the average rating  for each one, which was exactly what we wanted.   But again the important thing to notice is the  way that's worked is that we have had injection   into injection ... actually into injection. So if  I put some break points in there, if I go to the   repository we can see that's getting  injected with the context. But if we   go to the aggregator we can see that that is  getting injected there. And if we go to the   controller and put another break in, we can see  the injection that's happening there. So now   if I go back to the page and just refresh it,  we hit the creation for the repository which   is injected with the context. We then hit  it a second time. Now that's interesting.   That's because of the way I configured this.  Remember, I configured it as AddTtransient. That   means basically we get a different object every  time we ask for it, and we'll see how to vary that   in the next video. But then if I continue, we can  see we're getting the repository injected into the   aggregator. And then we're getting both of those  injected into the HomeController, and it ends up   all working. So injection into injection into  injection. But although it's quite complicated,   it's pretty simple to program and understand,  because it all basically comes down to the idea   that if we want something, we ask for the  interface in our constructor. And then we   do the configuration entirely in the Startup  - in the case of an ASP.NET application - and   that sets everything up. And so it just keeps  the independence of our various components very   clear. The other benefit we get from it -  and something that's always worth talking   about - the main reason we do this fundamentally  is, remember, we said we have the ability to vary   the implementation of the interface. So  we could provide some completely different   implementation - and I mentioned kind of real-time  applications where you'd have that, where   it would be useful if you're getting the data  from a web service rather than a database. But   it's also very important when it comes to testing,  because down here I've created some unit tests.   So here we've got our ControllerTests - which  I'll just uncomment - and we've got them there.   And the key thing to notice is what  we've done here is we have mocked those   services - those injectable objects. So I'm using  the Moq framework and I've got the interfaces   and then I can have a 'mockRepository' and a  'mockAggregator'. And then when you're doing   unit testing, strictly speaking you're not really  doing Dependency Injection because remember that   we said the reason we needed Dependency Injection  was we don't have access to the point in the code   where the constructor is being called, into  which we could pass our services. Well,   that's true when you've got the full ASP.NET  application, but when you're writing test code you   do have access to the constructor. And so in fact  we typically don't use the injection mechanism,   we do just hard code it in. So  we pass in our mockRepository   and our mockAggregator. Here we've configured  the mock Repository to return a couple of books,   and that is what will then get fetched out.  And so we have an Assert to look at those,   that's just looking at the main Index that looks  at all of the books. We've then got our   Summary ... and actually I've divided this up  into two separate tests, one of which i've called   'Summary_Shallow' and one of which I've called  'Summary_Deep'. And essentially Summary_Shallow is   a genuine unit test, in the sense  that it only tests the HomeController   and everything else is mocked. So I've mocked  the repository and I've mocked the aggregator,   and therefore on the aggregator I've set it  up to return what I'm pre-determining as the   correct summary - the correct averages. And that is  then what will get called by the HomeController.   And we assert that that's valid. So in this  case we know it's a unit test because it's   only the HomeController that's being tested. We  don't actually use the real ReviewRepository or   the real ReviewAggregator. Whereas here I've got  what we might call an integration test. We're   actually - although we're using a mock repository  - we are using the real ReviewAggregator. So   this test tests both the HomeController and the  aggregator. And if either one of them got it wrong   then we'd have to work out which one had the  error. So on the one hand, it's not as good as a   unit test because it doesn't really precisely tell  us where the error is, but on the other hand it   tests that we've integrated the things correctly  - that the HomeController and the ReviewAggregator   are working correctly side by side. And so  in this case we actually just have mock data   for four reviews over two books. And you can  see that our Assert is checking that it's got   the averages correct, so that it's got the 3.5  and the 1.5. So that's just another way of doing   it. And we get to pick and choose, because we can  pass into the constructor either a mock object or   a real object. And it's up to us what we want to  do. But then if we just run the tests, we'll see   as we had before that we've got green lights  coming in. So that was it for our first look   at Dependency Injection. There are still  a number of questions to be answered,   which we'll be looking at in future videos.  One is related to when we configured these,   what is it exactly that we mean by 'Transient'  and what are the alternatives there?   Another one we had was what do we do if we've  got this long list of injected services? Can we   simplify that? And another one was, well, what  if we're not in the realms of ASP.NET at all?   Can we still use this idea of Dependency Injection  in other circumstances? The answer to all of those   is yes, but you'll have to wait until next time  see it. So if you want to catch that, if you don't   want to miss it, do subscribe. If you liked this  one, do click like and I'll see you next time.
Info
Channel: Coding Tutorials
Views: 910
Rating: undefined out of 5
Keywords: .net core, entity framework, asp.net, c#, dependency injection, unit tests, integration tests, captioned, linq, groupby
Id: qEkm0n2Itgk
Channel Id: undefined
Length: 23min 37sec (1417 seconds)
Published: Fri Jul 09 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.