diff --git a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj
index 7bbf962..e9a1003 100644
--- a/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj
+++ b/CommunityToolkit.Authentication/CommunityToolkit.Authentication.csproj
@@ -7,12 +7,10 @@
This package includes .NET Standard authentication helpers such as:
- BaseProvider:
- IProvider:
- - MockPRovider:
+ - MockProvider:
- ProviderManager:
- - ProviderManagerChangedState:
- ProviderState:
- ProviderStateChangedEventArgs:
- - ProviderUpdatedEventArgs:
Community Toolkit Provider Authentication Auth
diff --git a/CommunityToolkit.Authentication/ProviderManager.cs b/CommunityToolkit.Authentication/ProviderManager.cs
index 5bae3ea..b4f82ef 100644
--- a/CommunityToolkit.Authentication/ProviderManager.cs
+++ b/CommunityToolkit.Authentication/ProviderManager.cs
@@ -15,7 +15,7 @@ namespace CommunityToolkit.Authentication
/// ProviderManager.Instance.GlobalProvider = await MsalProvider.CreateAsync(...);
///
///
- public partial class ProviderManager : INotifyPropertyChanged
+ public partial class ProviderManager
{
///
/// Gets the name of the toolkit client to identify self in Graph calls.
@@ -28,12 +28,14 @@ public partial class ProviderManager : INotifyPropertyChanged
public static ProviderManager Instance { get; } = new ProviderManager();
///
- /// Event called when the changes.
+ /// Event called when the instance changes.
///
- public event EventHandler ProviderUpdated;
+ public event EventHandler ProviderUpdated;
- ///
- public event PropertyChangedEventHandler PropertyChanged;
+ ///
+ /// Event called when the state changes.
+ ///
+ public event EventHandler ProviderStateChanged;
private IProvider _provider;
@@ -49,6 +51,7 @@ public IProvider GlobalProvider
set
{
+ var oldState = _provider?.State;
if (_provider != null)
{
_provider.StateChanged -= ProviderStateChanged;
@@ -56,14 +59,14 @@ public IProvider GlobalProvider
_provider = value;
+ var newState = _provider?.State;
if (_provider != null)
{
_provider.StateChanged += ProviderStateChanged;
}
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GlobalProvider)));
- ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderChanged));
- ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderStateChanged));
+ ProviderUpdated?.Invoke(this, _provider);
+ ProviderStateChanged?.Invoke(this, new ProviderStateChangedEventArgs(oldState, newState));
}
}
@@ -71,10 +74,5 @@ private ProviderManager()
{
// Use Instance
}
-
- private void ProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
- {
- ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderStateChanged));
- }
}
}
diff --git a/CommunityToolkit.Authentication/ProviderManagerChangedState.cs b/CommunityToolkit.Authentication/ProviderManagerChangedState.cs
deleted file mode 100644
index 038df46..0000000
--- a/CommunityToolkit.Authentication/ProviderManagerChangedState.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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.
-
-namespace CommunityToolkit.Authentication
-{
- ///
- /// Enum representing reasons for provider state changing.
- ///
- public enum ProviderManagerChangedState
- {
- ///
- /// The itself changed.
- ///
- ProviderChanged,
-
- ///
- /// The changed.
- ///
- ProviderStateChanged,
- }
-}
\ No newline at end of file
diff --git a/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs b/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs
index f897305..0603613 100644
--- a/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs
+++ b/CommunityToolkit.Authentication/ProviderStateChangedEventArgs.cs
@@ -16,7 +16,7 @@ public class ProviderStateChangedEventArgs : EventArgs
///
/// Previous .
/// Current .
- public ProviderStateChangedEventArgs(ProviderState oldState, ProviderState newState)
+ public ProviderStateChangedEventArgs(ProviderState? oldState, ProviderState? newState)
{
OldState = oldState;
NewState = newState;
@@ -25,11 +25,11 @@ public ProviderStateChangedEventArgs(ProviderState oldState, ProviderState newSt
///
/// Gets the previous state of the .
///
- public ProviderState OldState { get; private set; }
+ public ProviderState? OldState { get; private set; }
///
/// Gets the new state of the .
///
- public ProviderState NewState { get; private set; }
+ public ProviderState? NewState { get; private set; }
}
}
\ No newline at end of file
diff --git a/CommunityToolkit.Authentication/ProviderUpdatedEventArgs.cs b/CommunityToolkit.Authentication/ProviderUpdatedEventArgs.cs
deleted file mode 100644
index 9e8375a..0000000
--- a/CommunityToolkit.Authentication/ProviderUpdatedEventArgs.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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;
-
-namespace CommunityToolkit.Authentication
-{
- ///
- /// class for event.
- ///
- public class ProviderUpdatedEventArgs : EventArgs
- {
- ///
- /// Initializes a new instance of the class.
- ///
- /// value for reason for update.
- public ProviderUpdatedEventArgs(ProviderManagerChangedState reason)
- {
- Reason = reason;
- }
-
- ///
- /// Gets the reason for the provider update.
- ///
- public ProviderManagerChangedState Reason { get; private set; }
- }
-}
\ No newline at end of file
diff --git a/CommunityToolkit.Graph.Uwp/Controls/LoginButton/LoginButton.cs b/CommunityToolkit.Graph.Uwp/Controls/LoginButton/LoginButton.cs
new file mode 100644
index 0000000..05b01c8
--- /dev/null
+++ b/CommunityToolkit.Graph.Uwp/Controls/LoginButton/LoginButton.cs
@@ -0,0 +1,229 @@
+// 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.ComponentModel;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using CommunityToolkit.Authentication;
+using CommunityToolkit.Graph.Extensions;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+
+namespace CommunityToolkit.Graph.Uwp.Controls
+{
+ ///
+ /// The control is a button which can be used to sign the user in or show them profile details.
+ ///
+ [TemplatePart(Name = LoginButtonPart, Type = typeof(Button))]
+ [TemplatePart(Name = SignOutButtonPart, Type = typeof(ButtonBase))]
+ public partial class LoginButton : Control
+ {
+ private const string LoginButtonPart = "PART_LoginButton";
+ private const string SignOutButtonPart = "PART_SignOutButton";
+
+ private Button _loginButton;
+ private ButtonBase _signOutButton;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public LoginButton()
+ {
+ this.DefaultStyleKey = typeof(LoginButton);
+
+ ProviderManager.Instance.ProviderStateChanged += (sender, args) => LoadData();
+ }
+
+ ///
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ IsLoading = true;
+ LoadData();
+
+ if (_loginButton != null)
+ {
+ _loginButton.Click -= LoginButton_Click;
+ }
+
+ _loginButton = GetTemplateChild(LoginButtonPart) as Button;
+
+ if (_loginButton != null)
+ {
+ _loginButton.Click += LoginButton_Click;
+ }
+
+ if (_signOutButton != null)
+ {
+ _signOutButton.Click -= LoginButton_Click;
+ }
+
+ _signOutButton = GetTemplateChild(SignOutButtonPart) as ButtonBase;
+
+ if (_signOutButton != null)
+ {
+ _signOutButton.Click += SignOutButton_Click;
+ }
+ }
+
+ private async void LoginButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (this.UserDetails != null)
+ {
+ if (FlyoutBase.GetAttachedFlyout(_loginButton) is FlyoutBase flyout)
+ {
+ flyout.ShowAt(_loginButton);
+ }
+ }
+ else
+ {
+ var cargs = new CancelEventArgs();
+ LoginInitiated?.Invoke(this, cargs);
+
+ if (!cargs.Cancel)
+ {
+ await SignInAsync();
+ }
+ }
+ }
+
+ private async void SignOutButton_Click(object sender, RoutedEventArgs e)
+ {
+ await SignOutAsync();
+ }
+
+ private async void LoadData()
+ {
+ var provider = ProviderManager.Instance.GlobalProvider;
+
+ if (provider == null)
+ {
+ return;
+ }
+
+ if (provider.State == ProviderState.Loading)
+ {
+ IsLoading = true;
+ }
+ else if (provider.State == ProviderState.SignedIn)
+ {
+ try
+ {
+ // https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/master/src/components/mgt-login/mgt-login.ts#L139
+ // TODO: Batch with photo request later? https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/issues/29
+ UserDetails = await provider.GetClient().GetMeAsync();
+ }
+ catch (Exception e)
+ {
+ LoginFailed?.Invoke(this, new LoginFailedEventArgs(e));
+ }
+
+ IsLoading = false;
+ }
+ else if (provider.State == ProviderState.SignedOut)
+ {
+ UserDetails = null; // What if this was user provided? Should we not hook into these events then?
+
+ IsLoading = false;
+ }
+ else
+ {
+ // Provider in Loading state
+ Debug.Fail("unsupported state");
+ }
+ }
+
+ ///
+ /// Initiates logging in with the current registered in the .
+ ///
+ /// A representing the asynchronous operation.
+ public async Task SignInAsync()
+ {
+ if (UserDetails != null || IsLoading)
+ {
+ return;
+ }
+
+ var provider = ProviderManager.Instance.GlobalProvider;
+
+ if (provider != null)
+ {
+ try
+ {
+ IsLoading = true;
+ await provider.SignInAsync();
+
+ if (provider.State == ProviderState.SignedIn)
+ {
+ // TODO: include user details?
+ LoginCompleted?.Invoke(this, new EventArgs());
+
+ LoadData();
+ }
+ else
+ {
+ LoginFailed?.Invoke(this, new LoginFailedEventArgs(new TimeoutException("Login did not complete.")));
+ }
+ }
+ catch (Exception e)
+ {
+ LoginFailed?.Invoke(this, new LoginFailedEventArgs(e));
+ }
+ finally
+ {
+ IsLoading = false;
+ }
+ }
+ }
+
+ ///
+ /// Log a signed-in user out.
+ ///
+ /// A representing the asynchronous operation.
+ public async Task SignOutAsync()
+ {
+ // Close Menu
+ if (FlyoutBase.GetAttachedFlyout(_loginButton) is FlyoutBase flyout)
+ {
+ flyout.Hide();
+ }
+
+ if (IsLoading)
+ {
+ return;
+ }
+
+ var cargs = new CancelEventArgs();
+ LogoutInitiated?.Invoke(this, cargs);
+
+ if (cargs.Cancel)
+ {
+ return;
+ }
+
+ if (UserDetails != null)
+ {
+ UserDetails = null;
+ }
+ else
+ {
+ return; // No-op
+ }
+
+ var provider = ProviderManager.Instance.GlobalProvider;
+
+ if (provider != null)
+ {
+ IsLoading = true;
+ await provider.SignOutAsync();
+ IsLoading = false;
+
+ LogoutCompleted?.Invoke(this, new EventArgs());
+ }
+ }
+ }
+}
diff --git a/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs b/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs
new file mode 100644
index 0000000..24ec786
--- /dev/null
+++ b/CommunityToolkit.Graph.Uwp/Controls/PersonView/PersonView.cs
@@ -0,0 +1,228 @@
+// 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.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using CommunityToolkit.Authentication;
+using CommunityToolkit.Graph.Extensions;
+using Microsoft.Graph;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media.Imaging;
+
+namespace CommunityToolkit.Graph.Uwp.Controls
+{
+ ///
+ /// The control displays a user photo and can display their name and e-mail.
+ ///
+ public partial class PersonView : Control
+ {
+ private const string PersonViewDefaultImageSourceResourceName = "PersonViewDefaultImageSource";
+
+ ///
+ /// value used to retrieve the signed-in user's info.
+ ///
+ public const string PersonQueryMe = "me";
+
+ private string _photoId = null;
+
+ private string _defaultImageSource = "ms-appx:///Microsoft.Toolkit.Graph.Controls/Assets/person.png";
+
+ private BitmapImage _defaultImage;
+
+ private static async void PersonDetailsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is PersonView pv)
+ {
+ if (pv.PersonDetails != null)
+ {
+ if (pv?.PersonDetails?.GivenName?.Length > 0 && pv?.PersonDetails?.Surname?.Length > 0)
+ {
+ pv.Initials = string.Empty + pv.PersonDetails.GivenName[0] + pv.PersonDetails.Surname[0];
+ }
+ else if (pv?.PersonDetails?.DisplayName?.Length > 0)
+ {
+ // Grab first two initials in name
+ var initials = pv.PersonDetails.DisplayName.ToUpper().Split(' ').Select(i => i.First());
+ pv.Initials = string.Join(string.Empty, initials.Where(i => char.IsLetter(i)).Take(2));
+ }
+
+ if (pv?.UserPhoto?.UriSource?.AbsoluteUri == pv._defaultImageSource || pv?.PersonDetails?.Id != pv._photoId)
+ {
+ // Reload Image
+ pv.UserPhoto = pv._defaultImage;
+ await pv.LoadImageAsync(pv.PersonDetails);
+ }
+ else if (pv?.PersonDetails?.Id != pv._photoId)
+ {
+ pv.UserPhoto = pv._defaultImage;
+ pv._photoId = null;
+ }
+ }
+ }
+ }
+
+ private static void QueryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is PersonView view)
+ {
+ view.PersonDetails = null;
+ view.LoadData();
+ }
+ }
+
+ private static void PersonViewTypePropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is PersonView pv)
+ {
+ pv.IsLargeImage = pv.PersonViewType == PersonViewType.Avatar;
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PersonView()
+ {
+ this.DefaultStyleKey = typeof(PersonView);
+
+ _defaultImage = new BitmapImage(new Uri(_defaultImageSource));
+
+ ProviderManager.Instance.ProviderStateChanged += (sender, args) => LoadData();
+ }
+
+ ///
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ if (Resources.TryGetValue(PersonViewDefaultImageSourceResourceName, out object value) && value is string uri)
+ {
+ _defaultImageSource = uri;
+ _defaultImage = new BitmapImage(new Uri(_defaultImageSource)); // TODO: Couldn't load image from app package, only remote or in our assembly?
+ UserPhoto = _defaultImage;
+ }
+
+ LoadData();
+ }
+
+ private async void LoadData()
+ {
+ var provider = ProviderManager.Instance.GlobalProvider;
+
+ if (provider == null || provider.State != ProviderState.SignedIn)
+ {
+ // Set back to Default if not signed-in
+ if (provider != null)
+ {
+ UserPhoto = _defaultImage;
+ }
+
+ return;
+ }
+
+ if (PersonDetails != null && UserPhoto == null)
+ {
+ await LoadImageAsync(PersonDetails);
+ }
+ else if (!string.IsNullOrWhiteSpace(UserId) || PersonQuery?.ToLowerInvariant() == PersonQueryMe)
+ {
+ User user = null;
+ if (!string.IsNullOrWhiteSpace(UserId))
+ {
+ // TODO: Batch when API easier https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/issues/29
+ try
+ {
+ user = await provider.GetClient().GetUserAsync(UserId);
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ // TODO: Move to LoadImage based on previous call?
+ await DecodeStreamAsync(await provider.GetBetaClient().GetUserPhoto(UserId));
+ _photoId = UserId;
+ }
+ catch
+ {
+ }
+ }
+ else
+ {
+ try
+ {
+ user = await provider.GetClient().GetMeAsync();
+ }
+ catch
+ {
+ }
+
+ try
+ {
+ await DecodeStreamAsync(await provider.GetBetaClient().GetMyPhotoAsync());
+ _photoId = user.Id;
+ }
+ catch
+ {
+ }
+ }
+
+ if (user != null)
+ {
+ PersonDetails = user.ToPerson();
+ }
+ }
+ else if (PersonDetails == null && !string.IsNullOrWhiteSpace(PersonQuery))
+ {
+ var people = await provider.GetClient().FindPersonAsync(PersonQuery);
+ if (people != null && people.Count > 0)
+ {
+ var person = people.FirstOrDefault();
+ PersonDetails = person;
+ await LoadImageAsync(person);
+ }
+ }
+ }
+
+ private async Task LoadImageAsync(Person person)
+ {
+ try
+ {
+ // TODO: Better guarding
+ var graph = ProviderManager.Instance.GlobalProvider.GetBetaClient();
+
+ if (!string.IsNullOrWhiteSpace(person.UserPrincipalName))
+ {
+ await DecodeStreamAsync(await graph.GetUserPhoto(person.UserPrincipalName));
+ _photoId = person.Id; // TODO: Only set on success for photo?
+ }
+ else if (!string.IsNullOrWhiteSpace(person.ScoredEmailAddresses.First().Address))
+ {
+ // TODO https://github.com/microsoftgraph/microsoft-graph-toolkit/blob/master/src/components/mgt-person/mgt-person.ts#L174
+ }
+ }
+ catch
+ {
+ // If we can't load a photo, that's ok.
+ }
+ }
+
+ private async Task DecodeStreamAsync(Stream photoStream)
+ {
+ if (photoStream != null)
+ {
+ using (var ras = photoStream.AsRandomAccessStream())
+ {
+ var bitmap = new BitmapImage();
+ await bitmap.SetSourceAsync(ras);
+ UserPhoto = bitmap;
+ }
+ }
+ }
+ }
+}
diff --git a/CommunityToolkit.Graph.Uwp/Triggers/ProviderStateTrigger.cs b/CommunityToolkit.Graph.Uwp/Triggers/ProviderStateTrigger.cs
new file mode 100644
index 0000000..57c9368
--- /dev/null
+++ b/CommunityToolkit.Graph.Uwp/Triggers/ProviderStateTrigger.cs
@@ -0,0 +1,77 @@
+// 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 CommunityToolkit.Authentication;
+using Microsoft.Toolkit.Uwp;
+using Microsoft.Toolkit.Uwp.Helpers;
+using Windows.System;
+using Windows.UI.Xaml;
+
+namespace CommunityToolkit.Graph.Uwp
+{
+ ///
+ /// A StateTrigger for detecting when the global authentication provider has been signed in.
+ ///
+ public class ProviderStateTrigger : StateTriggerBase
+ {
+ ///
+ /// Identifies the DependencyProperty.
+ ///
+ public static readonly DependencyProperty StateProperty =
+ DependencyProperty.Register(nameof(State), typeof(ProviderState), typeof(ProviderStateTrigger), new PropertyMetadata(null, OnStateChanged));
+
+ private static void OnStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is ProviderStateTrigger instance)
+ {
+ instance.UpdateState();
+ }
+ }
+
+ private readonly DispatcherQueue _dispatcherQueue;
+
+ ///
+ /// Gets or sets the expected ProviderState.
+ ///
+ public ProviderState? State
+ {
+ get => (ProviderState?)GetValue(StateProperty);
+ set => SetValue(StateProperty, value);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProviderStateTrigger()
+ {
+ _dispatcherQueue = DispatcherQueue.GetForCurrentThread();
+ var weakEvent =
+ new WeakEventListener(this)
+ {
+ OnEventAction = (instance, source, args) => OnProviderStateChanged(source, args),
+ OnDetachAction = (weakEventListener) => ProviderManager.Instance.ProviderStateChanged -= weakEventListener.OnEvent,
+ };
+ ProviderManager.Instance.ProviderStateChanged += weakEvent.OnEvent;
+ UpdateState();
+ }
+
+ private void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
+ {
+ _ = _dispatcherQueue.EnqueueAsync(UpdateState, DispatcherQueuePriority.Normal);
+ }
+
+ private void UpdateState()
+ {
+ var provider = ProviderManager.Instance.GlobalProvider;
+ if (State != null && provider?.State != null)
+ {
+ SetActive(provider?.State == State);
+ }
+ else
+ {
+ SetActive(false);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs b/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs
index ee50139..e4ef1da 100644
--- a/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs
+++ b/CommunityToolkit.Graph/Extensions/ProviderExtensions.cs
@@ -12,60 +12,30 @@ namespace CommunityToolkit.Graph.Extensions
///
public static class ProviderExtensions
{
- private static GraphServiceClient _client;
- private static GraphServiceClient _betaClient;
-
- static ProviderExtensions()
- {
- ProviderManager.Instance.ProviderUpdated += OnProviderUpdated;
- }
-
- private static void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e)
- {
- var providerManager = sender as ProviderManager;
- if (e.Reason == ProviderManagerChangedState.ProviderChanged || !(providerManager.GlobalProvider?.State == ProviderState.SignedIn))
- {
- _client = null;
- _betaClient = null;
- }
- }
-
///
- /// Lazily gets a GraphServiceClient instance based on the current GlobalProvider.
- /// The client instance is cleared whenever the GlobalProvider changes.
+ /// Gets a GraphServiceClient instance based on the current GlobalProvider.
///
/// The provider for authenticating Graph calls.
/// A GraphServiceClient instance.
public static GraphServiceClient GetClient(this IProvider provider)
{
- if (_client == null && provider?.State == ProviderState.SignedIn)
+ return new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
{
- _client = new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) =>
- {
- await provider.AuthenticateRequestAsync(requestMessage);
- }));
- }
-
- return _client;
+ await provider.AuthenticateRequestAsync(requestMessage);
+ }));
}
///
- /// Lazily gets a GraphServiceClient instance based on the current GlobalProvider, but configured for the beta endpoint.
- /// The beta client instance is cleared whenever the GlobalProvider changes.
+ /// Gets a GraphServiceClient instance based on the current GlobalProvider, but configured for the beta endpoint.
///
/// The provider for authenticating Graph calls.
/// A GraphServiceClient instance configured for the beta endpoint.
public static GraphServiceClient GetBetaClient(this IProvider provider)
{
- if (_betaClient == null && provider?.State == ProviderState.SignedIn)
+ return new GraphServiceClient("https://graph.microsoft.com/beta", new DelegateAuthenticationProvider(async (requestMessage) =>
{
- _betaClient = new GraphServiceClient("https://graph.microsoft.com/beta", new DelegateAuthenticationProvider(async (requestMessage) =>
- {
- await provider.AuthenticateRequestAsync(requestMessage);
- }));
- }
-
- return _betaClient;
+ await provider.AuthenticateRequestAsync(requestMessage);
+ }));
}
}
}
diff --git a/README.md b/README.md
index f0f9634..b67824e 100644
--- a/README.md
+++ b/README.md
@@ -146,7 +146,7 @@ private async Task> GetDefaultTaskListAsync()
**That's all you need to get started!**
-You can use the `ProviderManager.Instance` to listen to changes in authentication status with the `ProviderUpdated` event or get direct access to the [.NET Graph Beta API](https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet) through `ProviderManager.Instance.GlobalProvider.GetBetaClient()`, just be sure to check if the `GlobalProvider` has been set first and its `State` is `SignedIn`:
+You can use the `ProviderManager.Instance` to listen to changes in authentication status with the `ProviderStateChanged` event or get direct access to the [.NET Graph Beta API](https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet) through `ProviderManager.Instance.GlobalProvider.GetBetaClient()`, just be sure to check if the `GlobalProvider` has been set first and its `State` is `SignedIn`:
```csharp
using CommunityToolkit.Authentication;
diff --git a/SampleTest/MainPage.xaml.cs b/SampleTest/MainPage.xaml.cs
new file mode 100644
index 0000000..4a4c577
--- /dev/null
+++ b/SampleTest/MainPage.xaml.cs
@@ -0,0 +1,95 @@
+// 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 CommunityToolkit.Authentication;
+using CommunityToolkit.Graph.Extensions;
+using Microsoft.Graph;
+using Microsoft.Graph.Extensions;
+using System;
+using System.Text.RegularExpressions;
+using Windows.UI.Xaml.Controls;
+
+namespace SampleTest
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MainPage : Page
+ {
+ // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2407
+ public DateTime Today => DateTimeOffset.Now.Date.ToUniversalTime();
+
+ // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2407
+ public DateTime ThreeDaysFromNow => Today.AddDays(3);
+
+ public IBaseRequestBuilder CalendarViewBuilder;
+ public IBaseRequestBuilder MessagesBuilder;
+ public IBaseRequestBuilder PlannerTasksBuilder;
+ public IBaseRequestBuilder TeamsChannelMessagesBuilder;
+
+ public MainPage()
+ {
+ this.InitializeComponent();
+
+ ProviderManager.Instance.ProviderUpdated += this.OnProviderUpdated;
+ ProviderManager.Instance.ProviderStateChanged += this.OnProviderStateChanged;
+ }
+
+ private void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
+ {
+ if (e.NewState == ProviderState.SignedIn)
+ {
+ var graphClient = ProviderManager.Instance.GlobalProvider.GetClient();
+
+ CalendarViewBuilder = graphClient.Me.CalendarView;
+ MessagesBuilder = graphClient.Me.Messages;
+ PlannerTasksBuilder = graphClient.Me.Planner.Tasks;
+ TeamsChannelMessagesBuilder = graphClient.Teams["02bd9fd6-8f93-4758-87c3-1fb73740a315"].Channels["19:d0bba23c2fc8413991125a43a54cc30e@thread.skype"].Messages;
+ }
+ else
+ {
+ ClearRequestBuilders();
+ }
+ }
+
+ private void OnProviderUpdated(object sender, IProvider provider)
+ {
+ if (provider == null)
+ {
+ ClearRequestBuilders();
+ }
+ }
+
+ private void ClearRequestBuilders()
+ {
+ CalendarViewBuilder = null;
+ MessagesBuilder = null;
+ PlannerTasksBuilder = null;
+ TeamsChannelMessagesBuilder = null;
+ }
+
+ public static string ToLocalTime(DateTimeTimeZone value)
+ {
+ // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2407
+ return value.ToDateTimeOffset().LocalDateTime.ToString("g");
+ }
+
+ public static string ToLocalTime(DateTimeOffset? value)
+ {
+ // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2654
+ return value?.LocalDateTime.ToString("g");
+ }
+
+ public static string RemoveWhitespace(string value)
+ {
+ // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/2654
+ return Regex.Replace(value, @"\t|\r|\n", " ");
+ }
+
+ public static bool IsTaskCompleted(int? percentCompleted)
+ {
+ return percentCompleted == 100;
+ }
+ }
+}
diff --git a/SampleTest/Samples/RoamingSettings/RoamingSettingsViewModel.cs b/SampleTest/Samples/RoamingSettings/RoamingSettingsViewModel.cs
new file mode 100644
index 0000000..011c6b1
--- /dev/null
+++ b/SampleTest/Samples/RoamingSettings/RoamingSettingsViewModel.cs
@@ -0,0 +1,195 @@
+// 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 CommunityToolkit.Authentication;
+using CommunityToolkit.Graph.Uwp.Helpers.RoamingSettings;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
+
+namespace SampleTest.Samples
+{
+ public class RoamingSettingsViewModel : INotifyPropertyChanged
+ {
+ private IProvider GlobalProvider => ProviderManager.Instance.GlobalProvider;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ private string _errorText;
+ public string ErrorText
+ {
+ get => _errorText;
+ set => Set(ref _errorText, value);
+ }
+
+ private RoamingSettingsHelper _roamingSettings;
+
+ private ObservableCollection> _additionalData;
+ public ObservableCollection> AdditionalData
+ {
+ get => _additionalData;
+ set => Set(ref _additionalData, value);
+ }
+
+ private string _keyInputText;
+ public string KeyInputText
+ {
+ get => _keyInputText;
+ set => Set(ref _keyInputText, value);
+ }
+
+ private string _valueInputText;
+ public string ValueInputText
+ {
+ get => _valueInputText;
+ set => Set(ref _valueInputText, value);
+ }
+
+ public RoamingSettingsViewModel()
+ {
+ _roamingSettings = null;
+ _keyInputText = string.Empty;
+ _valueInputText = string.Empty;
+
+ ProviderManager.Instance.ProviderStateChanged += (s, e) => CheckState();
+ CheckState();
+ }
+
+ public void GetValue()
+ {
+ try
+ {
+ ErrorText = string.Empty;
+ ValueInputText = string.Empty;
+
+ string key = KeyInputText;
+ string value = _roamingSettings.Read(key);
+
+ ValueInputText = value;
+ }
+ catch (Exception e)
+ {
+ ErrorText = e.Message;
+ }
+ }
+
+ public void SetValue()
+ {
+ try
+ {
+ ErrorText = string.Empty;
+
+ _roamingSettings.Save(KeyInputText, ValueInputText);
+
+ SyncRoamingSettings();
+ }
+ catch (Exception e)
+ {
+ ErrorText = e.Message;
+ }
+ }
+
+ public async void CreateCustomRoamingSettings()
+ {
+ try
+ {
+ ErrorText = string.Empty;
+
+ await _roamingSettings.Create();
+
+ AdditionalData = new ObservableCollection>(_roamingSettings.Cache);
+
+ KeyInputText = string.Empty;
+ ValueInputText = string.Empty;
+ }
+ catch (Exception e)
+ {
+ ErrorText = e.Message;
+ }
+ }
+
+ public async void DeleteCustomRoamingSettings()
+ {
+ try
+ {
+ ErrorText = string.Empty;
+
+ await _roamingSettings.Delete();
+
+ AdditionalData?.Clear();
+ KeyInputText = string.Empty;
+ ValueInputText = string.Empty;
+ }
+ catch (Exception e)
+ {
+ ErrorText = e.Message;
+ }
+ }
+
+ public async void SyncRoamingSettings()
+ {
+ try
+ {
+ ErrorText = string.Empty;
+ AdditionalData?.Clear();
+
+ await _roamingSettings.Sync();
+ if (_roamingSettings.Cache != null)
+ {
+ AdditionalData = new ObservableCollection>(_roamingSettings.Cache);
+ }
+ }
+ catch (Exception e)
+ {
+ ErrorText = e.Message;
+ }
+ }
+
+ private async void CheckState()
+ {
+ if (GlobalProvider != null && GlobalProvider.State == ProviderState.SignedIn)
+ {
+ await LoadState();
+ }
+ else
+ {
+ ClearState();
+ }
+ }
+
+ private async Task LoadState()
+ {
+ try
+ {
+ ClearState();
+
+ _roamingSettings = await RoamingSettingsHelper.CreateForCurrentUser();
+ }
+ catch (Exception e)
+ {
+ ErrorText = e.Message;
+ }
+ }
+
+ private void ClearState()
+ {
+ _roamingSettings = null;
+
+ KeyInputText = string.Empty;
+ ValueInputText = string.Empty;
+ }
+
+ private void Set(ref T field, T value, [CallerMemberName] string propertyName = null)
+ {
+ if (!EqualityComparer.Default.Equals(field, value))
+ {
+ field = value;
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+ }
+}
diff --git a/Samples/ManualGraphRequestSample/LoginButton.xaml.cs b/Samples/ManualGraphRequestSample/LoginButton.xaml.cs
index 26aba11..43e0853 100644
--- a/Samples/ManualGraphRequestSample/LoginButton.xaml.cs
+++ b/Samples/ManualGraphRequestSample/LoginButton.xaml.cs
@@ -17,7 +17,7 @@ public LoginButton()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState();
+ ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState();
UpdateState();
}
diff --git a/Samples/ManualGraphRequestSample/MainPage.xaml.cs b/Samples/ManualGraphRequestSample/MainPage.xaml.cs
index 98f7daa..cb6fc20 100644
--- a/Samples/ManualGraphRequestSample/MainPage.xaml.cs
+++ b/Samples/ManualGraphRequestSample/MainPage.xaml.cs
@@ -21,15 +21,13 @@ public MainPage()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += OnProviderUpdated;
+ ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged;
ProviderManager.Instance.GlobalProvider = new WindowsProvider(new string[] { "User.Read", "Tasks.ReadWrite" });
}
- private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e)
+ private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
{
- IProvider provider = ProviderManager.Instance.GlobalProvider;
-
- switch (provider?.State)
+ switch (e.NewState)
{
case ProviderState.SignedOut:
TaskCollection.Clear();
diff --git a/Samples/UwpMsalProviderSample/LoginButton.xaml.cs b/Samples/UwpMsalProviderSample/LoginButton.xaml.cs
index 4e0cbb6..24c3ecf 100644
--- a/Samples/UwpMsalProviderSample/LoginButton.xaml.cs
+++ b/Samples/UwpMsalProviderSample/LoginButton.xaml.cs
@@ -17,7 +17,7 @@ public LoginButton()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState();
+ ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState();
UpdateState();
}
diff --git a/Samples/UwpMsalProviderSample/MainPage.xaml.cs b/Samples/UwpMsalProviderSample/MainPage.xaml.cs
index c43e0fc..4e59f4c 100644
--- a/Samples/UwpMsalProviderSample/MainPage.xaml.cs
+++ b/Samples/UwpMsalProviderSample/MainPage.xaml.cs
@@ -14,25 +14,24 @@ public MainPage()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += OnProviderUpdated;
+ ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged;
}
- private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e)
+ private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
{
- var provider = ProviderManager.Instance.GlobalProvider;
- if (provider == null || provider.State != ProviderState.SignedIn)
- {
- SignedInUserTextBlock.Text = "Please sign in.";
- }
- else
+ if (e.NewState == ProviderState.SignedIn)
{
SignedInUserTextBlock.Text = "Signed in as...";
- var graphClient = provider.GetClient();
+ var graphClient = ProviderManager.Instance.GlobalProvider.GetClient();
var me = await graphClient.Me.Request().GetAsync();
SignedInUserTextBlock.Text = "Signed in as: " + me.DisplayName;
}
+ else
+ {
+ SignedInUserTextBlock.Text = "Please sign in.";
+ }
}
}
}
diff --git a/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs b/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs
index 2b0c74d..cd8008c 100644
--- a/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs
+++ b/Samples/UwpWindowsProviderSample/LoginButton.xaml.cs
@@ -17,7 +17,7 @@ public LoginButton()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState();
+ ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState();
UpdateState();
}
diff --git a/Samples/UwpWindowsProviderSample/MainPage.xaml.cs b/Samples/UwpWindowsProviderSample/MainPage.xaml.cs
index 3bb5de0..155e306 100644
--- a/Samples/UwpWindowsProviderSample/MainPage.xaml.cs
+++ b/Samples/UwpWindowsProviderSample/MainPage.xaml.cs
@@ -14,27 +14,26 @@ public MainPage()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += OnProviderUpdated;
+ ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged;
}
- private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e)
+ private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
{
- var provider = ProviderManager.Instance.GlobalProvider;
- if (provider == null || provider.State != ProviderState.SignedIn)
- {
- SignedInUserTextBlock.Text = "Please sign in.";
- ManagerButton.IsEnabled = false;
- }
- else
+ if (e.NewState == ProviderState.SignedIn)
{
ManagerButton.IsEnabled = true;
SignedInUserTextBlock.Text = "Signed in as...";
- var graphClient = provider.GetClient();
+ var graphClient = ProviderManager.Instance.GlobalProvider.GetClient();
var me = await graphClient.Me.Request().GetAsync();
SignedInUserTextBlock.Text = "Signed in as: " + me.DisplayName;
}
+ else
+ {
+ SignedInUserTextBlock.Text = "Please sign in.";
+ ManagerButton.IsEnabled = false;
+ }
}
}
}
diff --git a/Samples/WpfMsalProviderSample/LoginButton.xaml.cs b/Samples/WpfMsalProviderSample/LoginButton.xaml.cs
index 1ed9697..ee4836d 100644
--- a/Samples/WpfMsalProviderSample/LoginButton.xaml.cs
+++ b/Samples/WpfMsalProviderSample/LoginButton.xaml.cs
@@ -18,7 +18,7 @@ public LoginButton()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += (s, e) => UpdateState();
+ ProviderManager.Instance.ProviderStateChanged += (s, e) => UpdateState();
UpdateState();
}
diff --git a/Samples/WpfMsalProviderSample/MainWindow.xaml.cs b/Samples/WpfMsalProviderSample/MainWindow.xaml.cs
index 7db62ba..14635d6 100644
--- a/Samples/WpfMsalProviderSample/MainWindow.xaml.cs
+++ b/Samples/WpfMsalProviderSample/MainWindow.xaml.cs
@@ -17,25 +17,24 @@ public MainWindow()
{
InitializeComponent();
- ProviderManager.Instance.ProviderUpdated += this.OnProviderUpdated;
+ ProviderManager.Instance.ProviderStateChanged += OnProviderStateChanged;
}
- private async void OnProviderUpdated(object sender, ProviderUpdatedEventArgs e)
+ private async void OnProviderStateChanged(object sender, ProviderStateChangedEventArgs e)
{
- var provider = ProviderManager.Instance.GlobalProvider;
- if (provider == null || provider.State != ProviderState.SignedIn)
- {
- SignedInUserTextBlock.Text = "Please sign in.";
- }
- else
+ if (e.NewState == ProviderState.SignedIn)
{
SignedInUserTextBlock.Text = "Signed in as...";
- var graphClient = provider.GetClient();
+ var graphClient = ProviderManager.Instance.GlobalProvider.GetClient();
var me = await graphClient.Me.Request().GetAsync();
SignedInUserTextBlock.Text = "Signed in as: " + me.DisplayName;
}
+ else
+ {
+ SignedInUserTextBlock.Text = "Please sign in.";
+ }
}
}
}