-
Notifications
You must be signed in to change notification settings - Fork 125
Add chapter about dependency injection in Blazor #14
Add chapter about dependency injection in Blazor #14
Conversation
Thank you @rstropek! 🎸 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great - nice clear explanations!
docs/dependency-injection.md
Outdated
|
||
Blazor has [dependency injection (DI)](https://docs.microsoft.com/aspnet/core/fundamentals/dependency-injection) built-in. Blazor apps can use built-in services by having them injected into components. Blazor apps can also define custom services and make them available via DI. | ||
|
||
## What is dependency injection? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Firstly, I really like the overall tone of voice here and think this is just the right amount of background detail. Great job!
Secondly, I slightly object to the idea that "component should not create an instance of DataAccess
" as if that were always true. That's entirely the choice of the application developer. Sometimes there's value in working with an abstraction; sometimes it would just be unnecessary friction. It depends on the scenario. The DI system in Blazor is equally happy to supply instances of concrete service classes as it is to deal with interfaces.
I'd say something like:
DI is a technique for accessing services configured in a central location. This can be useful if:
- You want to share a single instance of a service class across many components (known as a "singleton" service)
- Or, if you want to decouple components from particular concrete service classes and only reference abstract interfaces. For example, you might have an interface
IDataAccess
implemented by a concrete classDataAccess
. If your components use DI to receive anIDataAccess
implementation, they aren't coupled to any concrete type. That means the implementation could easily be swapped, perhaps to a mock implementation in unit tests.
Blazor's DI system is responsible for supplying instances of services to components. It also resolves dependencies recursively, so that services themselves can depend on further services, and so on. DI is configured during startup of the app. An example is shown later in this topic.
docs/dependency-injection.md
Outdated
|
||
Blazor's implementation of `System.IServiceProvider` obtains its services from an underlying [Microsoft.Extensions.DependencyInjection.IServiceCollection](https://docs.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.iservicecollection). | ||
|
||
## Add services to DI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool - good explanation about configuration!
docs/dependency-injection.md
Outdated
| Method | Description | | ||
| ------------ | ----------- | | ||
| `IUriHelper` | Helpers for working with URIs and navigation state (singleton). | | ||
| `HttpClient` | Provides methods for sending HTTP requests and receiving HTTP responses from a resource identified by a URI (singleton). Note that this instance of [System.Net.Http.HttpClient](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient) is using the browser for handling the HTTP traffic in the background. Its [BaseAddress](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient.baseaddress) is automatically set to the base URI prefix of the Blazor app. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a couple of places it says "is using" or "is referencing". Maybe it's a language difference, but a more common phrasing would be simply "uses" or "references". Not a big deal though!
docs/dependency-injection.md
Outdated
| `IUriHelper` | Helpers for working with URIs and navigation state (singleton). | | ||
| `HttpClient` | Provides methods for sending HTTP requests and receiving HTTP responses from a resource identified by a URI (singleton). Note that this instance of [System.Net.Http.HttpClient](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient) is using the browser for handling the HTTP traffic in the background. Its [BaseAddress](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient.baseaddress) is automatically set to the base URI prefix of the Blazor app. | | ||
|
||
## Request a service in a component |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this section - well explained!
docs/dependency-injection.md
Outdated
|
||
## Dependency injection in services | ||
|
||
Complex services might require additional services. In the prior example, `DataAccess` might require Blazor's default service `HttpClient`. `@inject` or the `InjectAttribute` can't be used in services. *Constructor injection* must be used instead. Require services are added by adding parameters to the service's constructor. When dependency injection creates the service, it recognizes the services it requires in the constructor and provides them accordingly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: "Require services" should be "Required services"
docs/dependency-injection.md
Outdated
|
||
## System services | ||
|
||
Blazor provides default services that are automatically added to the service collection of a Blazor app. The following table shows a list of the default services currently provided by Blazor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it's worth clarifying that these are the default services in a BrowserServiceProvider
(which is what the project template uses by default in Program.cs
). If someone creates their own different service provider, it wouldn't contain these services.
I only see a handful of very minor grammatical changes to make. To save time, I'm just going to shoot them in there and then sign off. Stand by .... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This topic is 🔥 HOT HOT HOT 🔥 ... great job!
Thanks again @rstropek for handling this issue. 🚀
@danroth27 @SteveSandersonMS Do you have anything else here before merging? @rynowak ... do you have comments?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Just a few small comments.
docs/dependency-injection.md
Outdated
|
||
Blazor's DI system is responsible for supplying instances of services to components. DI also resolves dependencies recursively so that services themselves can depend on further services. DI is configured during startup of the app. An example is shown later in this topic. | ||
|
||
## Use of existing .NET mechanisms |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this section adds much and it delays getting into content that helps you get started using DI. I think I would just remove it.
docs/dependency-injection.md
Outdated
{ | ||
var serviceProvider = new BrowserServiceProvider(configure => | ||
{ | ||
configure.Add(ServiceDescriptor.Singleton<IDataAccess, DataAccess>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should just be configure.AddSingleton<IDataAccess, DataAccess>();
You just need to add a using for Microsoft.Extensions.DependencyInjection
.
docs/dependency-injection.md
Outdated
} | ||
``` | ||
|
||
`ServiceDescriptor` offers several overloads of three methods that are used to add services to Blazor's DI: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think of ServiceDescriptor
as being a lower level concept. Most of the time you just use the extension methods. I suggest replacing this sentence with: "Services can be configured with the following lifetimes:"
docs/dependency-injection.md
Outdated
| `IUriHelper` | Helpers for working with URIs and navigation state (singleton). | | ||
| `HttpClient` | Provides methods for sending HTTP requests and receiving HTTP responses from a resource identified by a URI (singleton). Note that this instance of [System.Net.Http.HttpClient](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient) uses the browser for handling the HTTP traffic in the background. Its [BaseAddress](https://docs.microsoft.com/dotnet/api/system.net.http.httpclient.baseaddress) is automatically set to the base URI prefix of the app. | | ||
|
||
Note that it is possible to use a custom services provider instead of the default `BrowserServiceProvider` that's added by the default template. A custom service provider doesn't automatically provide the default services listed in the table. Those services must be added to the new service provider explicitly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SteveSandersonMS Is this a bug? Shouldn't we populate the custom service provider with the default services?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That’s not something we have the option to do. If the developer provides their own IServiceProvider we can’t control what it returns; it’s up to them to delegate calls to a BrowserServiceProvider if they want to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That seems surprisingly different to how things work in ASP.NET Core. In ASP.NET Core you can switch to using a different IoC container (Autofac or whatever) but you still get the host provided services. But no need to hold up this PR on this discussion as the doc is currently correct.
docs/dependency-injection.md
Outdated
} | ||
``` | ||
|
||
In components derived from the base class, the `@inject` directive isn't required. The `InjectAttribute` of the base class is satisfactory: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
satisfactory -> sufficient
docs/dependency-injection.md
Outdated
... | ||
} | ||
} | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a link to https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection somewhere. Maybe just have an additional resources section at the end?
docs/dependency-injection.md
Outdated
|
||
## Service lifetime | ||
|
||
Note that Blazor doesn't automatically dispose injected services that implement `IDisposable`. Components can implement `IDisposable`. Services are disposed when the user navigates away from the component. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Services are disposed when the user navigates away from the component.
I think this should say: "Components are disposed when the user navigates away from the component, at which point the component can dispose any transient services"
Fixed everything outside of the custom service provider with the default services question. |
docs/dependency-injection.md
Outdated
```csharp | ||
static void Main(string[] args) | ||
{ | ||
var serviceProvider = new BrowserServiceProvider(configure => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more thing: I'm proposing that rename configure
to services
so that it's consistent with ASP.NET Core.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danroth27 One little thing tho that I find strange about this one ...
If the component is disposed when the user navigates away ... then how can it dispose transient services? ... It reads like it's doing something after it has been destroyed. Can we restructure it? ... e.g. ...
[EDIT] I used that ☝️ version. Let me know if we need to revert or update it. |
The more I think about this less sure I am that having disposable transient services is a thing. You didn't create the service, so it's weird that you would be responsible for disposing it. @pakrym What's your thoughts on this? |
@danroth27 That requirement is described in https://github.com/aspnet/Blazor/issues/464 |
You don't usually create singleton services, transients are created and should be disposed by DI container. |
docs/dependency-injection.md
Outdated
* The applicable constructor must be *public*. | ||
* There must only be one applicable constructor. In case of an ambiguity, DI throws an exception. | ||
|
||
## Service lifetime |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Services are created by the service provider, so they need to be disposed by the service provider. We need to address this as part of https://github.com/aspnet/Blazor/issues/464, but regardless I think this section should be removed as it contradicts that guidance. The content about implementing IDisposable
on components should be covered in the components doc, not here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
Fixes #6
This pull request adds a chapter about DI in Blazor as discussed with @guardrex. Compared to the version he reviewed, I added a code sample at the end of the document showing how to implement
IDisposable
in a Blazor component.