Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Microsoft.Toolkit.Mvvm package #3229

Merged
merged 256 commits into from
Aug 1, 2020
Merged
Show file tree
Hide file tree
Changes from 250 commits
Commits
Show all changes
256 commits
Select commit Hold shift + click to select a range
2c34963
Minor code refactoring
Sergio0694 Apr 9, 2020
1a72762
Added IoC.IsRegistered<TService>() API
Sergio0694 Apr 9, 2020
52a79f2
Minor bug fixes
Sergio0694 Apr 9, 2020
9dcca03
Added Ioc.GetAllRegisteredServices() API
Sergio0694 Apr 9, 2020
bfb8ccb
Minor code refactoring
Sergio0694 Apr 9, 2020
a58ecdd
Added Ioc.Unregister<TService>() API
Sergio0694 Apr 9, 2020
4554d3a
Added Ioc.Reset() API, minor bug fixes
Sergio0694 Apr 9, 2020
2cb2675
Added tests for the Ioc class
Sergio0694 Apr 9, 2020
f337280
Added Ioc.GetAllCreatedServices() API
Sergio0694 Apr 9, 2020
4f65e95
Minor code refactoring
Sergio0694 Apr 9, 2020
df973ea
Added Ioc.GetInstanceWithoutCaching<TService() API
Sergio0694 Apr 9, 2020
ce4b023
Minor code refactorings
Sergio0694 Apr 9, 2020
155b9de
Fixed an XML comment
Sergio0694 Apr 9, 2020
9d57eaf
Merge branch 'master' into feature/mvvm-apis
Sergio0694 Apr 9, 2020
e409d94
Fixed refactoring bugs
Sergio0694 Apr 9, 2020
4647330
Speed optimizations to Ioc.GetInstanceWithoutCaching<TService>()
Sergio0694 Apr 10, 2020
1c7cfe7
Added Ioc.TryGetInstance<TService> API
Sergio0694 Apr 10, 2020
6969545
Minor code refactoring
Sergio0694 Apr 10, 2020
5c88279
Fixed unit tests
Sergio0694 Apr 10, 2020
3b13c9e
Added empty Microsoft.Toolkit.Mvvm package
Sergio0694 Apr 13, 2020
ed5b3a1
Moved Ioc class to separate package
Sergio0694 Apr 13, 2020
47f1373
Added ObservableObject class
Sergio0694 Apr 13, 2020
bcd2674
Minor code style tweaks
Sergio0694 Apr 13, 2020
08dfe8a
Minor code refactorings
Sergio0694 Apr 13, 2020
ad613c3
Added RelayCommand type
Sergio0694 Apr 13, 2020
f93ae4f
Added the RelayCommand<T> type
Sergio0694 Apr 13, 2020
1e75231
Minor code refactoring
Sergio0694 Apr 13, 2020
79f4f4b
Ported minimal DictionarySlim<TKey, TValue> type from CoreFXLabs
Sergio0694 Apr 14, 2020
bb7db86
Added the IDictionary<in TKey> interface
Sergio0694 Apr 14, 2020
8963f3a
Initial draft of the Messenger class
Sergio0694 Apr 14, 2020
2e95d48
Added missing file headers
Sergio0694 Apr 14, 2020
805d940
Code tweaks and performance improvements
Sergio0694 Apr 14, 2020
5eacb12
Minor code style tweaks
Sergio0694 Apr 14, 2020
ae6ff24
Added DictionarySlim<TKey, TValue>.ContainsKey method
Sergio0694 Apr 14, 2020
0f7f2ac
Added Messenger.IsRegistered methods
Sergio0694 Apr 14, 2020
d0b110b
Added IReadOnlyDictionary<in TKey, out TValue> interface
Sergio0694 Apr 14, 2020
43c7202
Fixed some XML docs
Sergio0694 Apr 14, 2020
45c794d
Fixed a bug with new values being created when not needed
Sergio0694 Apr 14, 2020
6c68dcf
Renamed some interfaces to avoid naming collisions
Sergio0694 Apr 14, 2020
852e85e
Added Messenger.Unregister<TToken>() method
Sergio0694 Apr 14, 2020
3af3aff
Added more comments in the Messenger class
Sergio0694 Apr 14, 2020
ea68036
Added DictionarySlim<TKey, TValue>.Clear method
Sergio0694 Apr 14, 2020
7ab96f3
Added Messenger.Reset method
Sergio0694 Apr 14, 2020
6dc71d0
Added initial Messenger unit tests
Sergio0694 Apr 14, 2020
8c2cced
Minor code style tweaks
Sergio0694 Apr 15, 2020
6664609
Merge pull request #20 from Sergio0694/feature/high-perf-messenger
Sergio0694 Apr 15, 2020
bca38f5
Code refactoring
Sergio0694 Apr 15, 2020
de0d2ba
Added Messenger APIs with no input message
Sergio0694 Apr 15, 2020
6d22101
Added RequestMessageBase<T> type
Sergio0694 Apr 15, 2020
2cd0a20
Added Messenger request methods
Sergio0694 Apr 15, 2020
595474c
Minor code refactoring
Sergio0694 Apr 15, 2020
82bf497
Added PropertyChangedMessage<T> type
Sergio0694 Apr 15, 2020
82946f9
Added ViewModelBase class
Sergio0694 Apr 15, 2020
e549aef
The Messenger class is not static anymore
Sergio0694 Apr 15, 2020
1121fbe
Added messaging customization to ViewModelBase
Sergio0694 Apr 15, 2020
1e3e1e4
The Ioc class is not static anymore
Sergio0694 Apr 15, 2020
7396d08
Added dependency injection of service provider in ViewModelBase
Sergio0694 Apr 15, 2020
6fa643b
Code refactoring
Sergio0694 Apr 15, 2020
c40fc58
Added IIoc interface
Sergio0694 Apr 15, 2020
dd7e973
Added IMessenger interface
Sergio0694 Apr 15, 2020
fc63822
Moved request methods to extensions
Sergio0694 Apr 15, 2020
5b1258b
Code refactoring
Sergio0694 Apr 15, 2020
59a6a87
Added tests for ObservableObject
Sergio0694 Apr 15, 2020
245745b
Added tests for ViewModelBase
Sergio0694 Apr 15, 2020
fd3aba3
Added tests for relay commands
Sergio0694 Apr 15, 2020
c0a863b
Messenger XML comments improved
Sergio0694 Apr 15, 2020
2b38bac
Added ObservableObject.SetAndNotifyOnCompletion<TTask> method
Sergio0694 Apr 16, 2020
22aa752
Fixed a comment
Sergio0694 Apr 16, 2020
53982ad
Removed unnecessary using directives
Sergio0694 Apr 16, 2020
774ed79
Fixed an incorrect comment
Sergio0694 Apr 16, 2020
61248d2
Minor code tweaks
Sergio0694 Apr 16, 2020
58f3c64
Added AsyncRelayCommand type
Sergio0694 Apr 16, 2020
29daf8e
Added AsyncRelayCommand<T> type
Sergio0694 Apr 16, 2020
9c85a3c
Bug fixes in ObservableObject.SetAndNotifyOnCompletion
Sergio0694 Apr 16, 2020
61dd57d
Added ICommand<T> interface
Sergio0694 Apr 16, 2020
cc93648
Merge branch 'master' into feature/mvvm-apis
Sergio0694 Apr 16, 2020
7ea0d31
Code refactoring, removed build warnings
Sergio0694 Apr 16, 2020
6e90258
Code refactoring, added the IRelayCommand interface
Sergio0694 Apr 16, 2020
3497b58
Minor improvements and code tweaks
Sergio0694 Apr 17, 2020
6352082
Code refactoring, minor performance improvements
Sergio0694 Apr 17, 2020
a3ada63
Removed unnecessary notnull constraints
Sergio0694 Apr 17, 2020
05ef4fa
Minor code style tweaks
Sergio0694 Apr 17, 2020
0d80519
Simplified commands namespace structure
Sergio0694 Apr 17, 2020
900cc98
Renamed Services namespace to DependencyInjection
Sergio0694 Apr 17, 2020
2913e8f
Code style updates
Sergio0694 Apr 17, 2020
4128421
Fixed an incorrect using directive
Sergio0694 Apr 17, 2020
fdf01e3
Minor performance improvements to the Messenger class
Sergio0694 Apr 17, 2020
6bb3d4c
Minor memory improvements to the Messenger class
Sergio0694 Apr 17, 2020
d077563
Minor bug fixes
Sergio0694 Apr 17, 2020
7e623c0
Added Type2 struct
Sergio0694 Apr 17, 2020
1bffb04
Minor code tweaks
Sergio0694 Apr 17, 2020
63baa83
Fixed missing cleanup from Messenger.Unsubscribe(object)
Sergio0694 Apr 17, 2020
277effc
Fixed missing file headers
Sergio0694 Apr 17, 2020
399b10b
Minor API rename for consistency with WinUI
Sergio0694 Apr 17, 2020
56dfbaa
Updated project description
Sergio0694 Apr 17, 2020
f03dadb
Fixed an incorrect XML docs
Sergio0694 Apr 17, 2020
543172e
Renamed Commands folder to Input to follow namespace
Sergio0694 Apr 17, 2020
14e216b
Added more Messenger unregistration tests
Sergio0694 Apr 17, 2020
7c5928b
Improved IMessenger XML docs
Sergio0694 Apr 17, 2020
0bbfe9e
Added more fail tests in unit tests, improved documentation
Sergio0694 Apr 17, 2020
34c7c6d
Added more Messenger unit tests
Sergio0694 Apr 18, 2020
604cf7a
Improved internal documentation
Sergio0694 Apr 18, 2020
295b808
Added IAsyncRelayCommand and IAsyncRelayCommand<T> interfaces
Sergio0694 Apr 18, 2020
eab207c
Minor codegen improvement
Sergio0694 Apr 18, 2020
d5b8ece
Added nullability notation to command parameters
Sergio0694 Apr 18, 2020
97e7ace
Fixed an XML comment
Sergio0694 Apr 18, 2020
0655edb
Added nullability attributes for uncostrained T parameters
Sergio0694 Apr 18, 2020
9ea2e98
Improved nullability handling in commands
Sergio0694 Apr 18, 2020
06139b0
Added TaskExtensions class
Sergio0694 Apr 19, 2020
474d72e
Added tests for the TaskExtensions class
Sergio0694 Apr 19, 2020
c5f0b59
Added interoperability with IServiceProvider interface
Sergio0694 Apr 19, 2020
74cc8dc
Renamed some APIs
Sergio0694 Apr 19, 2020
01662e7
Code refactorings
Sergio0694 Apr 19, 2020
2c9ec04
Refactored Ioc class to use ASP.NET service provider
Sergio0694 Apr 19, 2020
36a819f
Added support for ServiceProviderOptions
Sergio0694 Apr 19, 2020
717fb9a
Updated project description
Sergio0694 Apr 19, 2020
86d3b49
Ioc optimization, ViewModelBase refactoring
Sergio0694 Apr 20, 2020
719e77d
Code refactoring, improved API surface
Sergio0694 Apr 20, 2020
dcb059f
Minor code style tweaks
Sergio0694 Apr 20, 2020
2f7c49e
Added Set<T> overload with target expression
Sergio0694 Apr 22, 2020
468929c
Added ValueChangedMessage<T> type
Sergio0694 Apr 22, 2020
f0b5bf0
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 Apr 23, 2020
6129c69
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 Apr 30, 2020
fdaa307
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 May 6, 2020
db41e0d
Merge remote-tracking branch 'upstream/master' into master2
Sergio0694 May 13, 2020
6b5c46b
Merge branch 'master2' into feature/mvvm-apis
Sergio0694 May 13, 2020
d1ae930
Updated multiline comments style
Sergio0694 May 13, 2020
0b36b21
Added lbugnion/mvvmlight license to ThirdPartyNotices.txt
Sergio0694 May 14, 2020
84eb856
Added note to refer to the ThirdPartyNotices.txt file
Sergio0694 May 14, 2020
a86d1c5
Fixed file headers
Sergio0694 May 14, 2020
0cc2aef
Minor code fixes
Sergio0694 May 14, 2020
5d6189e
Code refactoring to ObservableObject and ViewModelBase
Sergio0694 May 16, 2020
0de7441
Enabled ViewModelBase.IsActive broadcast
Sergio0694 May 16, 2020
fa4b2ef
Added .NET Standard 2.1 target
Sergio0694 May 16, 2020
24557f9
Added default implementations to IMessenger
Sergio0694 May 16, 2020
e0199ab
Renamed IMessengerExtensions class
Sergio0694 May 16, 2020
81c64ff
Copied the Unit type to the extensions class
Sergio0694 May 16, 2020
015913a
Added new IMessenger extensions
Sergio0694 May 16, 2020
2a75480
Removed unnecessary interface methods
Sergio0694 May 16, 2020
f0fb4cb
Removed .NET Standard 2.1 target
Sergio0694 May 16, 2020
93c653e
Removed original Unit type
Sergio0694 May 16, 2020
bcf2476
Renamed request extensions file
Sergio0694 May 16, 2020
672e861
Minor XML docs improvements
Sergio0694 May 16, 2020
72cff23
Merge branch 'dev/7.0.0' of https://github.com/windows-toolkit/Window…
Sergio0694 May 26, 2020
7ad0cd7
Merge branch 'windows-toolkit-dev/7.0.0' into feature/mvvm-apis
Sergio0694 May 26, 2020
99d8806
Moved Mvvm tests to shared project
Sergio0694 May 26, 2020
d5fbe05
Fixed merge error in 3rd party notices
Sergio0694 May 28, 2020
283af26
Updated Microsoft.Extensions.DependencyInjection package
Sergio0694 Jun 2, 2020
bb97787
Tweaked some XML docs
Sergio0694 Jun 2, 2020
65fa303
Added support for proxy fields with LINQ expression
Sergio0694 Jun 2, 2020
0b7d0a0
Improved value type parameters when canExecute is not used
Sergio0694 Jun 4, 2020
69a9ff3
Code refactoring, improved Request pattern APIs
Sergio0694 Jun 5, 2020
7df8a85
Minor API tweaks and comments improvements
Sergio0694 Jun 5, 2020
c6b4cb6
Minor bug fix
Sergio0694 Jun 5, 2020
121d564
Merge branch 'dev/7.0.0' into feature/mvvm-apis
Sergio0694 Jun 20, 2020
ec675df
Updated NuGet packages, tweaked Mvvm .csproj
Sergio0694 Jun 20, 2020
50a6095
Removed unnecessary attributes
Sergio0694 Jun 20, 2020
9a9aa6b
Added new Set<T> overloads with callback
Sergio0694 Jun 20, 2020
f71923e
Deprecated NotifyTaskCompletion<TResult> type
Sergio0694 Jun 22, 2020
6ed7b5e
Code refactoring, added TaskResultConverter
Sergio0694 Jun 22, 2020
8a7cd06
Added missing XML note about return type
Sergio0694 Jun 22, 2020
0c4669b
Moved al messenger-related functionality to ViewModelBase
Sergio0694 Jun 22, 2020
ceba0d4
Merge branch 'dev/7.0.0' into feature/mvvm-apis
Sergio0694 Jun 23, 2020
f86a79b
Minor code tweak
Sergio0694 Jun 23, 2020
3a10a95
Added override support for events in ObservableObject
Sergio0694 Jun 24, 2020
ab467ec
Fixed a typo
Sergio0694 Jun 24, 2020
60b1a65
Improved nullability annotations
Sergio0694 Jun 24, 2020
2d470ea
Added IDictionarySlim<TKey, TValue>.TryRemove API
Sergio0694 Jun 24, 2020
5f06cde
Added object base version of TryRemove API
Sergio0694 Jun 24, 2020
2876669
Added IDictionarySlim interface
Sergio0694 Jun 24, 2020
cf053f6
Added property to track the total handlers
Sergio0694 Jun 24, 2020
279a5e9
~17% speedup in Messenger.Send
Sergio0694 Jun 24, 2020
8f2bfe2
Removed unnecessary overload
Sergio0694 Jun 24, 2020
10cafd5
~88% speedup in Messenger.Send
Sergio0694 Jun 24, 2020
849c3dd
Merge pull request #24 from Sergio0694/optimization/faster-messenger
Sergio0694 Jun 24, 2020
4ece5a5
Fixed a refactoring typo
Sergio0694 Jun 24, 2020
bee9454
Merge branch 'master' into feature/mvvm-apis
azchohfi Jun 24, 2020
6b3bbc0
Fixed merge issue
Sergio0694 Jun 24, 2020
e744928
Added tests for TaskExtensions class
Sergio0694 Jun 25, 2020
fcaa59e
Fixed a copy-paste error in filename
Sergio0694 Jun 25, 2020
95ac72f
Added tests for TaskResultConverter class
Sergio0694 Jun 25, 2020
c5669bb
Fixed build error due to [Obsolete] in a unit test
Sergio0694 Jun 25, 2020
8fb8a30
Renamed some APIs in RequestMessage<T>
Sergio0694 Jun 25, 2020
ce5132f
Merge branch 'master' into feature/mvvm-apis
Sergio0694 Jun 26, 2020
05a0b4e
Added AsyncRequestMessage<T> class
Sergio0694 Jun 26, 2020
572663f
Added CollectionRequestMessage<T> class
Sergio0694 Jun 26, 2020
6923f58
Added AsyncCollectionRequestMessage<T> class
Sergio0694 Jun 26, 2020
26e4053
Improved Messenger XML docs
Sergio0694 Jun 26, 2020
430ce04
Added unit tests for new request message types
Sergio0694 Jun 26, 2020
8a94bcb
Minor bug fixes and code refactoring
Sergio0694 Jun 28, 2020
e46a589
Added missing readonly modifier to Unit type
Sergio0694 Jun 28, 2020
53ea2f7
Fixed ambiguous match in XML comment
Sergio0694 Jun 28, 2020
1179ab1
Minor code style tweaks
Sergio0694 Jun 28, 2020
a70f278
Minor code refactoring
Sergio0694 Jun 28, 2020
1726279
Updated Task<T>.ResultOrDefault<T> on .NET Standard 2.1
Sergio0694 Jun 28, 2020
dc5398a
Merge branch 'master' into feature/mvvm-apis
Sergio0694 Jun 30, 2020
7b755a8
Minor tweaks to AsyncCollectionRequestMessage<T>
Sergio0694 Jul 1, 2020
4a1ec14
Added GetResponsesAsync API to AsyncCollectionRequestMessage<T>
Sergio0694 Jul 1, 2020
573bbad
Fixed a bug in the unit tests
Sergio0694 Jul 1, 2020
0fd19a3
Merge branch 'master' into feature/mvvm-apis
michael-hawker Jul 9, 2020
c6d65a2
Added ISubscriber<TMessage> interface, new extensions
Sergio0694 Jul 10, 2020
1cbe53c
Minor code refactoring
Sergio0694 Jul 10, 2020
7fa5994
Added new unit tests, minor bug fixes
Sergio0694 Jul 10, 2020
50b55fe
Merge branch 'feature/mvvm-apis' of https://github.com/Sergio0694/Win…
Sergio0694 Jul 10, 2020
34af434
Added ViewModelBase test for message unregistration
Sergio0694 Jul 10, 2020
53d1a9d
Added more comments to SetAndNotifyOnCompletion
Sergio0694 Jul 10, 2020
866dc8c
Improved remarks for ViewModeBase.OnDeactivated
Sergio0694 Jul 10, 2020
7da27bc
Improved tests for ISubscriber<T> APIs
Sergio0694 Jul 10, 2020
716d31c
Merge branch 'master' into feature/mvvm-apis
Sergio0694 Jul 10, 2020
f9165df
Initial code refactoring to SetAndNotifyOnCompletion
Sergio0694 Jul 10, 2020
904cd55
Added IsRunning property to async commands
Sergio0694 Jul 10, 2020
88f85ab
Added SetAndNotifyOnCompletion overload with callback
Sergio0694 Jul 10, 2020
b33b2fa
More code refactoring to SetAndNotifyOnCompletion
Sergio0694 Jul 10, 2020
f634006
Fixed an XML doc
Sergio0694 Jul 10, 2020
d98c257
Fixed initial IsRunning property update
Sergio0694 Jul 10, 2020
b91b218
Added unit tests for AsyncRelayCommand, bug fixes
Sergio0694 Jul 10, 2020
62c6829
Merge pull request #25 from Sergio0694/feature/extended-commands
Sergio0694 Jul 10, 2020
5725d45
Improved remarks for SetAndNotifyOnCompletion
Sergio0694 Jul 10, 2020
2a9d3e5
Improved Test_ObservableObject_Events
Sergio0694 Jul 10, 2020
0938168
Tweaked visibility of some test members
Sergio0694 Jul 10, 2020
eab9d9a
Added more tests for faulty monitored tasks
Sergio0694 Jul 10, 2020
0d423e8
Added more Ioc tests
Sergio0694 Jul 11, 2020
e4137b4
Refactored Task result extensions/converter
Sergio0694 Jul 11, 2020
8a5b294
Added sealed modifier to DictionarySlim<TKey, TValue>
Sergio0694 Jul 13, 2020
eb9d70a
Removed sealed modifier, this was a bad idea
Sergio0694 Jul 13, 2020
3032cb1
Renamed some APIs for consistency
Sergio0694 Jul 15, 2020
58805a7
Added more comments to DictionarySlim<,> type
Sergio0694 Jul 15, 2020
e8016f4
Fixed missing file rename for 3032cb1
Sergio0694 Jul 15, 2020
49d2ea1
Renamed ViewModelBase to ObservableRecipient
Sergio0694 Jul 15, 2020
8233d7c
Reduced thread contention in Messenger.Unregister<TToken>
Sergio0694 Jul 16, 2020
a671902
Fixed a bug in Messenger.Reset
Sergio0694 Jul 17, 2020
1123577
Minor code refactoring
Sergio0694 Jul 17, 2020
44451f2
Renamed some IMessenger APIs/extensions for clarity
Sergio0694 Jul 17, 2020
4094153
Fixed a bug in MessengerExtensions.RegisterAll
Sergio0694 Jul 18, 2020
a1ee70e
Minor code style tweaks
Sergio0694 Jul 18, 2020
053814e
Added ObservableObject.Set IEqualityComparer<T> overloads
Sergio0694 Jul 18, 2020
2b4e666
Major performance/memory boost in RegisterAll
Sergio0694 Jul 18, 2020
21e971f
Another perf boost (with compiled LINQ expressions)
Sergio0694 Jul 18, 2020
96a2a3f
Merge branch 'master' into feature/mvvm-apis
Sergio0694 Jul 21, 2020
ea0b85a
Renamed Set -> SetProperty for consistency with other libs
Sergio0694 Jul 22, 2020
4a1a61f
Merge branch 'master' into feature/mvvm-apis
Sergio0694 Jul 23, 2020
1542c0e
Removed unnecessary overhead in request messages
Sergio0694 Jul 23, 2020
afed416
Merge branch 'master' into feature/mvvm-apis
michael-hawker Jul 27, 2020
1c15e36
Merge remote-tracking branch 'upstream/master' into feature/mvvm-apis
Sergio0694 Jul 27, 2020
4238e6e
Code tweaks to support #3293
Sergio0694 Jul 27, 2020
cd7a981
Switched property name args to string?
Sergio0694 Jul 30, 2020
6c8ddde
Renamed IServiceCollection arg to services
Sergio0694 Jul 30, 2020
445d7a5
Changed sender type to object in notification message
Sergio0694 Jul 30, 2020
ffefa55
Renamed Task.ResultOrDefault extensions
Sergio0694 Jul 30, 2020
ffcc807
Updated package description for ViewModelBase
Sergio0694 Jul 30, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
421 changes: 421 additions & 0 deletions Microsoft.Toolkit.Mvvm/ComponentModel/ObservableObject.cs

