Skip to content

Commit 563f422

Browse files
Merge pull request #24 from windows-toolkit/feature/preview2-update
Feature/preview2 update
2 parents f7fd837 + f11f318 commit 563f422

File tree

6 files changed

+222
-67
lines changed

6 files changed

+222
-67
lines changed

docs/mvvm/AsyncRelayCommand.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ The [`AsyncRelayCommand`](https://docs.microsoft.com/dotnet/api/microsoft.toolki
1515

1616
`AsyncRelayCommand` and `AsyncRelayCommand<T>` have the following main features:
1717

18-
- They extend the functionalities of the non-asynchronous commands included in the library, with support for `Task`-returning delegates.
18+
- They extend the functionalities of the synchronous commands included in the library, with support for `Task`-returning delegates.
19+
- They can wrap asynchronous functions with an additional `CancellationToken` parameter to support cancelation, and they expose a `CanBeCanceled` and `IsCancellationRequested` properties, as well as a `Cancel` method.
1920
- They expose an `ExecutionTask` property that can be used to monitor the progress of a pending operation, and an `IsRunning` that can be used to check when an operation completes. This is particularly useful to bind a command to UI elements such as loading indicators.
2021
- They implement the `IAsyncRelayCommand` and `IAsyncRelayCommand<T>` interfaces, which means that viewmodel can easily expose commands using these to reduce the tight coupling between types. For instance, this makes it easier to replace a command with a custom implementation exposing the same public API surface, if needed.
2122

docs/mvvm/Ioc.md

Lines changed: 98 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,82 @@
11
---
22
title: Ioc
33
author: Sergio0694
4-
description: A type that facilitates the use of the IServiceProvider type
4+
description: An introduction to the use of the IServiceProvider type through the Microsoft.Extensions.DependencyInjection APIs
55
keywords: windows 10, uwp, windows community toolkit, uwp community toolkit, uwp toolkit, mvvm, service, dependency injection, net core, net standard
66
dev_langs:
77
- csharp
88
---
99

1010
# Ioc ([Inversion of control](https://en.wikipedia.org/wiki/Inversion_of_control))
1111

12-
The [`Ioc`](https://docs.microsoft.com/dotnet/api/microsoft.toolkit.mvvm.DependencyInjection.Ioc) class is a type that facilitates the use of the `IServiceProvider` type. It's powered by the `Microsoft.Extensions.DependencyInjection` package, which provides a fully featured and powerful DI set of APIs, and acts as an easy to setup and use `IServiceProvider`.
12+
A common pattern that can be used to increase modularity in the codebase of an application using the MVVM pattern is to use some form of inversion of control. One of the most common solution in particular is to use dependency injection, which consists in creating a number of services that are injected into backend classes (ie. passed as parameters to the viewmodel constructors) - this allows code using these services not to rely on implementation details of these services, and it also makes it easy to swap the concrete implementations of these services. This pattern also makes it easy to make platform-specific features available to backend code, by abstracting them through a service which is then injected where needed.
13+
14+
The MVVM Toolkit doesn't provide built-in APIs to facilitate the usage of this pattern, as there already exist dedicated libraries specifically for this such as the `Microsoft.Extensions.DependencyInjection` package, which provides a fully featured and powerful DI set of APIs, and acts as an easy to setup and use `IServiceProvider`. The following guide will refer to this library and provide a series of examples of how to integrate it into applications using the MVVM pattern.
1315

1416
## Configure and resolve services
1517

16-
The main entry point is the `ConfigureServices` method, which can be used like so:
18+
The first step is to declare an `IServiceProvider` instance, and to initialize all the necessary services, usually at startup. For instance, on UWP (but a similar setup can be used on other frameworks too):
1719

1820
```csharp
19-
// Register the services at startup
20-
Ioc.Default.ConfigureServices(services =>
21+
public sealed partial class App : Application
2122
{
22-
services.AddSingleton<IFilesService, FilesService>();
23-
services.AddSingleton<ISettingsService, SettingsService>();
24-
// Other services here...
25-
});
23+
public App()
24+
{
25+
Services = ConfigureServices();
26+
27+
this.InitializeComponent();
28+
}
29+
30+
/// <summary>
31+
/// Gets the current <see cref="App"/> instance in use
32+
/// </summary>
33+
public new static App Current => (App)Application.Current;
34+
35+
/// <summary>
36+
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
37+
/// </summary>
38+
public IServiceProvider Services { get; }
39+
40+
/// <summary>
41+
/// Configures the services for the application.
42+
/// </summary>
43+
private static IServiceProvider ConfigureServices()
44+
{
45+
var services = new ServiceCollection();
46+
47+
services.AddSingleton<IFilesService, FilesService>();
48+
services.AddSingleton<ISettingsService, SettingsService>();
49+
services.AddSingleton<IClipboardService, ClipboardService>();
50+
services.AddSingleton<IShareService, ShareService>();
51+
services.AddSingleton<IEmailService, EmailService>();
2652

27-
// Retrieve a service instance when needed
28-
IFilesService fileService = Ioc.Default.GetService<IFilesService>();
53+
return services.BuildServiceProvider();
54+
}
55+
}
2956
```
3057

31-
The `Ioc.Default` property offers a thread-safe `IServiceProvider` instance that can be used anywhere in the application to resolve services. The `ConfigureService` method handles the initialization of that service. It is also possible to create different `Ioc` instances and to initialize each with different services.
58+
Here the `Services` property is initialized at startup, and all the application services and viewmodels are registered. There is also a new `Current` property that can be used to easily access the `Services` property from other views in the application. For instance:
59+
60+
```csharp
61+
IFilesService filesService = App.Current.Services.GetService<IFilesService>();
62+
63+
// Use the files service here...
64+
```
65+
66+
The key aspect here is that each service may very well be using platform-specific APIs, but since those are all abstracted away through the interface our code is using, we don't need to worry about them whenever we're just resolving an instance and using it to perform operations.
3267

3368
## Constructor injection
3469

3570
One powerful feature that is available is "constructor injection", which means that the DI service provider is able to automatically resolve indirect dependencies between registered services when creating instances of the type being requested. Consider the following service:
3671

3772
```csharp
38-
public class ConsoleLogger : ILogger
73+
public class FileLogger : IFileLogger
3974
{
40-
private readonly IFileService FileService;
75+
private readonly IFilesService FileService;
4176
private readonly IConsoleService ConsoleService;
4277

43-
public ConsoleLogger(
44-
IFileService fileService,
78+
public FileLogger(
79+
IFilesService fileService,
4580
IConsoleService consoleService)
4681
{
4782
FileService = fileService;
@@ -52,22 +87,61 @@ public class ConsoleLogger : ILogger
5287
}
5388
```
5489

55-
Here we have a `ConsoleLogger` implementing the `ILogger` interface, and requiring `IFileService` and `IConsoleService` instances. Constructor injection means the DI service provider will "automagically" gather all the necessary services, like so:
90+
Here we have a `FileLogger` type implementing the `IFileLogger` interface, and requiring `IFilesService` and `IConsoleService` instances. Constructor injection means the DI service provider will automatically gather all the necessary services, like so:
5691

5792
```csharp
58-
// Register the services at startup
59-
Ioc.Default.ConfigureServices(services =>
93+
/// <summary>
94+
/// Configures the services for the application.
95+
/// </summary>
96+
private static IServiceProvider ConfigureServices()
6097
{
61-
services.AddSingleton<IFileService, FileService>();
98+
var services = new ServiceCollection();
99+
100+
services.AddSingleton<IFilesService, FilesService>();
62101
services.AddSingleton<IConsoleService, ConsoleService>();
63-
services.AddSingleton<ILogger, ConsoleLogger>();
64-
});
102+
services.AddSingleton<IFileLogger, FileLogger>();
103+
104+
return services.BuildServiceProvider();
105+
}
65106

66107
// Retrieve a logger service with constructor injection
67-
ILogger consoleLogger = Ioc.Default.GetService<ILogger>();
108+
IFileLogger fileLogger = App.Current.Services.GetService<IFileLogger>();
109+
```
110+
111+
The DI service provider will automatically check whether all the necessary services are registered, then it will retrieve them and invoke the constructor for the registered `IFileLogger` concrete type, to get the instance to return.
112+
113+
## What about viewmodels?
114+
115+
A service provider has "service" in its name, but it can actually be used to resolve instances of any class, including viewmodels! The same concepts explained above still apply, including constructor injection. Imagine we had a `ContactsViewModel` type, using an `IContactsService` and an `IPhoneService` instance through its constructor. We could have a `ConfigureServices` method like this:
116+
117+
```csharp
118+
/// <summary>
119+
/// Configures the services for the application.
120+
/// </summary>
121+
private static IServiceProvider ConfigureServices()
122+
{
123+
var services = new ServiceCollection();
124+
125+
// Services
126+
services.AddSingleton<IContactsService, ContactsService>();
127+
services.AddSingleton<IPhoneService, PhoneService>();
128+
129+
// Viewmodels
130+
services.AddTransient<ContactsViewModel>();
131+
132+
return services.BuildServiceProvider();
133+
}
68134
```
69135

70-
The DI service provider will automatically check whether all the necessary services are registered, then it will retrieve them and invoke the constructor for the registered `ILogger` concrete type, to get the instance to return - all done automatically!
136+
And then in our `ContactsView`, we would assign the data context as follows:
137+
138+
```csharp
139+
public ContactsView()
140+
{
141+
this.InitializeComponent();
142+
this.DataContext = App.Current.Services.GetService<ContactsViewModel>();
143+
}
144+
```
71145

72146
## More docs
73147

0 commit comments

Comments
 (0)