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.