Large diffs are not rendered by default.

234 changes: 234 additions & 0 deletions Microsoft.Toolkit.Mvvm/ComponentModel/ObservableRecipient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#pragma warning disable SA1512

// This file is inspired from the MvvmLight libray (lbugnion/mvvmlight),
// more info in ThirdPartyNotices.txt in the root of the project.

using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Microsoft.Toolkit.Mvvm.Messaging;
using Microsoft.Toolkit.Mvvm.Messaging.Messages;

namespace Microsoft.Toolkit.Mvvm.ComponentModel
{
/// <summary>
/// A base class for observable objects that also acts as recipients for messages. This class is an extension of
/// <see cref="ObservableObject"/> which also provides built-in support to use the <see cref="IMessenger"/> type.
/// </summary>
public abstract class ObservableRecipient : ObservableObject
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning behind this change?
"ViewModelBase" is a widely used name and its purpose is implied from its name.
The name "ObservableRecipient" makes me think it is something that will receive "observables".
This new name seems to be very much focused on the ability to receive messages. Will there be an equivalent object that can be used as a general-purpose base class for anyone not using messages? Or should they just use ObservableObject directly? If this is intended to be an alternative to ObservableObject that also broadcasts messages when properties are changed, why not a name that reflects this?
This class doesn't receive anything, but rather it sends/broadcasts messages. Were names like "BroadcastingObservableObject" also considered? Why was "ObservableRecipient" deemed the most appropriate name to use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Matt, that's a good question, and something I plan to properly document in the docs too! 😄

I made these changes after going through all the feedbacks from @deanchalk and discussing this with @michael-hawker. Basically, the idea is that the ViewModelBase name was itself confusing (and rightly so) for many users: the name implied it was just a "general purpose" base for viewmodels, when in fact that wasn't really the case. All the basic viewmodel functionality was already provided just by ObservableObject, and ViewModelBase was solely focused on doing proper integration with the IMessenger type. As a result, users might've felt "forced" to use the messenger in all cases (which wasn't the intent here), or could've just looked at that Messenger property thinking "I don't need this, why is it here?".

As a result, I've now renamed that ObservableRecipient, which also is more consistent with the general naming scheme: "Observable" like the other base class, and "recipient" which is the term used in all the messenger APIs. This class is now explicitly a more specialized version of ObservableObject that is meant to be used to support IMessenger, providing both new overloads for the Set method that integrate with it, as well as adding automatic setup for messages, if needed. And also, with a couple of constructors to use with DI, either automatically grabbing the shared IMessenger, or letting users inject their own from a DI service provider, so offer maximum flexibility.

Will there be an equivalent object that can be used as a general-purpose base class for anyone not using messages? Or should they just use ObservableObject directly?

Yes, people not interested in using messages should just inherit from ObservableObject, that class already provides all the basic functionalities to support the INotifyProperty[Changed|Changing] interface(s).

This class doesn't receive anything, but rather it sends/broadcasts messages. Were names like "BroadcastingObservableObject" also considered? Why was "ObservableRecipient" deemed the most appropriate name to use?

This is not correct, this class does act as a recipient. In fact, in many cases (eg. in my experience) it often acts more as a recipient than a broadcaster (hence the name too). You can see that the base implementation of OnActivated (as suggested by Michael) automatically registers all the declared message handlers (through the IRecipient<TMessage> interface), and users also have the ability to fine tune the subscription more by overriding that method. The OnDeactivated method also automatically unregisters all the message handlers, and these two are invoked by the setter for IsActive. In general, this class offers ready to use support for receiving messages, first and foremost. The ability to also broadcast messages is just a bonus, also because sending messages requires less setup than receiving one.

Hope this helps!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The terminology and lack of documentation certainly have the potential to be very confusing. As is, this has the potential to receive messages but doesn't (as far as I can tell) receive anything itself. (yet?)

{
/// <summary>
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
/// </summary>
/// <remarks>
/// This constructor will produce an instance that will use the <see cref="Messaging.Messenger.Default"/> instance
/// to perform requested operations. It will also be available locally through the <see cref="Messenger"/> property.
/// </remarks>
protected ObservableRecipient()
: this(Messaging.Messenger.Default)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ObservableRecipient"/> class.
/// </summary>
/// <param name="messenger">The <see cref="IMessenger"/> instance to use to send messages.</param>
protected ObservableRecipient(IMessenger messenger)
{
Messenger = messenger;
}

/// <summary>
/// Gets the <see cref="IMessenger"/> instance in use.
/// </summary>
protected IMessenger Messenger { get; }

private bool isActive;

/// <summary>
/// Gets or sets a value indicating whether the current view model is currently active.
/// </summary>
public bool IsActive
{
get => this.isActive;
set
{
if (SetProperty(ref this.isActive, value, true))
{
if (value)
{
OnActivated();
}
else
{
OnDeactivated();
}
}
}
}

/// <summary>
/// Raised whenever the <see cref="IsActive"/> property is set to <see langword="true"/>.
/// Use this method to register to messages and do other initialization for this instance.
/// </summary>
/// <remarks>
/// The base implementation registers all messages for this recipients that have been declared
/// explicitly through the <see cref="IRecipient{TMessage}"/> interface, using the default channel.
/// For more details on how this works, see the <see cref="MessengerExtensions.RegisterAll"/> method.
/// If you need more fine tuned control, want to register messages individually or just prefer
/// the lambda-style syntax for message registration, override this method and register manually.
/// </remarks>
protected virtual void OnActivated()
{
Messenger.RegisterAll(this);
}

/// <summary>
/// Raised whenever the <see cref="IsActive"/> property is set to <see langword="false"/>.
/// Use this method to unregister from messages and do general cleanup for this instance.
/// </summary>
/// <remarks>
/// The base implementation unregisters all messages for this recipient. It does so by
/// invoking <see cref="IMessenger.UnregisterAll"/>, which removes all registered
/// handlers for a given subscriber, regardless of what token was used to register them.
/// That is, all registered handlers across all subscription channels will be removed.
/// </remarks>
protected virtual void OnDeactivated()
{
Messenger.UnregisterAll(this);
}

/// <summary>
/// Broadcasts a <see cref="PropertyChangedMessage{T}"/> with the specified
/// parameters, without using any particular token (so using the default channel).
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The value of the property before it changed.</param>
/// <param name="newValue">The value of the property after it changed.</param>
/// <param name="propertyName">The name of the property that changed.</param>
/// <remarks>
/// You should override this method if you wish to customize the channel being
/// used to send the message (eg. if you need to use a specific token for the channel).
/// </remarks>
protected virtual void Broadcast<T>(T oldValue, T newValue, string propertyName)
{
var message = new PropertyChangedMessage<T>(this, propertyName, oldValue, newValue);

Messenger.Send(message);
}

/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// This method is just like <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/>, just with the addition
/// of the <paramref name="broadcast"/> parameter. As such, following the behavior of the base method,
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(ref T field, T newValue, bool broadcast, [CallerMemberName] string propertyName = null!)
{
return SetProperty(ref field, newValue, EqualityComparer<T>.Default, broadcast, propertyName);
}

/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// See additional notes about this overload in <see cref="SetProperty{T}(ref T,T,bool,string)"/>.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="field">The field storing the property's value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(ref T field, T newValue, IEqualityComparer<T> comparer, bool broadcast, [CallerMemberName] string propertyName = null!)
{
if (!broadcast)
{
return SetProperty(ref field, newValue, comparer, propertyName);
}

T oldValue = field;

if (SetProperty(ref field, newValue, comparer, propertyName))
{
Broadcast(oldValue, newValue, propertyName);

return true;
}

return false;
}

/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event. Similarly to
/// the <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/> method, this overload should only be
/// used when <see cref="ObservableObject.SetProperty{T}(ref T,T,string)"/> can't be used directly.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
/// <remarks>
/// This method is just like <see cref="ObservableObject.SetProperty{T}(T,T,Action{T},string)"/>, just with the addition
/// of the <paramref name="broadcast"/> parameter. As such, following the behavior of the base method,
/// the <see cref="ObservableObject.PropertyChanging"/> and <see cref="ObservableObject.PropertyChanged"/> events
/// are not raised if the current and new value for the target property are the same.
/// </remarks>
protected bool SetProperty<T>(T oldValue, T newValue, Action<T> callback, bool broadcast, [CallerMemberName] string propertyName = null!)
{
return SetProperty(oldValue, newValue, EqualityComparer<T>.Default, callback, broadcast, propertyName);
}

/// <summary>
/// Compares the current and new values for a given property. If the value has changed,
/// raises the <see cref="ObservableObject.PropertyChanging"/> event, updates the property with
/// the new value, then raises the <see cref="ObservableObject.PropertyChanged"/> event.
/// See additional notes about this overload in <see cref="SetProperty{T}(T,T,Action{T},bool,string)"/>.
/// </summary>
/// <typeparam name="T">The type of the property that changed.</typeparam>
/// <param name="oldValue">The current property value.</param>
/// <param name="newValue">The property's value after the change occurred.</param>
/// <param name="comparer">The <see cref="IEqualityComparer{T}"/> instance to use to compare the input values.</param>
/// <param name="callback">A callback to invoke to update the property value.</param>
/// <param name="broadcast">If <see langword="true"/>, <see cref="Broadcast{T}"/> will also be invoked.</param>
/// <param name="propertyName">(optional) The name of the property that changed.</param>
/// <returns><see langword="true"/> if the property was changed, <see langword="false"/> otherwise.</returns>
protected bool SetProperty<T>(T oldValue, T newValue, IEqualityComparer<T> comparer, Action<T> callback, bool broadcast, [CallerMemberName] string propertyName = null!)
{
if (!broadcast)
{
return SetProperty(oldValue, newValue, comparer, callback, propertyName);
}

if (SetProperty(oldValue, newValue, comparer, callback, propertyName))
{
Broadcast(oldValue, newValue, propertyName);

return true;
}

return false;
}
}
}
145 changes: 145 additions & 0 deletions Microsoft.Toolkit.Mvvm/DependencyInjection/Ioc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Threading;
using Microsoft.Extensions.DependencyInjection;

#nullable enable

namespace Microsoft.Toolkit.Mvvm.DependencyInjection
{
/// <summary>
/// A type that facilitates the use of the <see cref="IServiceProvider"/> type.
/// The <see cref="Ioc"/> provides the ability to configure services in a singleton, thread-safe
/// service provider instance, which can then be used to resolve service instances.
/// The first step to use this feature is to declare some services, for instance:
/// <code>
/// public interface ILogger
/// {
/// void Log(string text);
/// }
/// </code>
/// <code>
/// public class ConsoleLogger : ILogger
/// {
/// void Log(string text) => Console.WriteLine(text);
/// }
/// </code>
/// Then the services configuration should then be done at startup, by calling one of
/// the available <see cref="ConfigureServices(IServiceCollection)"/> overloads, like so:
/// <code>
/// Ioc.Default.ConfigureServices(collection =>
/// {
/// collection.AddSingleton&lt;ILogger, Logger&gt;();
/// });
/// </code>
/// Finally, you can use the <see cref="Ioc"/> instance (which implements <see cref="IServiceProvider"/>)
/// to retrieve the service instances from anywhere in your application, by doing as follows:
/// <code>
/// Ioc.Default.GetService&lt;ILogger&gt;().Log("Hello world!");
/// </code>
/// </summary>
public sealed class Ioc : IServiceProvider
{
/// <summary>
/// Gets the default <see cref="Ioc"/> instance.
/// </summary>
public static Ioc Default { get; } = new Ioc();

/// <summary>
/// The <see cref="ServiceProvider"/> instance to use, if initialized.
/// </summary>
private ServiceProvider? serviceProvider;

/// <inheritdoc/>
object? IServiceProvider.GetService(Type serviceType)
{
// As per section I.12.6.6 of the official CLI ECMA-335 spec:
// "[...] read and write access to properly aligned memory locations no larger than the native
// word size is atomic when all the write accesses to a location are the same size. Atomic writes
// shall alter no bits other than those written. Unless explicit layout control is used [...],
// data elements no larger than the natural word size [...] shall be properly aligned.
// Object references shall be treated as though they are stored in the native word size."
// The field being accessed here is of native int size (reference type), and is only ever accessed
// directly and atomically by a compare exhange instruction (see below), or here. We can therefore
// assume this read is thread safe with respect to accesses to this property or to invocations to one
// of the available configuration methods. So we can just read the field directly and make the necessary
// check with our local copy, without the need of paying the locking overhead from this get accessor.
ServiceProvider? provider = this.serviceProvider;

if (provider is null)
{
ThrowInvalidOperationExceptionForMissingInitialization();
}

return provider!.GetService(serviceType);
}

/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="setup">The configuration delegate to use to add services.</param>
public void ConfigureServices(Action<IServiceCollection> setup)
{
ConfigureServices(setup, new ServiceProviderOptions());
}

/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="setup">The configuration delegate to use to add services.</param>
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param>
public void ConfigureServices(Action<IServiceCollection> setup, ServiceProviderOptions options)
{
var collection = new ServiceCollection();

setup(collection);

ConfigureServices(collection, options);
}

/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="collection">The input <see cref="IServiceCollection"/> instance to use.</param>
public void ConfigureServices(IServiceCollection collection)
{
ConfigureServices(collection, new ServiceProviderOptions());
}

/// <summary>
/// Initializes the shared <see cref="IServiceProvider"/> instance.
/// </summary>
/// <param name="collection">The input <see cref="IServiceCollection"/> instance to use.</param>
/// <param name="options">The <see cref="ServiceProviderOptions"/> instance to configure the service provider behaviors.</param>
public void ConfigureServices(IServiceCollection collection, ServiceProviderOptions options)
{
ServiceProvider newServices = collection.BuildServiceProvider(options);

ServiceProvider? oldServices = Interlocked.CompareExchange(ref this.serviceProvider, newServices, null);

if (!(oldServices is null))
{
ThrowInvalidOperationExceptionForRepeatedConfiguration();
}
}

/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when the <see cref="ServiceProvider"/> property is used before initialization.
/// </summary>
private static void ThrowInvalidOperationExceptionForMissingInitialization()
{
throw new InvalidOperationException("The service provider has not been configured yet");
}

/// <summary>
/// Throws an <see cref="InvalidOperationException"/> when a configuration is attempted more than once.
/// </summary>
private static void ThrowInvalidOperationExceptionForRepeatedConfiguration()
{
throw new InvalidOperationException("The default service provider has already been configured");
}
}
}
Loading