SolvedWindowsCommunityToolkit [Feature] Basic MVVM primitives (.NET Standard)
βοΈAccepted Answer
Relaying some updates here from the UWP Discord server to keep everyone up to date.
Following an extensive conversation with @Aminator and some prototyping, I went ahead and did a number of changes in #3229, specifically related to the dependency injection area:
- Completely nuked the old
Ioc
class/interface. - The package now has a dependency on
Microsoft.Extensions.DependencyInjection
so we can use those types directly (eg.IServiceProvider
), which make the package better for interoperability, saves us work/test/maintenance and can also act as an easy to use "entry point" for users to become familiar with the APIs from that package. @azchohfi I think you'll be happy about this since you were the one that suggested that package from the startπ - With the better integration now, the
ViewModelBase
class now takes the generalIServiceProvider
interface in its secondary constructors, so users also have the ability to do DI with their own service providers coming from somewhere else. - On the topic of better integration, with this change we'd now have both the toolkit and the Windows Template Studio all using the same exact DI APIs, coming from an official 1st party Microsoft package (
Microsoft.Extensions.DependencyInjection
). - The
Ioc
class now acts as a super easy to use service configurator for those APIs. - Tested this already on UWP Debug/Release as well, works just fine.
Sample usage of how to configure services with the new Ioc
class:
Ioc.Default.ConfigureServices(services =>
{
services.AddSingleton<ILogger, Logger>();
services.AddSingleton<IPrinter, PrinterService>();
// ...
});
The Ioc
class then exposes an IServiceProvider ServiceProvider
property which is automatically used by ViewModel
base if no other provider is specified, which means that users can just use that code to initialize services, and then every single ViewModel
in their app will automatically have the resulting IServiceProvider
instance being injected. They can then just use all the extensions available from the Microsoft.Extensions.DependencyInjection
package to get service instances from that provider.
This solution has the advantage of being both easy to use, highly customizable, and using the same APIs from Microsoft.Extensions.DependencyInjection
while still having the same behavior from the original Ioc
class from MvvmLight
. Plus we don't have any code to maintain at all in that area.
Other general changes from the PR (and a few more points for @skendrot I forgot to mention the other day). Regarding having basic MVVM building blocks in the toolkit, I realized the toolkit actually already does have a few of them. In particular Singleton<T>
and NotifyTaskCompletion<TResult>
:
- The
Singleton<T>
is scheduled for removal in7.0
already (see #3134), but that's just for performance reasons and not lack of usage. In fact, that class was basically a minimalistic inversion of control container. With the newMvvm
package, users relying on that class will be able to easily switch to the newIoc
class without losing any functionality (and gaining the additional features). - The
NotifyTaskCompletion<TResult>
class is being replaced by theSetAndNotifyOnCompletion
method fromObservableObject
, which exposes the same functionalities (and more) without requiring dedicated types at all, which also makes it more efficient and makes the code easier to read. Here is how the new method can be used in a small sample:
private Task<string> myTask = Task.FromResult<string>(null);
public Task<string> MyTask
{
get => myTask;
private set => SetAndNotifyOnCompletion(ref myTask, () => myTask, value);
}
<TextBlock>
<Run Text="Task status:"/>
<Run Text="{x:Bind ViewModel.MyTask.Status, Mode=OneWay}"/>
<LineBreak/>
<Run Text="Result:"/>
<Run
xmlns:ex="using:Microsoft.Toolkit.Extensions"
Text="{x:Bind ex:TaskExtensions.ResultOrDefault(ViewModel.MyTask), Mode=OneWay}"/>
</TextBlock>
Which results in this with a function that waits two seconds and then returns "Hello world!"
:
Same feature as before (but in a dedicated, specialized package now), and without the additional "proxy" object, so that the property you have in your models is actually of type Task<T>
Other Answers:
To add to this, I had ported @StephenCleary's MVVM Async helpers from his articles to UWP here. Though I didn't realize he had them on GitHub already here (though not sure if UWP enabled).
Also, Windows Template Studio has an MVVM Basic pattern with some basic helpers, this could be an opportunity to align those in the toolkit instead of part of their template engine. FYI @sibille.
@Sergio0694 I think the best place to start before we dive deeper in code, is to talk more to @lbugnion and @sibille and figure out the best path forward (even if community driven) for longer-term support of a light-weight MVVM framework.
@Sergio0694 and @michael-hawker I think adding basic MVVM classes to the Toolkit makes a lot of sense.
Among the goals for the MVVMBasic implementation in Windows Template Studio was to provide a solution for people who:
- can't or don't want to use a 3rd party MVVM Framework
- want to build their own MVVMFramework
https://github.com/microsoft/WindowsTemplateStudio/blob/dev/docs/UWP/frameworks/mvvmbasic.md
Not sure if this is important to a lot of people, if it is, in WTS we could still offer people both options MVVMBasic (using the WCT) or MVVMBasic (generating the classes in the code).
Adding "basic" mvvm tooling has been discussed numerous times, both publicly and privately.
Here are a few I found easily:
- #556: Move commands from sample project to Toolkit
- #831: INotifyPropertyChanged base class
- #943: Add DelegateCommand/RelayCommand to the Toolkit
- #2444: Rx Mvvm Framework
- #2893: Do we need a Messenger system or event bus for the pub-sub pattern?
There are a lot of frameworks already out there.
Just to name a few:
- MVVM Light
- Prism
- Caliburn Micro
- MVVM Cross
MVVM Light is just one implementation. If it seems that MVVM Light needs a maintainer or work to make it better or "more light" wouldn't it be best to put work into that project? It's already established, it's well known, and it's it's heavily used (as a MVVM framework. Not saying this toolkit isn't used).
MVVM Frameworks always start out as something small. Then more and more features are added making them larger and larger. Creating a framework within the toolkit would just explode to being more and more.
My suggestion is that we should maintain the stance that there are already frameworks out there and that's not what this toolkit is meant for.
@hansmbakker Sergio wants to explicitly not use constructor injection and instead uses the anti-pattern where you access the IServiceProvider
directly in your view model. The ViewModelBase
accesses a static instance of the IServiceProvider
by default, that's why it doesn't need to be registered as well.
This is the opposite to what I do in my apps. I always use constructor injection in my view models and services, which is also why I don't need the Ioc
or ViewModelBase
classes at all. Singleton and sometimes transient view models are also part of the service provider if you use this pattern. This has the benefit that the view models and services don't know where their dependencies come from and can also be instantiated normally if needed. This is also the recommended pattern in ASP.NET.
Lastly, views are never part of the service provider because those should generally have an empty constructor to be able to instantiate them in XAML, which would interfere with constructor injection. The services a view needs should come from the associated view model instead, which is typically the DataContext
property and exposed as a strongly typed ViewModel
property to the view for proper support with x:Bind
.
Describe the problem this feature would solve
Follow up from the conversation with @michael-hawker on the UWP Community Discord server.
With the
MvvmLight
library not being supported anymore, and other existing libraries being much bigger in scope that what is often needed for simple apps, I was wondering whether we should add some basics MVVM primitives to the toolkit, possibly in a newMicrosoft.Toolkit.Mvvm
package so that users not in need of those wouldn't have to reference those too. We're also using this occasion to integrate with theMicrosoft.Extensions.DependencyInjection
package to provide dependency injection features, so that it'll be both easier for devs to interoperate between this package and existing code, as well as allowing us to offload that part of the code to an official library from Microsoft.All this would come together in a new
Microsoft.Toolkit.Mvvm
package.Preview package
The CI is now running for #3229 , so you should be able to just get the latest package from the CI build artifacts.
Key Tenants
Draft docs hereπ
Feature setπ§°
The idea for this package would be to have a series of common, self-contained, lightweight types:
ObservableObject
ViewModelBase
Ioc
RelayCommand
RelayCommand<T>
AsyncRelayCommand
AsyncRelayCommand<T>
IRelayCommand
IRelayCommand<in T>
IAsyncRelayCommand
IAsyncRelayCommand<in T>
Messenger
IMessenger
PropertyChangedMessage<T>
RequestMessage<T>
AsyncRequestMessage<T>
CollectionRequestMessage<T>
AsyncCollectionRequestMessage<T>
ValueChangedMessage<T>
These types alone are usually enough for many users building apps using the MVVM toolkit.
Having these built right into the toolkit would allow them to just reference the new package and bet all set, without having to go dig through other documentation and referencing a series of external packages just to gain access to these basic types. Furthermore, we could keep these smaller in scopes than other big libraries like
autofac
orPrism
, which include a lot of features that are not necessarily useful for many (particularly non-enterprise) devs, while still taking a toll on performance and memory usage. This would also make the learning curve much less steep for developers.As an example of the benefit of a more modern codebase, which is both more optimized and minimalistic, here's a benchmark showing the performance of the
Messenger
class from bothMvvmLight
andCalcium
, and the one from the related PR (WCT
stands for this newMvvm
package). Note thatCalcium
only supports a single channel mode:Some summarized results for the
Messenger
class from #3229:MvvmLight
, and ~11% faster thanCalcium
. At the same time, it uses about 38 million times less memory thanMvvmLight
, specifically just 20 bytes vs. almost 700MB (!), and about 387,000x less memory thanCalcium
as well, specifically again just 20 bytes vs. about 7 MB.MvvmLight
, and using almost 7 million times less memory, specifically just 416 bytes vs almost 3 GB (!).The full benchmark code can be found here.
Describe the solution
As mentioned above, and as can be seen in the related PR #3229, this proposal is to add a new
Microsoft.Toolkit.Mvvm
package, integrating some features fromMvvmLight
, refactoring them when needed, in order to offer a self-contained alternative. The package would not need a reference to any otherMicrosoft.Toolkit
packages, it would be lightweight and focused on ease of use, performance and memory efficiency.Since it targets .NET Standard 2.0, this means that it can be used anywhere from UWP apps, to Uno, Xamarin, Unity, ASP.NET, etc. Literally any framework supporting the .NET Standard 2.0 feature set. There are no platform specific dependencies at all. The whole package is entirely platform, runtime, and UI stack agnostic.
Additionally, it would integrate with the existing
Microsoft.Extensions.DependencyInjection
package, to offer a well known and tested DI platform, with the addition of an easy to use entry point in the toolkit, especially for new users. This would also remove a lot of code from the actualMvvm
package, as there would not be the need to write and maintain yet another DI library or set of APIs.As mentioned above, the package would target .NET Standard 2.0 to have as much reach as possible, and it wouldn't have any platform-specific dependencies.
Having a set of MVVM-enabled features built-in into the toolkit would align with the general philosophy of the toolkit to help developers getting started in common scenarios (such as with the MVVM pattern), and it would complement all the available APIs from the BCL that just lack a "reference" implementation (such as
INotifyPropertyChanged
orICommand
).Furthermore, having a package like this being published by a Microsoft supported project directly would be a big selling points for users not wanting to add "3rd party" references to their projects (this point has been brought up before in the comments below as well by other users).
Describe alternatives you've considered
As mentioned above, common alternatives like
autofac
,Fody
orPrism
are much bigger in scope than what many devs typically need and introduce more overhead.MvvmLight
on the other hand is also not supported anymore, other than some forks.