diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6f5cd08..6442a92 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us fix something that isn't working as expected title: '' -labels: bug +labels: "bug :bug:" assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md index ef255cc..34a7cfb 100644 --- a/.github/ISSUE_TEMPLATE/documentation.md +++ b/.github/ISSUE_TEMPLATE/documentation.md @@ -2,7 +2,7 @@ name: Documentation about: I have a documentation suggestion or question title: "[Docs]" -labels: documentation +labels: "documentation :page_with_curl:" assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index d8b8a7d..0b339ff 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -2,7 +2,7 @@ name: Feature request about: I have a new idea or improvement for the toolkit title: "[Feature]" -labels: feature request +labels: feature request 📬 assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 0b936ef..21b0162 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -2,7 +2,7 @@ name: Question about: I have a question about how to use something in the toolkit. title: "[Question]" -labels: question +labels: "question :grey_question:" assignees: '' --- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..571b740 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,40 @@ +Fixes # + + + +## PR Type + +What kind of change does this PR introduce? + + + + + + + + + + + + +## What is the current behavior? + + + +## What is the new behavior? + +## PR Checklist + +Please check if your PR fulfills the following requirements: + +- [ ] Tested code with current [supported SDKs](https://github.com/windows-toolkit/Graph-Controls/blob/main/README.md) +- [ ] Sample in sample app has been added / updated (for bug fixes / features) + - [ ] Icon has been created (if new sample) following the [Thumbnail Style Guide and templates](https://github.com/windows-toolkit/WindowsCommunityToolkit-design-assets) +- [ ] Tests for the changes have been added (for bug fixes / features) (if applicable) +- [ ] Header has been added to all new source files (run _build/UpdateHeaders.bat_) +- [ ] Contains **NO** breaking changes + + + +## Other information diff --git a/Directory.Build.props b/Directory.Build.props index 227593e..5971618 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,14 +13,15 @@ en-US - uap10.0.16299;MonoAndroid80;xamarinios10;netstandard2.0 + uap10.0.17763;MonoAndroid90;xamarinios10;netstandard2.0 $(MSBuildProjectName.Contains('.Design')) $(MSBuildProjectName.Contains('Test')) $(MSBuildProjectName.Contains('Uwp')) $(MSBuildProjectName.Contains('Sample')) + $(MSBuildProjectName.Contains('Wpf')) 17763 - 16299 + 17763 $(MSBuildThisFileDirectory)bin\nupkg @@ -56,23 +57,23 @@ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb - - + + - + - + - + true @@ -82,7 +83,7 @@ $(DefineConstants);__WASM__ - + $(NoWarn);CS0649;CS0067;CS1998 @@ -95,7 +96,7 @@ - + @@ -109,4 +110,4 @@ $(JAVA_HOME_8_X64) - \ No newline at end of file + diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/GraphPresenter/GraphPresenter.cs b/Microsoft.Toolkit.Graph.Controls/Controls/GraphPresenter/GraphPresenter.cs new file mode 100644 index 0000000..dd5d2ee --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Controls/GraphPresenter/GraphPresenter.cs @@ -0,0 +1,113 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Graph; +using Microsoft.Toolkit.Uwp.Helpers; +using Newtonsoft.Json.Linq; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Graph.Controls +{ + /// + /// Specialized to fetch and display data from the Microsoft Graph. + /// + public partial class GraphPresenter : ContentPresenter + { + /// + /// Gets or sets a to be used to make a request to the graph. The results will be automatically populated to the property. Use a to change the presentation of the data. + /// + public IBaseRequestBuilder RequestBuilder + { + get { return (IBaseRequestBuilder)GetValue(RequestBuilderProperty); } + set { SetValue(RequestBuilderProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// + /// The identifier for the dependency property. + /// + public static readonly DependencyProperty RequestBuilderProperty = + DependencyProperty.Register(nameof(RequestBuilder), typeof(IBaseRequestBuilder), typeof(GraphPresenter), new PropertyMetadata(null)); + + /// + /// Gets or sets the of item returned by the . + /// Set to the base item type and use the property to indicate if a collection is expected back. + /// + public Type ResponseType { get; set; } + + /// + /// Gets or sets a value indicating whether the returned data from the is a collection. + /// + public bool IsCollection { get; set; } + + /// + /// Gets or sets list of representing values to pass into the request built by . + /// + public List QueryOptions { get; set; } = new List(); + + /// + /// Gets or sets a string to indicate a sorting order for the . This is a helper to add this specific request option to the . + /// + public string OrderBy { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public GraphPresenter() + { + Loaded += GraphPresenter_Loaded; + } + + private async void GraphPresenter_Loaded(object sender, RoutedEventArgs e) + { + // Note: some interfaces from the Graph SDK don't implement IBaseRequestBuilder properly, see https://github.com/microsoftgraph/msgraph-sdk-dotnet/issues/722 + if (RequestBuilder != null) + { + var request = new BaseRequest( + RequestBuilder.RequestUrl, + RequestBuilder.Client); // TODO: Do we need separate Options here? + request.Method = "GET"; + request.QueryOptions = QueryOptions?.Select(option => option.ToQueryOption())?.ToList() ?? new List(); + + // Handle Special QueryOptions + if (!string.IsNullOrWhiteSpace(OrderBy)) + { + request.QueryOptions.Add(new Microsoft.Graph.QueryOption("$orderby", OrderBy)); + } + + try + { + var response = await request.SendAsync(null, CancellationToken.None).ConfigureAwait(false) as JObject; + + //// TODO: Deal with paging? + + var values = response["value"]; + object data = null; + + if (IsCollection) + { + data = values.ToObject(Array.CreateInstance(ResponseType, 0).GetType()); + } + else + { + data = values.ToObject(ResponseType); + } + + _ = DispatcherHelper.ExecuteOnUIThreadAsync(() => Content = data); + } + catch + { + // TODO: We should figure out what we want to do for Loading/Error states here. + } + } + } + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/GraphPresenter/QueryOption.cs b/Microsoft.Toolkit.Graph.Controls/Controls/GraphPresenter/QueryOption.cs new file mode 100644 index 0000000..9f17a46 --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Controls/GraphPresenter/QueryOption.cs @@ -0,0 +1,34 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Graph.Controls +{ + /// + /// XAML Proxy for . + /// + public partial class QueryOption : DependencyObject + { + /// + public string Name { get; set; } + + /// + public string Value { get; set; } + + /// + /// Constructs a value representing this proxy. + /// + /// result. + public Microsoft.Graph.QueryOption ToQueryOption() + { + return new Microsoft.Graph.QueryOption(Name, Value); + } + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Events.cs b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Events.cs index b3a8adb..2cb11f8 100644 --- a/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Events.cs +++ b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Events.cs @@ -25,7 +25,7 @@ public partial class LoginButton /// /// The user canceled the login process or was unable to sign in. /// - public event EventHandler LoginFailed; + public event EventHandler LoginFailed; /// /// The user started to logout - cancelable. diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Properties.cs b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Properties.cs index c9e2e97..d7a5e66 100644 --- a/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Properties.cs +++ b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.Properties.cs @@ -46,6 +46,6 @@ public bool IsLoading /// The identifier for the dependency property. /// public static readonly DependencyProperty IsLoadingProperty = - DependencyProperty.Register("IsLoading", typeof(bool), typeof(LoginButton), new PropertyMetadata(true)); + DependencyProperty.Register(nameof(IsLoading), typeof(bool), typeof(LoginButton), new PropertyMetadata(true)); } } diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.cs b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.cs index 8622648..a596b07 100644 --- a/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.cs +++ b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginButton.cs @@ -32,15 +32,7 @@ public LoginButton() { this.DefaultStyleKey = typeof(LoginButton); - ProviderManager.Instance.ProviderUpdated += (sender, args) => - { - if (!IsLoading && ProviderManager.Instance?.GlobalProvider?.State == ProviderState.Loading) - { - IsLoading = true; - } - - LoadData(); - }; + ProviderManager.Instance.ProviderUpdated += (sender, args) => LoadData(); } /// @@ -48,6 +40,7 @@ protected override void OnApplyTemplate() { base.OnApplyTemplate(); + IsLoading = true; LoadData(); if (_loginButton != null) @@ -110,7 +103,11 @@ private async void LoadData() return; } - if (provider.State == ProviderState.SignedIn) + if (provider.State == ProviderState.Loading) + { + IsLoading = true; + } + else if (provider.State == ProviderState.SignedIn) { try { @@ -118,22 +115,24 @@ private async void LoadData() // TODO: Batch with photo request later? https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/issues/29 UserDetails = await provider.Graph.Me.Request().GetAsync(); } - catch (Exception) + catch (Exception e) { - LoginFailed?.Invoke(this, new EventArgs()); + 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 - return; + System.Diagnostics.Debug.Fail("unsupported state"); } - - IsLoading = false; } /// @@ -142,7 +141,7 @@ private async void LoadData() /// A representing the asynchronous operation. public async Task LoginAsync() { - if (UserDetails != null) + if (UserDetails != null || IsLoading) { return; } @@ -151,19 +150,31 @@ public async Task LoginAsync() if (provider != null) { - await provider.LoginAsync(); - - if (provider.State == ProviderState.SignedIn) + try { - // TODO: include user details? - LoginCompleted?.Invoke(this, new EventArgs()); + IsLoading = true; + await provider.LoginAsync(); + + 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."))); + } } - else + catch (Exception e) { - LoginFailed?.Invoke(this, new EventArgs()); + LoginFailed?.Invoke(this, new LoginFailedEventArgs(e)); + } + finally + { + IsLoading = false; } - - LoadData(); } } @@ -173,6 +184,17 @@ public async Task LoginAsync() /// A representing the asynchronous operation. public async Task LogoutAsync() { + // Close Menu + if (FlyoutBase.GetAttachedFlyout(_loginButton) is FlyoutBase flyout) + { + flyout.Hide(); + } + + if (IsLoading) + { + return; + } + var cargs = new CancelEventArgs(); LogoutInitiated?.Invoke(this, cargs); @@ -194,16 +216,12 @@ public async Task LogoutAsync() if (provider != null) { + IsLoading = true; await provider.LogoutAsync(); + IsLoading = false; LogoutCompleted?.Invoke(this, new EventArgs()); } - - // Close Menu - if (FlyoutBase.GetAttachedFlyout(_loginButton) is FlyoutBase flyout) - { - flyout.Hide(); - } } } } diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginFailedEventArgs.cs b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginFailedEventArgs.cs new file mode 100644 index 0000000..bab5f49 --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Controls/LoginButton/LoginFailedEventArgs.cs @@ -0,0 +1,50 @@ +// 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 Microsoft.Toolkit.Graph.Controls +{ + /// + /// for event. + /// + public class LoginFailedEventArgs : EventArgs + { + /// + /// Gets the exception which occured during login. + /// + public Exception Exception { get; private set; } + + /// + /// Gets the inner exception which occured during login. + /// + public Exception InnerException + { + get + { + return Exception?.InnerException; + } + } + + /// + /// Gets the error message of the inner error or error. + /// + public string Message + { + get + { + return Exception?.InnerException?.Message ?? Exception?.Message; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Exception encountered during login. + public LoginFailedEventArgs(Exception exception) + { + Exception = exception; + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/PeoplePicker/PeoplePicker.cs b/Microsoft.Toolkit.Graph.Controls/Controls/PeoplePicker/PeoplePicker.cs new file mode 100644 index 0000000..c5850e2 --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Controls/PeoplePicker/PeoplePicker.cs @@ -0,0 +1,116 @@ +// 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.Collections; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.Graph; +using Microsoft.Toolkit.Graph.Extensions; +using Microsoft.Toolkit.Graph.Providers; +using Microsoft.Toolkit.Uwp.UI.Controls; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace Microsoft.Toolkit.Graph.Controls +{ + /// + /// Control which allows user to search for a person or contact within Microsoft Graph. Built on top of . + /// + public partial class PeoplePicker : TokenizingTextBox + { + private DispatcherTimer _typeTimer = new DispatcherTimer(); + + /// + /// Initializes a new instance of the class. + /// + public PeoplePicker() + { + this.DefaultStyleKey = typeof(PeoplePicker); + + SuggestedItemsSource = new ObservableCollection(); + + TextChanged += TokenBox_TextChanged; + TokenItemAdding += TokenBox_TokenItemTokenItemAdding; + } + + private async void TokenBox_TokenItemTokenItemAdding(TokenizingTextBox sender, TokenItemAddingEventArgs args) + { + using (args.GetDeferral()) + { + // Try and convert typed text to people + var graph = ProviderManager.Instance.GlobalProvider.Graph; + if (graph != null) + { + args.Item = (await graph.FindPersonAsync(args.TokenText)).CurrentPage.FirstOrDefault(); + } + + // If we didn't find anyone, then don't add anyone. + if (args.Item == null) + { + args.Cancel = true; + + // TODO: We should raise a 'person not found' type event or automatically display some feedback? + } + } + } + + private void TokenBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (!args.CheckCurrent()) + { + return; + } + + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) + { + var text = sender.Text; + var list = SuggestedItemsSource as IList; + + if (list != null) + { + _typeTimer.Debounce( + async () => + { + var graph = ProviderManager.Instance.GlobalProvider.Graph; + if (graph != null) + { + // If empty, will clear out + list.Clear(); + + if (!string.IsNullOrWhiteSpace(text)) + { + foreach (var user in (await graph.FindUserAsync(text)).CurrentPage) + { + // Exclude people in suggested list that we already have picked + if (!Items.Any(person => (person as Person)?.Id == user.Id)) + { + list.Add(user.ToPerson()); + } + } + + // Grab ids of current suggestions + var ids = list.Cast().Select(person => (person as Person).Id); + + foreach (var contact in (await graph.FindPersonAsync(text)).CurrentPage) + { + // Exclude people in suggested list that we already have picked + // Or already suggested + if (!Items.Any(person => (person as Person)?.Id == contact.Id) && + !ids.Any(id => id == contact.Id)) + { + list.Add(contact); + } + } + } + } + + // TODO: If we don't have Graph connection and just list of Person should we auto-filter here? + }, TimeSpan.FromSeconds(0.3)); + } + } + } + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Controls/PeoplePicker/PeoplePicker.xaml b/Microsoft.Toolkit.Graph.Controls/Controls/PeoplePicker/PeoplePicker.xaml new file mode 100644 index 0000000..7b87816 --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Controls/PeoplePicker/PeoplePicker.xaml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj b/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj index d1bad7c..c259958 100644 --- a/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj +++ b/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj @@ -15,6 +15,9 @@ UWP Toolkit Windows Controls MSAL Microsoft Graph AadLogin ProfileCard Person PeoplePicker Login false true + 8.0 + Debug;Release;CI + AnyCPU;ARM;ARM64;x64;x86 CS1701;1702;Uno0001;NU1701 @@ -31,12 +34,14 @@ - - + + + - - + + + @@ -44,8 +49,9 @@ - - + + + @@ -56,7 +62,7 @@ - + diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderBehaviorBase.Properties.cs b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderBehaviorBase.Properties.cs new file mode 100644 index 0000000..82ca59c --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderBehaviorBase.Properties.cs @@ -0,0 +1,66 @@ +// 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 Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Graph.Providers +{ + /// + /// Properties for . + /// + public partial class CommonProviderBehaviorBase + { + /// + /// Gets or sets the Client ID (the unique application (client) ID assigned to your app by Azure AD when the app was registered). + /// + /// + /// For details about how to register an app and get a client ID, + /// see the Register an app quick start. + /// + public string ClientId + { + get { return (string)GetValue(ClientIdProperty); } + set { SetValue(ClientIdProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// + /// The identifier for the dependency property. + /// + public static readonly DependencyProperty ClientIdProperty = + DependencyProperty.Register(nameof(ClientId), typeof(string), typeof(CommonProviderBehaviorBase), new PropertyMetadata(string.Empty)); + + /// + /// Gets or sets the redirect URI (the URI the identity provider will send the security tokens back to). + /// + public string RedirectUri + { + get { return (string)GetValue(RedirectUriProperty); } + set { SetValue(RedirectUriProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// + /// The identifier for the dependency property. + /// + public static readonly DependencyProperty RedirectUriProperty = +#if DOTNET + DependencyProperty.Register(nameof(RedirectUri), typeof(string), typeof(CommonProviderBehaviorBase), new PropertyMetadata("http://localhost")); //// https://aka.ms/msal-net-os-browser +#else + DependencyProperty.Register(nameof(RedirectUri), typeof(string), typeof(CommonProviderBehaviorBase), new PropertyMetadata("https://login.microsoftonline.com/common/oauth2/nativeclient")); +#endif + + /// + /// Gets or sets the list of Scopes (permissions) to request on initial login. + /// + /// + /// This list can be modified by controls which require specific scopes to function. This will aid in requesting all scopes required by controls used before login is initiated, if using the LoginButton. + /// + public ScopeSet Scopes { get; set; } = new ScopeSet { "User.Read", "User.ReadBasic.All" }; + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderBehaviorBase.cs b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderBehaviorBase.cs new file mode 100644 index 0000000..3679269 --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Providers/CommonProviderBehaviorBase.cs @@ -0,0 +1,16 @@ +// 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 Microsoft.Toolkit.Uwp.UI.Behaviors; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Graph.Providers +{ + /// + /// Provides a common base class for UWP XAML based provider wrappers to the Microsoft.Graph.Auth SDK. + /// + public abstract partial class CommonProviderBehaviorBase : BehaviorBase + { + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/InteractiveProviderBehavior.cs b/Microsoft.Toolkit.Graph.Controls/Providers/InteractiveProviderBehavior.cs new file mode 100644 index 0000000..f771bac --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Providers/InteractiveProviderBehavior.cs @@ -0,0 +1,52 @@ +// 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.Linq; +using Windows.UI.Core; + +namespace Microsoft.Toolkit.Graph.Providers +{ + /// + /// Put in a xaml page with ClientId + /// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Acquiring-tokens-interactively. + /// + /// + /// + /// <Interactivity:Interaction.Behaviors> + /// <providers:InteractiveProviderBehavior ClientId = "MyClientIdGuid"/> + /// </Interactivity:Interaction.Behaviors> + /// + /// + public class InteractiveProviderBehavior : CommonProviderBehaviorBase + { + private object lock_sync = new object(); + private bool initialized = false; + + /// + protected override bool Initialize() + { + lock (lock_sync) + { + if (!initialized) + { +#if DOTNET + _ = Dispatcher.BeginInvoke(new Action(async () => + { + ProviderManager.Instance.GlobalProvider = + await QuickCreate.CreateMsalProviderAsync(ClientId, RedirectUri, Scopes.ToArray()); + }), System.Windows.Threading.DispatcherPriority.Normal); +#else + _ = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => + { + ProviderManager.Instance.GlobalProvider = + await QuickCreate.CreateMsalProviderAsync(ClientId, RedirectUri, Scopes.ToArray()); + }); +#endif + } + } + + return base.Initialize(); + } + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/MockProviderBehavior.cs b/Microsoft.Toolkit.Graph.Controls/Providers/MockProviderBehavior.cs new file mode 100644 index 0000000..4f56d3a --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Providers/MockProviderBehavior.cs @@ -0,0 +1,58 @@ +// 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.Linq; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Graph.Providers +{ + /// + /// Put in a xaml page with ClientId + /// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Acquiring-tokens-interactively. + /// + /// + /// + /// <Interactivity:Interaction.Behaviors> + /// <providers:InteractiveProviderBehavior ClientId = "MyClientIdGuid"/> + /// </Interactivity:Interaction.Behaviors> + /// + /// + public class MockProviderBehavior : CommonProviderBehaviorBase + { + private object lock_sync = new object(); + private bool initialized = false; + + /// + /// Gets or sets a value indicating whether the mock provider is signed-in upon initialization. + /// + public bool SignedIn + { + get { return (bool)GetValue(SignedInProperty); } + set { SetValue(SignedInProperty, value); } + } + + /// + /// Identifies the dependency property. + /// + /// + /// The identifier for the dependency property. + /// + public static readonly DependencyProperty SignedInProperty = + DependencyProperty.Register(nameof(SignedIn), typeof(bool), typeof(MockProviderBehavior), new PropertyMetadata(true)); + + /// + protected override bool Initialize() + { + lock (lock_sync) + { + if (!initialized) + { + ProviderManager.Instance.GlobalProvider = new MockProvider(SignedIn); + } + } + + return base.Initialize(); + } + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/QuickCreate.cs b/Microsoft.Toolkit.Graph.Controls/Providers/QuickCreate.cs index 3c8337b..487e020 100644 --- a/Microsoft.Toolkit.Graph.Controls/Providers/QuickCreate.cs +++ b/Microsoft.Toolkit.Graph.Controls/Providers/QuickCreate.cs @@ -1,17 +1,36 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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.Reflection; +using System.Threading.Tasks; using Microsoft.Graph.Auth; using Microsoft.Identity.Client; using Microsoft.Toolkit.Graph.Providers; -using System.Reflection; -using System.Threading.Tasks; +using Uno.UI.MSAL; namespace Microsoft.Toolkit.Graph.Providers { + //// TODO: This should probably live in .NET Standard lib; however, Uno one needs to be at UI layer for Parent Window? + //// TODO: Need to set up XAML Islands sample to test in the new repo and make sure this works from this context. + + /// + /// Helper class to easily initialize provider from just ClientId. + /// public static class QuickCreate { + /// + /// Easily creates a from a ClientId. + /// + /// + /// + /// ProviderManager.Instance.GlobalProvider = await QuickCreate.CreateMsalProviderAsync("MyClientId"); + /// + /// + /// Registered ClientId. + /// RedirectUri for auth response. + /// List of Scopes to initially request. + /// New reference. public static async Task CreateMsalProviderAsync(string clientid, string redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient", string[] scopes = null) { var client = PublicClientApplicationBuilder.Create(clientid) @@ -19,9 +38,7 @@ public static async Task CreateMsalProviderAsync(string clientid, .WithRedirectUri(redirectUri) .WithClientName(ProviderManager.ClientName) .WithClientVersion(Assembly.GetExecutingAssembly().GetName().Version.ToString()) -#if __ANDROID__ - .WithParentActivityOrWindow(() => Uno.UI.ContextHelper.Current as Android.App.Activity) -#endif + .WithUnoHelpers() .Build(); if (scopes == null) diff --git a/Microsoft.Toolkit.Graph.Controls/Providers/ScopeSet.cs b/Microsoft.Toolkit.Graph.Controls/Providers/ScopeSet.cs new file mode 100644 index 0000000..b3a418a --- /dev/null +++ b/Microsoft.Toolkit.Graph.Controls/Providers/ScopeSet.cs @@ -0,0 +1,74 @@ +// 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.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Microsoft.Toolkit.Graph.Providers +{ + /// + /// Helper Class for XAML string Scope conversion. + /// + [Windows.Foundation.Metadata.CreateFromString(MethodName = "Microsoft.Toolkit.Graph.Providers.ScopeSet.ConvertToScopeArray")] + public class ScopeSet : Collection + { + /// + /// Empty ScopeSet helper. + /// + public static readonly ScopeSet Empty = new ScopeSet(new string[] { }); + + /// + /// Helper to convert a string of scopes to a list of strings. + /// + /// Comma separated scope list. + /// New List of strings, i.e. ScopeSet. + public static ScopeSet ConvertToScopeArray(string rawString) + { + if (rawString != null) + { + return new ScopeSet(rawString.Split(',')); + } + + return ScopeSet.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + /// Array to copy as ScopeSet. + public ScopeSet(string[] arr) + { + this.AddRange(arr); + } + + /// + /// Initializes a new instance of the class. + /// + /// List to copy as ScopeSet. + public ScopeSet(List list) + { + this.AddRange(list.ToArray()); + } + + /// + /// Initializes a new instance of the class. + /// + public ScopeSet() + { + } + + /// + /// Adds range of items to the scope set. + /// + /// Items to add. + public void AddRange(string[] items) + { + foreach (var item in items) + { + this.Add(item); + } + } + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Graph.Controls/Themes/Generic.xaml index cc156c3..6cf76ca 100644 --- a/Microsoft.Toolkit.Graph.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Graph.Controls/Themes/Generic.xaml @@ -2,6 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + \ No newline at end of file diff --git a/Microsoft.Toolkit.Graph.Controls/VisualStudioToolsManifest.xml b/Microsoft.Toolkit.Graph.Controls/VisualStudioToolsManifest.xml index 03751c3..71baf39 100644 --- a/Microsoft.Toolkit.Graph.Controls/VisualStudioToolsManifest.xml +++ b/Microsoft.Toolkit.Graph.Controls/VisualStudioToolsManifest.xml @@ -2,6 +2,7 @@ + diff --git a/Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs b/Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs index b56b303..9d7668a 100644 --- a/Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs +++ b/Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs @@ -2,6 +2,7 @@ // 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.Threading.Tasks; using Microsoft.Graph; @@ -33,8 +34,8 @@ public static Person ToPerson(this User user) { new RankedEmailAddress() { - Address = user.Mail ?? user.UserPrincipalName - } + Address = user.Mail ?? user.UserPrincipalName, + }, }, GivenName = user.GivenName, Surname = user.Surname, @@ -43,16 +44,16 @@ public static Person ToPerson(this User user) CompanyName = user.CompanyName, Department = user.Department, Title = user.JobTitle, - OfficeLocation = user.OfficeLocation + OfficeLocation = user.OfficeLocation, }; } /// /// Shortcut to perform a person query. /// - /// Instance of the + /// Instance of the . /// User to search for. - /// collection of . + /// collection of . public static async Task FindPersonAsync(this GraphServiceClient graph, string query) { try @@ -72,11 +73,35 @@ public static async Task FindPersonAsync(this GraphSe return new UserPeopleCollectionPage(); } + /// + /// Shortcut to perform a user query. + /// + /// Instance of the . + /// User to search for. + /// collection of . + public static async Task FindUserAsync(this GraphServiceClient graph, string query) + { + try + { + return await graph + .Users + .Request() + .Filter($"startswith(displayName, '{query}') or startswith(givenName, '{query}') or startswith(surname, '{query}') or startswith(mail, '{query}') or startswith(userPrincipalName, '{query}')") + .WithScopes(new string[] { "user.readbasic.all" }) + .GetAsync(); + } + catch + { + } + + return new GraphServiceUsersCollectionPage(); + } + /// /// Helper to get the photo of a particular user. /// - /// Instance of the - /// UserID + /// Instance of the . + /// UserID. /// Stream with user photo or null. public static async Task GetUserPhoto(this GraphServiceClient graph, string userid) { @@ -98,16 +123,17 @@ public static async Task GetUserPhoto(this GraphServiceClient graph, str } /// - /// Extension to provider Searching on OData Requests + /// Extension to provider Searching on OData Requests. /// - /// type + /// type. /// Request chain. - /// Query to add for searching in QueryOptions - /// Same type + /// Query to add for searching in QueryOptions. + /// Same type. public static T Search(this T request, string query) where T : IBaseRequest { - request.QueryOptions?.Add(new QueryOption("$search", query)); + // Need quotes around query for e-mail searches: https://docs.microsoft.com/en-us/graph/people-example#perform-a-fuzzy-search + request.QueryOptions?.Add(new QueryOption("$search", '"' + query + '"')); return request; } diff --git a/Microsoft.Toolkit.Graph/Extensions/HttpRequestMessageExtensions.cs b/Microsoft.Toolkit.Graph/Extensions/HttpRequestMessageExtensions.cs new file mode 100644 index 0000000..9d75abf --- /dev/null +++ b/Microsoft.Toolkit.Graph/Extensions/HttpRequestMessageExtensions.cs @@ -0,0 +1,49 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.Toolkit.Graph.Extensions +{ + /// + /// Helpers for Graph related HTTP Headers. + /// + internal static class HttpRequestMessageExtensions + { + private const string SdkVersion = "SdkVersion"; + private const string LibraryName = "wct"; + private const string Bearer = "Bearer"; + private const string MockGraphToken = "{token:https://graph.microsoft.com/}"; + + public static void AddSdkVersion(this HttpRequestMessage request) + { + if (request == null || request.Headers == null) + { + return; + } + + if (request.Headers.TryGetValues(SdkVersion, out IEnumerable values)) + { + var versions = new List(values); + versions.Insert(0, LibraryName + "/" + Assembly.GetExecutingAssembly().GetName().Version); + request.Headers.Remove(SdkVersion); + request.Headers.Add(SdkVersion, versions); + } + } + + public static void AddMockProviderToken(this HttpRequestMessage request) + { + request + .Headers + .Authorization = new AuthenticationHeaderValue(Bearer, MockGraphToken); + } + } +} diff --git a/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj b/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj index 7267057..c84ec69 100644 --- a/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj +++ b/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj @@ -12,6 +12,8 @@ Full + Debug;Release;CI + AnyCPU;ARM;ARM64;x64;x86 CS1701;1702;CS8002;Uno0001;NU1701 @@ -19,13 +21,13 @@ $(DefineConstants);WINRT - + $(DefineConstants);WINRT - - - + + + diff --git a/Microsoft.Toolkit.Graph/Providers/IProvider.cs b/Microsoft.Toolkit.Graph/Providers/IProvider.cs index 4e089ea..ea59b80 100644 --- a/Microsoft.Toolkit.Graph/Providers/IProvider.cs +++ b/Microsoft.Toolkit.Graph/Providers/IProvider.cs @@ -34,13 +34,13 @@ public interface IProvider : IAuthenticationProvider /// /// Login the user. /// - /// + /// . Task LoginAsync(); /// /// Logout the user. /// - /// + /// . Task LogoutAsync(); } } diff --git a/Microsoft.Toolkit.Graph/Providers/MockProvider.cs b/Microsoft.Toolkit.Graph/Providers/MockProvider.cs new file mode 100644 index 0000000..06517ac --- /dev/null +++ b/Microsoft.Toolkit.Graph/Providers/MockProvider.cs @@ -0,0 +1,101 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Graph; +using Microsoft.Toolkit.Graph.Extensions; + +namespace Microsoft.Toolkit.Graph.Providers +{ + /// + /// Provider to connect to the example data set for Microsoft Graph. Useful for prototyping and samples. + /// + public class MockProvider : IProvider + { + private const string GRAPH_PROXY_URL = "https://proxy.apisandbox.msdn.microsoft.com/svc?url="; + + private ProviderState _state = ProviderState.Loading; + + /// + public ProviderState State + { + get + { + return _state; + } + + private set + { + var current = _state; + _state = value; + + StateChanged?.Invoke(this, new StateChangedEventArgs(current, _state)); + } + } + + /// + public GraphServiceClient Graph => new GraphServiceClient( + new DelegateAuthenticationProvider((requestMessage) => + { + var requestUri = requestMessage.RequestUri.ToString(); + + // Prepend Proxy Service URI to our request + requestMessage.RequestUri = new Uri(GRAPH_PROXY_URL + Uri.EscapeDataString(requestUri)); + + return this.AuthenticateRequestAsync(requestMessage); + })); + + /// + public event EventHandler StateChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Whether the default state should be signedIn, defaults to true. + public MockProvider(bool signedIn = true) + { + if (signedIn) + { + State = ProviderState.SignedIn; + } + else + { + State = ProviderState.SignedOut; + } + } + + /// + public Task AuthenticateRequestAsync(HttpRequestMessage request) + { + request.AddSdkVersion(); + + request.AddMockProviderToken(); + + return Task.FromResult(0); + } + + /// + public async Task LoginAsync() + { + State = ProviderState.Loading; + await Task.Delay(3000); + State = ProviderState.SignedIn; + } + + /// + public async Task LogoutAsync() + { + State = ProviderState.Loading; + await Task.Delay(3000); + State = ProviderState.SignedOut; + } + } +} diff --git a/Microsoft.Toolkit.Graph/Providers/MsalProvider.cs b/Microsoft.Toolkit.Graph/Providers/MsalProvider.cs index 66443e3..42c00bc 100644 --- a/Microsoft.Toolkit.Graph/Providers/MsalProvider.cs +++ b/Microsoft.Toolkit.Graph/Providers/MsalProvider.cs @@ -6,9 +6,11 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Reflection; using System.Threading.Tasks; using Microsoft.Graph; using Microsoft.Identity.Client; +using Microsoft.Toolkit.Graph.Extensions; namespace Microsoft.Toolkit.Graph.Providers { @@ -39,7 +41,7 @@ public ProviderState State return _state; } - set + private set { var current = _state; _state = value; @@ -55,7 +57,7 @@ public ProviderState State public event EventHandler StateChanged; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. . /// private MsalProvider() { @@ -75,9 +77,10 @@ public static async Task CreateAsync(IPublicClientApplication clie { Client = client, Provider = provider, - Graph = new GraphServiceClient(provider) }; + msal.Graph = new GraphServiceClient(msal); + await msal.TrySilentSignInAsync(); return msal; @@ -86,6 +89,8 @@ public static async Task CreateAsync(IPublicClientApplication clie /// public async Task AuthenticateRequestAsync(HttpRequestMessage request) { + request.AddSdkVersion(); + try { await Provider.AuthenticateRequestAsync(request); diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs b/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs index 39b29f2..f585ef5 100644 --- a/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs +++ b/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.ComponentModel; using Microsoft.Graph; using Microsoft.Identity.Client; using Microsoft.Toolkit.Helpers; @@ -17,23 +18,26 @@ namespace Microsoft.Toolkit.Graph.Providers /// ProviderManager.Instance.GlobalProvider = await MsalProvider.CreateAsync(...); /// /// - public class ProviderManager + public class ProviderManager : INotifyPropertyChanged { /// /// Gets the name of the toolkit client to identify self in Graph calls. /// - public static readonly string ClientName = "Windows Community Toolkit" + ThisAssembly.AssemblyVersion; + public static readonly string ClientName = "wct/" + ThisAssembly.AssemblyVersion; /// - /// Gets the instance of the GlobalProvider + /// Gets the instance of the GlobalProvider. /// - public static ProviderManager Instance => Singleton.Instance; + public static ProviderManager Instance { get; } = new ProviderManager(); /// /// Event called when the changes. /// public event EventHandler ProviderUpdated; + /// + public event PropertyChangedEventHandler PropertyChanged; + private IProvider _provider; /// @@ -61,9 +65,16 @@ public IProvider GlobalProvider } ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderChanged)); + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GlobalProvider))); } } + private ProviderManager() + { + // Use Instance + } + private void ProviderStateChanged(object sender, StateChangedEventArgs e) { ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderStateChanged)); diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderManagerChangedState.cs b/Microsoft.Toolkit.Graph/Providers/ProviderManagerChangedState.cs index 31ff1b9..c7a5c2f 100644 --- a/Microsoft.Toolkit.Graph/Providers/ProviderManagerChangedState.cs +++ b/Microsoft.Toolkit.Graph/Providers/ProviderManagerChangedState.cs @@ -17,6 +17,6 @@ public enum ProviderManagerChangedState /// /// The changed. /// - ProviderStateChanged + ProviderStateChanged, } } \ No newline at end of file diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderState.cs b/Microsoft.Toolkit.Graph/Providers/ProviderState.cs index ec7c856..720f24a 100644 --- a/Microsoft.Toolkit.Graph/Providers/ProviderState.cs +++ b/Microsoft.Toolkit.Graph/Providers/ProviderState.cs @@ -22,6 +22,6 @@ public enum ProviderState /// /// The user is signed-in. /// - SignedIn + SignedIn, } } diff --git a/Microsoft.Toolkit.Graph/Providers/StateChangedEventArgs.cs b/Microsoft.Toolkit.Graph/Providers/StateChangedEventArgs.cs index d795176..f2f65d0 100644 --- a/Microsoft.Toolkit.Graph/Providers/StateChangedEventArgs.cs +++ b/Microsoft.Toolkit.Graph/Providers/StateChangedEventArgs.cs @@ -14,8 +14,8 @@ public class StateChangedEventArgs : EventArgs /// /// Initializes a new instance of the class. /// - /// Previous - /// Current + /// Previous . + /// Current . public StateChangedEventArgs(ProviderState oldState, ProviderState newState) { OldState = oldState; diff --git a/README.md b/README.md index c918561..1e5b77d 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,6 @@ If you need similar controls for the Web, please use the [Microsoft Graph Toolki * Windows 10 18362 (🚧 TODO: Check Lower SDKs) * Android via [Uno.Graph-Controls](https://aka.ms/wgt-uno) use `Uno.Microsoft.Graph.Controls` package. * 🚧 Coming Soon 🚧 - * PeoplePicker control - * XAML Islands Sample * iOS (Waiting on [MSAL#1378](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/1378) merge should be 4.4.0?) ## Getting Started diff --git a/SampleGraphApp/SampleGraphApp.Droid/Main.cs b/SampleGraphApp/SampleGraphApp.Droid/Main.cs index 8f6b5a4..b066943 100644 --- a/SampleGraphApp/SampleGraphApp.Droid/Main.cs +++ b/SampleGraphApp/SampleGraphApp.Droid/Main.cs @@ -27,7 +27,7 @@ namespace SampleGraphApp.Droid public class Application : Windows.UI.Xaml.NativeApplication { public Application(IntPtr javaReference, JniHandleOwnership transfer) - : base(new App(), javaReference, transfer) + : base(() => new App(), javaReference, transfer) { ConfigureUniversalImageLoader(); } diff --git a/SampleGraphApp/SampleGraphApp.Droid/Properties/AndroidManifest.xml b/SampleGraphApp/SampleGraphApp.Droid/Properties/AndroidManifest.xml index 730b910..0df46d0 100644 --- a/SampleGraphApp/SampleGraphApp.Droid/Properties/AndroidManifest.xml +++ b/SampleGraphApp/SampleGraphApp.Droid/Properties/AndroidManifest.xml @@ -1,15 +1,15 @@  - - + + - + - + \ No newline at end of file diff --git a/SampleGraphApp/SampleGraphApp.Droid/SampleGraphApp.Droid.csproj b/SampleGraphApp/SampleGraphApp.Droid/SampleGraphApp.Droid.csproj index 7e0b2ab..9ecf8ba 100644 --- a/SampleGraphApp/SampleGraphApp.Droid/SampleGraphApp.Droid.csproj +++ b/SampleGraphApp/SampleGraphApp.Droid/SampleGraphApp.Droid.csproj @@ -71,10 +71,12 @@ - + + + diff --git a/SampleGraphApp/SampleGraphApp.Shared/Assets/FileIcon.png b/SampleGraphApp/SampleGraphApp.Shared/Assets/FileIcon.png new file mode 100644 index 0000000..0435822 Binary files /dev/null and b/SampleGraphApp/SampleGraphApp.Shared/Assets/FileIcon.png differ diff --git a/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml b/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml index 2a50db0..7b87c9f 100644 --- a/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml +++ b/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml @@ -6,10 +6,278 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wgt="using:Microsoft.Toolkit.Graph.Controls" - mc:Ignorable="d"> + xmlns:graph="using:Microsoft.Graph" + xmlns:glob="using:System.Globalization" + xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" + xmlns:providers="using:Microsoft.Toolkit.Graph.Providers" + xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:ex="using:Microsoft.Toolkit.Extensions" + mc:Ignorable="d" + Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" + xmlns:controlsKeepThis="using:Microsoft.Toolkit.Uwp.UI.Controls"> + + + + + - - - - + + + + + + + + + The `LoginButton` above allows your user and application to easily connect to the Microsoft Graph. They can then also easily logout from the drop-down menu. + + + + + + + The `PeoplePicker` lets a logged in user easily search for familiar people they interact with or contacts. Great for emails or messages. + + + Picked People: + + + + + + + + + + + + + + + + + The `GraphPresenter` is a unique control in the library which makes it easier for a developer to make any graph call and configure a nice display template in XAML. This opens up a world of possibilities for many uses outside of any other control available within this library. You can see a few examples of what's possible below. + + + + + + + + + + + + + The following example shows the `Me.CalendarView` API. + My Upcoming Calendar Events: + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following example shows the `Me.Messages` API for getting a user's inbox mail messages. + My Messages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The following example shows the `Me.Planner.Tasks` API for getting a user's tasks. + My Tasks: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Due + + + + + + + + + + + + + + + + + + + + + + + + The following example shows the beta `Teams/id/Channels/id/messages` API for getting a list of messages (without replies) from a Channel in Teams. + My Chat Messages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml.cs b/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml.cs index d54ef80..50f0cbe 100644 --- a/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml.cs +++ b/SampleGraphApp/SampleGraphApp.Shared/MainPage.xaml.cs @@ -2,13 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Graph; +using Microsoft.Graph.Extensions; using Microsoft.Toolkit.Graph.Providers; -using System.Security.Cryptography; -using System.Threading.Tasks; +using System; +using System.Text.RegularExpressions; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 - namespace SampleGraphApp { /// @@ -16,21 +17,64 @@ namespace SampleGraphApp /// public sealed partial class MainPage : Page { - public MainPage() + // 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); + + // Workaround for https://github.com/unoplatform/uno/issues/3261 + public GraphServiceClient Graph { - // Register Client ID Instructions: https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app - var ClientId = "CLIENT_ID_HERE"; - _ = Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => - { - ProviderManager.Instance.GlobalProvider = await QuickCreate.CreateMsalProviderAsync( - ClientId, -#if __ANDROID__ - $"msal{ClientId}://auth", // Need to change redirectUri on Android for protocol registration from AndroidManifest.xml, ClientId needs to be updated there as well to match above. -#endif - scopes: new string[] { "user.read", "user.readbasic.all", "people.read" }); - }); + get { return (GraphServiceClient)GetValue(GraphProperty); } + set { SetValue(GraphProperty, value); } + } + + // Using a DependencyProperty as the backing store for Graph. This enables animation, styling, binding, etc... + public static readonly DependencyProperty GraphProperty = + DependencyProperty.Register(nameof(Graph), typeof(GraphServiceClient), typeof(MainPage), new PropertyMetadata(null)); + public MainPage() + { this.InitializeComponent(); + + ProviderManager.Instance.ProviderUpdated += MainPage_ProviderUpdated; + + MainPage_ProviderUpdated(null, null); + } + + private void MainPage_ProviderUpdated(object sender, ProviderUpdatedEventArgs e) + { + Graph = ProviderManager.Instance.GlobalProvider.Graph; + } + + 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; + } + + public static IBaseRequestBuilder GetTeamsChannelMessagesBuilder(string team, string channel) + { + // Workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/3064 + return ProviderManager.Instance.GlobalProvider.Graph.Teams[team].Channels[channel].Messages; } } } diff --git a/SampleGraphApp/SampleGraphApp.UWP/Package.appxmanifest b/SampleGraphApp/SampleGraphApp.UWP/Package.appxmanifest index 0278653..db89ac9 100644 --- a/SampleGraphApp/SampleGraphApp.UWP/Package.appxmanifest +++ b/SampleGraphApp/SampleGraphApp.UWP/Package.appxmanifest @@ -32,7 +32,7 @@ Executable="SampleGraphApp.exe" EntryPoint="SampleGraphApp.App"> en-US UAP 10.0.18362.0 - 10.0.16299.0 + 10.0.17763.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -45,7 +45,6 @@ false false true - true @@ -93,10 +92,10 @@ true true - + - + diff --git a/SampleGraphApp/SampleGraphApp.Wasm/SampleGraphApp.Wasm.csproj b/SampleGraphApp/SampleGraphApp.Wasm/SampleGraphApp.Wasm.csproj index 329be87..2a9a6fb 100644 --- a/SampleGraphApp/SampleGraphApp.Wasm/SampleGraphApp.Wasm.csproj +++ b/SampleGraphApp/SampleGraphApp.Wasm/SampleGraphApp.Wasm.csproj @@ -33,9 +33,9 @@ - - - + + + \ No newline at end of file diff --git a/SampleGraphApp/SampleGraphApp.iOS/SampleGraphApp.iOS.csproj b/SampleGraphApp/SampleGraphApp.iOS/SampleGraphApp.iOS.csproj index 893a995..4782d42 100644 --- a/SampleGraphApp/SampleGraphApp.iOS/SampleGraphApp.iOS.csproj +++ b/SampleGraphApp/SampleGraphApp.iOS/SampleGraphApp.iOS.csproj @@ -127,7 +127,7 @@ - + diff --git a/Windows-Toolkit-Graph-Controls.sln b/Windows-Toolkit-Graph-Controls.sln index 51db34d..ee8569f 100644 --- a/Windows-Toolkit-Graph-Controls.sln +++ b/Windows-Toolkit-Graph-Controls.sln @@ -53,6 +53,13 @@ Global AppStore|iPhoneSimulator = AppStore|iPhoneSimulator AppStore|x64 = AppStore|x64 AppStore|x86 = AppStore|x86 + CI|Any CPU = CI|Any CPU + CI|ARM = CI|ARM + CI|ARM64 = CI|ARM64 + CI|iPhone = CI|iPhone + CI|iPhoneSimulator = CI|iPhoneSimulator + CI|x64 = CI|x64 + CI|x86 = CI|x86 Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM Debug|ARM64 = Debug|ARM64 @@ -104,6 +111,20 @@ Global {B2246169-0CD8-473C-AFF6-172310E2C3F6}.AppStore|x64.Build.0 = Release|Any CPU {B2246169-0CD8-473C-AFF6-172310E2C3F6}.AppStore|x86.ActiveCfg = Release|Any CPU {B2246169-0CD8-473C-AFF6-172310E2C3F6}.AppStore|x86.Build.0 = Release|Any CPU + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|Any CPU.ActiveCfg = CI|Any CPU + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|Any CPU.Build.0 = CI|Any CPU + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|ARM.ActiveCfg = CI|ARM + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|ARM.Build.0 = CI|ARM + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|ARM64.ActiveCfg = CI|ARM64 + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|ARM64.Build.0 = CI|ARM64 + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|iPhone.ActiveCfg = CI|Any CPU + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|iPhone.Build.0 = CI|Any CPU + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|iPhoneSimulator.ActiveCfg = CI|Any CPU + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|iPhoneSimulator.Build.0 = CI|Any CPU + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|x64.ActiveCfg = CI|x64 + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|x64.Build.0 = CI|x64 + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|x86.ActiveCfg = CI|x86 + {B2246169-0CD8-473C-AFF6-172310E2C3F6}.CI|x86.Build.0 = CI|x86 {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU {B2246169-0CD8-473C-AFF6-172310E2C3F6}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -174,6 +195,20 @@ Global {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.AppStore|x64.Build.0 = Release|Any CPU {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.AppStore|x86.ActiveCfg = Release|Any CPU {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.AppStore|x86.Build.0 = Release|Any CPU + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|Any CPU.ActiveCfg = CI|Any CPU + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|Any CPU.Build.0 = CI|Any CPU + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|ARM.ActiveCfg = CI|ARM + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|ARM.Build.0 = CI|ARM + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|ARM64.ActiveCfg = CI|ARM64 + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|ARM64.Build.0 = CI|ARM64 + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|iPhone.ActiveCfg = CI|Any CPU + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|iPhone.Build.0 = CI|Any CPU + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|iPhoneSimulator.ActiveCfg = CI|Any CPU + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|iPhoneSimulator.Build.0 = CI|Any CPU + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|x64.ActiveCfg = CI|x64 + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|x64.Build.0 = CI|x64 + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|x86.ActiveCfg = CI|x86 + {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.CI|x86.Build.0 = CI|x86 {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {42252EE8-7E68-428F-972B-6D2DD3AA12CC}.Debug|ARM.ActiveCfg = Debug|Any CPU @@ -258,6 +293,27 @@ Global {E22A785B-E355-47C8-8736-18C067C2C67A}.AppStore|x86.ActiveCfg = Release|Any CPU {E22A785B-E355-47C8-8736-18C067C2C67A}.AppStore|x86.Build.0 = Release|Any CPU {E22A785B-E355-47C8-8736-18C067C2C67A}.AppStore|x86.Deploy.0 = Release|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|Any CPU.ActiveCfg = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|Any CPU.Build.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|Any CPU.Deploy.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|ARM.ActiveCfg = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|ARM.Build.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|ARM.Deploy.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|ARM64.ActiveCfg = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|ARM64.Build.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|ARM64.Deploy.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|iPhone.ActiveCfg = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|iPhone.Build.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|iPhone.Deploy.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|iPhoneSimulator.Build.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|iPhoneSimulator.Deploy.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|x64.ActiveCfg = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|x64.Build.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|x64.Deploy.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|x86.ActiveCfg = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|x86.Build.0 = Debug|Any CPU + {E22A785B-E355-47C8-8736-18C067C2C67A}.CI|x86.Deploy.0 = Debug|Any CPU {E22A785B-E355-47C8-8736-18C067C2C67A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E22A785B-E355-47C8-8736-18C067C2C67A}.Debug|Any CPU.Build.0 = Debug|Any CPU {E22A785B-E355-47C8-8736-18C067C2C67A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU @@ -349,6 +405,21 @@ Global {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.AppStore|x86.ActiveCfg = Release|x86 {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.AppStore|x86.Build.0 = Release|x86 {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.AppStore|x86.Deploy.0 = Release|x86 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|Any CPU.ActiveCfg = CI|x86 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|ARM.ActiveCfg = CI|ARM + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|ARM.Build.0 = CI|ARM + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|ARM.Deploy.0 = CI|ARM + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|ARM64.ActiveCfg = CI|ARM64 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|ARM64.Build.0 = CI|ARM64 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|ARM64.Deploy.0 = CI|ARM64 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|iPhone.ActiveCfg = CI|x86 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|iPhoneSimulator.ActiveCfg = CI|x86 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|x64.ActiveCfg = CI|x64 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|x64.Build.0 = CI|x64 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|x64.Deploy.0 = CI|x64 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|x86.ActiveCfg = CI|x86 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|x86.Build.0 = CI|x86 + {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.CI|x86.Deploy.0 = CI|x86 {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.Debug|Any CPU.ActiveCfg = Debug|x86 {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.Debug|ARM.ActiveCfg = Debug|ARM {B5011C5A-36F3-446A-9019-90FFB5FE99AA}.Debug|ARM.Build.0 = Debug|ARM diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f1382f0..7f34579 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,12 +1,12 @@ # Control which branches get CI triggers (defaults to all branches if this parameter is not set) trigger: -- master -- rel/* + - master + - rel/* # Specify the target branches for pull request builds pr: -- master -- rel/* + - master + - rel/* jobs: -- template: azure-windows-pipeline.yml \ No newline at end of file +- template: azure-windows-pipeline.yml diff --git a/azure-windows-pipeline.yml b/azure-windows-pipeline.yml index c5f9138..73070a8 100644 --- a/azure-windows-pipeline.yml +++ b/azure-windows-pipeline.yml @@ -7,7 +7,7 @@ jobs: vmImage: windows-2019 variables: - BuildConfiguration: Release + BuildConfiguration: CI ANDROID_NDK_HOME: C:\Microsoft\AndroidNDK64\android-ndk-r16b ANDROID_NDK_PATH: C:\Microsoft\AndroidNDK64\android-ndk-r16b AndroidNdkDirectory: C:\Microsoft\AndroidNDK64\android-ndk-r16b diff --git a/build/build.cake b/build/build.cake index 0c4e6a6..54663a5 100644 --- a/build/build.cake +++ b/build/build.cake @@ -17,8 +17,8 @@ var target = Argument("target", "Default"); // VERSIONS ////////////////////////////////////////////////////////////////////// -var gitVersioningVersion = "2.1.65"; -var inheritDocVersion = "1.1.1.1"; +var gitVersioningVersion = "3.1.91"; +var inheritDocVersion = "2.5.2"; ////////////////////////////////////////////////////////////////////// // VARIABLES @@ -150,7 +150,7 @@ Task("Build") { MaxCpuCount = 0 } - .SetConfiguration("Release") + .SetConfiguration("CI") .WithTarget("Restore"); MSBuild(Solution, buildSettings); @@ -162,7 +162,7 @@ Task("Build") { MaxCpuCount = 0 } - .SetConfiguration("Release") + .SetConfiguration("CI") .WithTarget("Build") .WithProperty("GenerateLibraryLayout", "true"); @@ -207,40 +207,12 @@ Task("Package") var buildSettings = new MSBuildSettings { MaxCpuCount = 0 } - .SetConfiguration("Release") + .SetConfiguration("CI") .WithTarget("Pack") .WithProperty("GenerateLibraryLayout", "true") .WithProperty("PackageOutputPath", nupkgDir); MSBuild(Solution, buildSettings); - - // Build and pack C++ packages - buildSettings = new MSBuildSettings - { - MaxCpuCount = 0 - } - .SetConfiguration("Native"); - - buildSettings.SetPlatformTarget(PlatformTarget.ARM); - MSBuild(Solution, buildSettings); - - buildSettings.SetPlatformTarget(PlatformTarget.x64); - MSBuild(Solution, buildSettings); - - buildSettings.SetPlatformTarget(PlatformTarget.x86); - MSBuild(Solution, buildSettings); - - var nuGetPackSettings = new NuGetPackSettings - { - OutputDirectory = nupkgDir, - Version = Version - }; - - var nuspecs = GetFiles("./*.nuspec"); - foreach (var nuspec in nuspecs) - { - NuGetPack(nuspec, nuGetPackSettings); - } }); diff --git a/global.json b/global.json index e6e7cdb..479d6cd 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,9 @@ { "msbuild-sdks": { - "MSBuild.Sdk.Extras": "2.0.43" + "MSBuild.Sdk.Extras": "2.0.54" + }, + "sdk": { + "version": "3.1.200", + "rollForward": "latestPatch" } -} \ No newline at end of file +} diff --git a/version.json b/version.json index ee65fed..bf872c9 100644 --- a/version.json +++ b/version.json @@ -1,11 +1,11 @@ { - "version": "6.0.0-build.{height}", + "version": "6.1.0-build.{height}", "publicReleaseRefSpec": [ "^refs/heads/master$", // we release out of master "^refs/heads/dev$", // we release out of dev "^refs/heads/rel/\\d+\\.\\d+\\.\\d+" // we also release branches starting with rel/N.N.N ], - "nugetPackageVersion":{ + "nugetPackageVersion": { "semVer": 2 }, "cloudBuild": {