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..5fa1a3c --- /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 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 => (Microsoft.Graph.QueryOption)option)?.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..8af7159 --- /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 sealed class QueryOption + { + /// + public string Name { get; set; } + + /// + public string Value { get; set; } + + /// + /// Implicit conversion for to . + /// + /// query option to convert. + public static implicit operator Microsoft.Graph.QueryOption(QueryOption option) + { + return new Microsoft.Graph.QueryOption(option.Name, option.Value); + } + } +} diff --git a/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj b/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj index 6cd7fde..f44f769 100644 --- a/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj +++ b/Microsoft.Toolkit.Graph.Controls/Microsoft.Toolkit.Graph.Controls.csproj @@ -15,6 +15,7 @@ UWP Toolkit Windows Controls MSAL Microsoft Graph AadLogin ProfileCard Person PeoplePicker Login false true + 8.0 Debug;Release;CI AnyCPU;ARM;ARM64;x64;x86 @@ -34,8 +35,8 @@ - - + + diff --git a/Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs b/Microsoft.Toolkit.Graph/Extensions/GraphExtensions.cs index 8e8a433..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; diff --git a/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj b/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj index 8e49be7..5164e15 100644 --- a/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj +++ b/Microsoft.Toolkit.Graph/Microsoft.Toolkit.Graph.csproj @@ -21,8 +21,8 @@ - - + + diff --git a/Microsoft.Toolkit.Graph/Providers/MockProvider.cs b/Microsoft.Toolkit.Graph/Providers/MockProvider.cs index 863c3dd..a0d54ec 100644 --- a/Microsoft.Toolkit.Graph/Providers/MockProvider.cs +++ b/Microsoft.Toolkit.Graph/Providers/MockProvider.cs @@ -21,6 +21,8 @@ namespace Microsoft.Toolkit.Graph.Providers /// public class MockProvider : IProvider { + private const string GRAPH_PROXY_URL = "https://proxy.apisandbox.msdn.microsoft.com/svc?url="; + private ProviderState _state = ProviderState.Loading; /// @@ -42,22 +44,15 @@ private set /// public GraphServiceClient Graph => new GraphServiceClient( - "https://proxy.apisandbox.msdn.microsoft.com/svc?url=" + HttpUtility.HtmlEncode("https://graph.microsoft.com/beta/"), - new DelegateAuthenticationProvider((requestMessage) => - { - //// Temporary Workaround for https://github.com/microsoftgraph/msgraph-sdk-dotnet-core/issues/59 - //// ------------------------ - var requestUri = requestMessage.RequestUri.ToString(); - var index = requestUri.IndexOf("&"); - if (index >= 0) - { - requestMessage.RequestUri = new Uri(requestUri.Remove(index, 1).Insert(index, "?")); - } - - //// End Workaround - - return this.AuthenticateRequestAsync(requestMessage); - })); + 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; diff --git a/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs b/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs index ba03df9..f585ef5 100644 --- a/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs +++ b/Microsoft.Toolkit.Graph/Providers/ProviderManager.cs @@ -3,6 +3,10 @@ // 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; namespace Microsoft.Toolkit.Graph.Providers { @@ -14,7 +18,7 @@ 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. @@ -31,6 +35,9 @@ public class ProviderManager /// public event EventHandler ProviderUpdated; + /// + public event PropertyChangedEventHandler PropertyChanged; + private IProvider _provider; /// @@ -58,6 +65,8 @@ public IProvider GlobalProvider } ProviderUpdated?.Invoke(this, new ProviderUpdatedEventArgs(ProviderManagerChangedState.ProviderChanged)); + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(GlobalProvider))); } } diff --git a/SampleTest/Assets/FileIcon.png b/SampleTest/Assets/FileIcon.png new file mode 100644 index 0000000..0435822 Binary files /dev/null and b/SampleTest/Assets/FileIcon.png differ diff --git a/SampleTest/MainPage.xaml b/SampleTest/MainPage.xaml index 986f831..54dc422 100644 --- a/SampleTest/MainPage.xaml +++ b/SampleTest/MainPage.xaml @@ -2,16 +2,19 @@ x:Class="SampleTest.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="using:SampleTest" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:local="using:SampleTest" xmlns:wgt="using:Microsoft.Toolkit.Graph.Controls" xmlns:graph="using:Microsoft.Graph" + xmlns:global="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:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"> + xmlns:controlsKeepThis="using:Microsoft.Toolkit.Uwp.UI.Controls"> - + - - - - - Picked People: - - - - - - - - - + + + + + + + + + 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/SampleTest/MainPage.xaml.cs b/SampleTest/MainPage.xaml.cs index 2689205..428741f 100644 --- a/SampleTest/MainPage.xaml.cs +++ b/SampleTest/MainPage.xaml.cs @@ -2,6 +2,11 @@ // 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; +using System.Text.RegularExpressions; using Windows.UI.Xaml.Controls; namespace SampleTest @@ -11,9 +16,44 @@ namespace SampleTest /// 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 MainPage() { this.InitializeComponent(); } + + 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/SampleTest/SampleTest.csproj b/SampleTest/SampleTest.csproj index e98c1d1..4a1c7d9 100644 --- a/SampleTest/SampleTest.csproj +++ b/SampleTest/SampleTest.csproj @@ -130,6 +130,7 @@ +