{"id":4818,"date":"2020-06-16T07:33:57","date_gmt":"2020-06-16T14:33:57","guid":{"rendered":"https:\/\/officedevblogs.wpengine.com\/?p=4818"},"modified":"2020-06-16T07:33:57","modified_gmt":"2020-06-16T14:33:57","slug":"building-a-windows-presentation-foundation-wpf-microsoft-teams-app","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/building-a-windows-presentation-foundation-wpf-microsoft-teams-app\/","title":{"rendered":"Building a Windows Presentation Foundation (WPF) Microsoft Teams App"},"content":{"rendered":"<p>Author: Alessandro Avila, a Microsoft Apps Consultant developer<\/p>\n<p><em>Today\u2019s guest blog post comes from Alessandro Avila, a Microsoft Apps Consultant developer, and his experiences with app development, cloud migration, and overall digital transformation at ABB \u2013 a global leader in robotics, power, heavy electrical equipment, and automation technology based in Europe.<\/em><\/p>\n<h2>Introduction<\/h2>\n<p>Teams is the hub for teamwork in Microsoft 365 \u2013 bringing together people, chat, content, calls and apps in a central space for collaboration and productivity. Enterprises across the globe rely on the rich features provided by the platform to build powerful workspaces and provide valuable feedback to the Teams product group to further enhance those experiences. This post covers my recent experience with an enterprise customer, ABB \u2013 a global digital transformation leader, helping their marketing organization through their Teams adoption, as well as custom app solution development to address some of their process automation needs.<\/p>\n<p>ABB\u2019s marketing organization was an early adopter of Teams \u2013 and heavily used the Microsoft Planner app integration in Teams to organize activities and tasks in a systematic and structured fashion and assign them to staff across different divisions and geos. For those unfamiliar with the app, Microsoft Planner is a project management application that allows teams to create, assign and organize work visually to better facilitate teamwork and monitor progress. With the high usage of Teams and the Planner integration, our customer needed a solution that would be able to automate the capability of copying Planner plans (including tasks\/activities\/content) in bulk across different Teams teams \u2013 eliminating the time-consuming manual copy process that it was performing currently.<\/p>\n<p>In this post we\u2019ll walk you through our custom app solution demo we built for ABB to address their need and requirements. We\u2019ll cover knowledge areas spanning across Teams app development, Graph APIs, C#, and Azure.<\/p>\n<h3>Planner app integration in Teams<\/h3>\n<p>Teams extends content and web services through \u2018<em>apps\u2019<\/em> that enable one or more capabilities teams can use in their daily work for collaboration and productivity. You can take advantage of the many Microsoft and 3<sup>rd<\/sup>-party app integrations available in the Teams store or you can even build your own custom app for your organization or widely-available to the public.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4831\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-different-apps-available-in-Teams-for-addition-as-a-tab-in-a-Teams-channel-914x1024.png\" alt=\"Screenshot of different apps available in Teams for addition as a tab in a Teams channel\" width=\"450\" height=\"504\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-different-apps-available-in-Teams-for-addition-as-a-tab-in-a-Teams-channel-914x1024.png 914w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-different-apps-available-in-Teams-for-addition-as-a-tab-in-a-Teams-channel-268x300.png 268w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-different-apps-available-in-Teams-for-addition-as-a-tab-in-a-Teams-channel-768x861.png 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-different-apps-available-in-Teams-for-addition-as-a-tab-in-a-Teams-channel-1370x1536.png 1370w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-different-apps-available-in-Teams-for-addition-as-a-tab-in-a-Teams-channel.png 1697w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>With the Microsoft Planner app integration in Teams, teams can create custom plans, create tasks, and assign work item all within a Teams channel. Tasks are organized in Buckets (Kanban) by default and enhanced with contextual details (Task progress, Start Date, Due Date, Checklist) that fosters teamwork, collaboration, and productivity.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4832\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-Planner-plan\u2019s-boards-view-1024x459.png\" alt=\"Screenshot of Planner plan\u2019s boards view\" width=\"450\" height=\"202\" \/> <img decoding=\"async\" class=\"alignleft wp-image-4833\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-Planner\u2019s-task-details-pane-1005x1024.png\" alt=\"Screenshot of Planner\u2019s task details pane\" width=\"450\" height=\"459\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>The Planner app integration in Teams, provides the ability to copy an existing Planner plan as a tab. However, there are a couple limitations which impact the user experience for these migration scenarios:<\/p>\n<ol>\n<li>Provides only an \u2018<em>intra-team\u2019<\/em> copy (i.e., you can create a copy of an existing plan only within the same Team where the source plan resides);<\/li>\n<li>By selecting an existing plan and adding it as a tab, the copied plan is actually <strong>a pointer<\/strong> to the previous one: any changes made to the newly created plan would affect the source plan; in other words, it\u2019s a <strong>shallow copy<\/strong>, not a <strong>deep copy<\/strong>.<\/li>\n<\/ol>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4830\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-copy-plan-option-1024x896.png\" alt=\"Screenshot of copy plan option\" width=\"450\" height=\"394\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-copy-plan-option-1024x896.png 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-copy-plan-option-300x263.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-copy-plan-option-768x672.png 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Screenshot-of-copy-plan-option.png 1477w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>To address these limitations that was impacting our client\u2019s experience, we built a custom app solution (\u201cTeamsApp\u201d) which enables an <em>inter-Teams<\/em> copy of Planner Plans, with <strong>full copy<\/strong> of underlying Buckets and Tasks and gives IT Admins a tool that can be used to set up tasks within a new team.<\/p>\n<h3>Prerequisites<\/h3>\n<p>Before we dive into the demo, we\u2019ll walk through some of the prerequisites you\u2019ll need to complete.<\/p>\n<p>We\u2019ll be leveraging Microsoft Graph APIs, so a<strong> Microsoft 365 tenant<\/strong> is needed. We recommend using a developer tenancy for this and you can get one if you register for the Microsoft 365 Developer Program <a href=\"https:\/\/developer.microsoft.com\/en-us\/microsoft-365\/dev-program\">here<\/a>.\u00a0 An <strong>Azure AD App<\/strong> needs to be registered in the Azure Portal, as well.<\/p>\n<p>Having these set up will enable communication between \u2018TeamsApp\u2019 and the Microsoft Identity Platform of the specific tenant and establishes the information that it uses to get OAuth Access Tokens. Through the registered Azure AD App, the \u2018TeamsApp\u2019 can access Microsoft 365 organization resources.<\/p>\n<h4>Azure AD App Registration<\/h4>\n<p><em>(For a detailed description, follow the steps explained in the <a href=\"https:\/\/docs.microsoft.com\/en-us\/graph\/auth-register-app-v2\">Microsoft Docs<\/a><\/em><em>).<\/em><\/p>\n<ol>\n<li>Sign into the Azure Portal with a Work or School \/ Personal Account.<\/li>\n<li>If the specified account gives access to more than one tenant, select the desired tenant.<\/li>\n<li>From the left-menu: Azure Active Directory -&gt; App registrations -&gt; New registration.<\/li>\n<\/ol>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4827\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-18-View-of-final-content-migration-1024x343.png\" alt=\"\" width=\"450\" height=\"151\" \/> <img decoding=\"async\" class=\"alignleft wp-image-4835\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Azure-Registration-Window-1024x482.jpg\" alt=\"Azure Registration Window\" width=\"450\" height=\"212\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Azure-Registration-Window-1024x482.jpg 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Azure-Registration-Window-300x141.jpg 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Azure-Registration-Window-768x362.jpg 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Azure-Registration-Window.jpg 1246w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>4. Fill in the mandatory fields:<\/p>\n<p style=\"padding-left: 40px\">a. <strong>Name<\/strong>: the app name (e.g.: \u201c<em>TeamsApp<\/em>\u201d).<\/p>\n<p style=\"padding-left: 40px\">b. <strong>Supported account types<\/strong>: it depends on which accounts will access TeamsApp and Graph APIs through the AD v2.0 application. You can choose between:<\/p>\n<p style=\"padding-left: 80px\">i. (default) <em>Accounts in this organizational directory<\/em> &lt;yourOrgName&gt;: this option maps to Azure AD only single tenant; select this option if you\u2019re building a line-of-business application;<\/p>\n<p style=\"padding-left: 80px\">ii. <em>Accounts in any organizational directory<\/em>: this option maps to Azure AD only multi-tenant;<\/p>\n<p style=\"padding-left: 80px\">iii. Accounts<em> in any organizational directory and personal Microsoft accounts<\/em> (e.g. Skype, Xbox, Outlook.com); this option maps to Azure AD multi-tenant and personal Microsoft accounts; this option targets the widest set of customers. Note: This was the option we used.<\/p>\n<p style=\"padding-left: 40px\">c. <strong>Redirect URI<\/strong>: it\u2019s URI AD will return the authentication response to, after successfully authenticating the user.<\/p>\n<p style=\"padding-left: 80px\">i. Select \u201c<em>Public client (mobile &amp; desktop)<\/em>\u201d from the first dropdown;<\/p>\n<p style=\"padding-left: 80px\">ii. Set the value to \u201c<em>urn:ietf:wg:oauth:2.0:oob\u201d<\/em> in the textbox.<\/p>\n<p>5. Click on <strong>Register<\/strong>.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4834\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/App-registration-window.png\" alt=\"App registration window\" width=\"450\" height=\"404\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/App-registration-window.png 921w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/App-registration-window-300x269.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/App-registration-window-768x690.png 768w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>6. On the application page, take note of the <strong>Application (client) ID<\/strong> field, we\u2019ll need it when it comes to properly setting the authentication properties in our app;<\/p>\n<p>7. Since we are using <strong>NET<\/strong> as .NET authentication library (v4.9.0) to get tokens from the AD identity platform, we need to get back to our application and change the <strong>Redirect URI<\/strong> field.<\/p>\n<p>8. From the <strong>Suggested Redirect URIs<\/strong> section, select the URI in the form:<\/p>\n<p>msal&lt;appclientID&gt;:\/\/auth (MSAL only)<\/p>\n<p>9. Take note of the new <strong>Redirect URI<\/strong> as well.<\/p>\n<h4>App Permissions<\/h4>\n<p>Since \u2018TeamsApp\u2019 integrates with Microsoft Identity Platform, it follows an authorization model that gives users control over how data can be accessed; see <a href=\"https:\/\/docs.microsoft.com\/en-us\/azure\/active-directory\/develop\/v2-permissions-and-consent\">Microsoft Docs<\/a> for more information about permissions and consent.<\/p>\n<p>Specifying the app\u2019s permissions (or scopes), we have fine-grained control over data our APIs can access to perform their functions.<\/p>\n<p>For our purposes, we need to setup two permissions:<\/p>\n<ol>\n<li>Read.All: Sign in and read user profile<\/li>\n<li>ReadWrite.All: Read and write all groups<\/li>\n<\/ol>\n<p>Here are the permissions already granted for the demo AD App:<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4828\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-1024x399.jpg\" alt=\"Permissions screen\" width=\"450\" height=\"175\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-1024x399.jpg 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-300x117.jpg 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-768x299.jpg 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen.jpg 1473w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>Let\u2019s add a new permission:<\/p>\n<ol>\n<li>Click on \u201cAdd a permission\u201d button;<\/li>\n<li>Select \u201cMicrosoft Graph\u201d as Microsoft APIs, then Delegated permissions as type of permissions, since \u2018TeamsApp\u2019 needs to access the API as the signed-in user.<\/li>\n<li>Look for \u201cGroup.ReadWrite.All\u201d, check it and click on Add permissions button.<\/li>\n<\/ol>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4829\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-1024x864.png\" alt=\"Permissions screen\" width=\"450\" height=\"380\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-1024x864.png 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-300x253.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-768x648.png 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-1536x1297.png 1536w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Permissions-screen-2048x1729.png 2048w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>4. Since Admin consent is required for this permission, click on \u201cGrant Admin consent for Contoso\u201d.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4836\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Consent-screen.png\" alt=\"Consent screen\" width=\"450\" height=\"14\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Consent-screen.png 1000w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Consent-screen-300x9.png 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Consent-screen-768x24.png 768w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<h2>The \u2018TeamsApp\u2019 Solution<\/h2>\n<h3>Solution Architecture<\/h3>\n<p>We developed \u2018TeamsApp\u2019 with Visual Studio, and it contains the following two projects:<\/p>\n<ul>\n<li><strong>TeamsApp.Lib<\/strong>: Class library serves as the \u201ccore\u201d solution and provides the building blocks of the architecture: access to APIs, Authentication Layer, Factory Pattern, Helpers, Extension Methods, Models and Settings<\/li>\n<\/ul>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4819\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-10-Project-structure.jpg\" alt=\"Project structure\" width=\"450\" height=\"392\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-10-Project-structure.jpg 570w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-10-Project-structure-300x261.jpg 300w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<ul>\n<li><strong>TeamsApp.WPF<\/strong>: The main windows application built with the WPF (Windows Presentation Foundation) framework; one main window (<strong>MainWindow<\/strong>) from where all operations will be performed<\/li>\n<\/ul>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4820\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-11-Project-structure.jpg\" alt=\"Project structure\" width=\"450\" height=\"310\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-11-Project-structure.jpg 569w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-11-Project-structure-300x207.jpg 300w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>Either two projects are built using <strong>.NET Framework 4.7.2<\/strong> (even though some <em>C# 8.0 Preview 2<\/em> features are used \u2013 the <em>inline using<\/em>, to mention the most recurrent).<\/p>\n<h3>Authentication and access<\/h3>\n<p>To interact with Microsoft Graph REST APIs, a JSON-like access token is required and is provided by the Microsoft Identity Platform (Azure AD v2.0). It\u2019s needed to validate the caller (the Azure AD App) and check that it has the proper permissions to access Graph resources (each API endpoint will expose different resources, each of them requires different permissions, as you can discover in more details through this <a href=\"https:\/\/docs.microsoft.com\/en-us\/graph\/use-the-api\">link<\/a>).<\/p>\n<p>\u2018TeamsApp\u2019 uses <strong>MSAL.NET (Microsoft Authentication Library for .NET)<\/strong> to easily get tokens and allow users to sign in with their work or school accounts (it depends on how you configured the Azure AD App in <a href=\"#_Azure_AD_App\">Azure AD App Registration<\/a> (4.b).<\/p>\n<p>The class responsible for authentication is AuthenticationHelper (Helpers\\AuthenticationHelper.cs in TeamsApp.Lib). This class implements the Singleton design pattern to expose a single instance of an authentication provider that\u2019d be used for the entire application\u2019execution; <strong>ClientID<\/strong> and <strong>ReturnUrl <\/strong>must be set in the <strong>App.xaml<\/strong> file (TeamsApp.WPF) before running the application, with the values provided during the App registration in the Azure Portal (see <a href=\"#_Azure_AD_App\">Azure AD App Registration<\/a> for details); replace placeholders in the tags <strong>ida:ClientID<\/strong> and <strong>ida:ReturnUrl<\/strong>:<\/p>\n<p>&lt;system:String x:Key=&#8221;ida:ClientID&#8221;&gt;[put your ClientID here]&lt;\/system:String&gt;\n&lt;system:String x:Key=&#8221;ida:ReturnUrl&#8221;&gt;[put your ReplaceUrl here]&lt;\/system:String&gt;<\/p>\n<h3>Graph APIs<\/h3>\n<p>Microsoft Graph exposes several endpoints to access Microsoft Cloud service resources. After getting the authentication token (see previous section), you can access resources through HTTP calls that look like the following:<\/p>\n<p>{HTTP method} <a href=\"https:\/\/graph.microsoft.com\/%7bversion%7d\/%7bresource%7d?%7bquery-parameters%7d\">https:\/\/graph.microsoft.com\/{version}\/{resource}?{query-parameters}<\/a><\/p>\n<p>The components of a requests are:<\/p>\n<ul>\n<li><strong>{HTTP method}<\/strong>: the method used to perform the request to Microsoft Graph; the API supports the following methods: GET, POST, PATCH, PUT, DELETE;<\/li>\n<li><strong>{version}<\/strong>: the version of the Microsoft Graph API your application is using; TeamsApp lets you to choose between the following two versions (see <strong>O365Settings<\/strong> static class):\n<ul>\n<li><strong>0<\/strong>: includes GA (Generally Available APIs);<\/li>\n<li><strong>beta<\/strong>: preview APIs, this is the default in TeamsApp (feel free to change it).<\/li>\n<\/ul>\n<\/li>\n<li><strong>{resource}<\/strong>: the resource we are performing a request to;<\/li>\n<li><strong>{query-parameters}<\/strong>: optional REST parameters.<\/li>\n<\/ul>\n<p>In the <em>TeamsApp.Lib\\Api<\/em> folder the <strong>API<\/strong> abstract class represents the root type for managing Graph API calls. The HttpClientApi class inherits from API since it\u2019s a specialization of it. Each HttClientApi <em>sub-type<\/em> (BucketApi, PlannerApi, TaskApi, etc.) exposes properties and helper methods (GET\/POST, mainly) to access resources of that specific sub-type. Each sub-type inherits from HttpClientApi class.<\/p>\n<p>\u2018TeamsApp\u2019 provides a <strong>caching abstraction layer<\/strong> (Factory\\TeamsFactory) to quickly get the correct API type from the list of more frequently accessed APIs; the solution leverages on the <strong>CacheManager.Core<\/strong> NuGet package.<\/p>\n<h3>Logging<\/h3>\n<p>The logging layer in \u2018TeamsApp\u2019 is based on a telemetry library called Common.Diagnostics, a brand-new .NET solution built upon System.Diagnostics for categorized and multi-layered tracing by means of listeners \u2013 supported by .NET framework 4.6.2+ and .NET Core 3.0+. Under the \u2018Logs\u2019 folder, you\u2019ll find an early version of its core class (TraceManager.cs). For more information about how to use the whole library, have access to the documentation and .nuget packages, please visit the official repo at <a href=\"https:\/\/github.com\/diginsight\/telemetry\">https:\/\/github.com\/diginsight\/telemetry<\/a>.<\/p>\n<h2>Demo<\/h2>\n<p>Now we\u2019ll show how to use the \u2018TeamsApp application to perform a copy of a Planner from a Team to another.<\/p>\n<p><strong>Source Team:<\/strong> Contoso<\/p>\n<p><strong>Destination Team:<\/strong> Sales and Marketing<\/p>\n<p>Let\u2019s start by creating a new Planner tab called <strong>Planner.Source<\/strong>, in the <strong>General<\/strong> channel:<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4821\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-12-Planner.Source-tab-1024x307.jpg\" alt=\" Planner.Source tab\" width=\"450\" height=\"135\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-12-Planner.Source-tab-1024x307.jpg 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-12-Planner.Source-tab-300x90.jpg 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-12-Planner.Source-tab-768x231.jpg 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-12-Planner.Source-tab.jpg 1472w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p>We\u2019ll be providing the Planner with the following content:<\/p>\n<ul>\n<li>Two buckets: \u201cTo Do\u201d and \u201cAnother bucket\u201d<\/li>\n<li>Two tasks for each bucket (named progressively, as you can see from the picture above)<\/li>\n<\/ul>\n<p>An empty destination Planner (<strong>Planner.Dest<\/strong>) has been created in the Sales and Marketing Team:<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4822\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-13-Planner.Dest-tab-1024x329.jpg\" alt=\"Planner.Dest tab\" width=\"450\" height=\"145\" srcset=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-13-Planner.Dest-tab-1024x329.jpg 1024w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-13-Planner.Dest-tab-300x96.jpg 300w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-13-Planner.Dest-tab-768x247.jpg 768w, https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-13-Planner.Dest-tab.jpg 1472w\" sizes=\"(max-width: 450px) 100vw, 450px\" \/><\/p>\n<p><strong>Step 1:<\/strong> Click <strong>Connect<\/strong> button and provide user credentials:<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4823\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-14-Main-page-of-\u2018TeamsApp\u2019-app-1024x567.jpg\" alt=\"Main page of \u2018TeamsApp\u2019 app\" width=\"450\" height=\"249\" \/><\/p>\n<p><strong>Step 2:<\/strong> Click <strong>Load Teams<\/strong> and select either <em>Source Team<\/em> or <em>Destination Team<\/em> from related dropdowns. The user will get all available Planners from selected teams.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4824\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-15-Load-teams-button-within-the-\u2018TeamsApp\u2019-1024x567.png\" alt=\" Load teams button within the \u2018TeamsApp\u2019\" width=\"450\" height=\"249\" \/><\/p>\n<p><strong>Step 3:<\/strong> Select <strong>Planner.Source<\/strong> as source planner and <strong>Planner.Dest<\/strong> as destination planner.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4825\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-16-Source-planner-and-destination-planner-inputs-1024x567.png\" alt=\"Source planner and destination planner inputs\" width=\"450\" height=\"249\" \/><\/p>\n<p><strong>Step 4:<\/strong> Click on <strong>COPY<\/strong> button and wait for the planner copy to complete.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4826\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-17-Completion-of-the-copy-1024x566.png\" alt=\"Completion of the copy\" width=\"450\" height=\"249\" \/><\/p>\n<p>As you can see here, the <strong>Planner.Dest<\/strong>\u2019s tab has all of the content migrated into the relevant buckets after copy.<\/p>\n<p><img decoding=\"async\" class=\"alignleft wp-image-4827\" src=\"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-content\/uploads\/sites\/73\/2020\/06\/Image-18-View-of-final-content-migration-1024x343.png\" alt=\"View of final content migration\" width=\"650\" height=\"218\" \/><\/p>\n<h2>GitHub<\/h2>\n<p>Here\u2019s the link to the official \u2018TeamsApp\u2019 GitHub repo: <a href=\"https:\/\/github.com\/alessandro-avila\/copy-planner-microsoft-teams-application\">https:\/\/github.com\/alessandro-avila\/copy-planner-microsoft-teams-application<\/a>.<\/p>\n<h2>Acknowledgements<\/h2>\n<p>My heartfelt thanks to Dario Airoldi for supporting me during the development of this application. His \u2018never give up\u2019 approach and determination helped exceed our customer\u2019s expectations and provided useful insights via his Common.Diagnostics telemetry library (see Logs section). That greatly helped me in troubleshooting the first application runs with the customer.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post covers Alessandro Avila&#8217;s recent experience with an enterprise customer, ABB \u2013 a global digital transformation leader, helping their marketing organization through their Teams adoption, as well as custom app solution development to address some of their process automation needs.<\/p>\n","protected":false},"author":69076,"featured_media":25159,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[128],"tags":[61,27],"class_list":["post-4818","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-microsoft-teams","tag-azure","tag-windows"],"acf":[],"blog_post_summary":"<p>This post covers Alessandro Avila&#8217;s recent experience with an enterprise customer, ABB \u2013 a global digital transformation leader, helping their marketing organization through their Teams adoption, as well as custom app solution development to address some of their process automation needs.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/4818","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/users\/69076"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/comments?post=4818"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/posts\/4818\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media\/25159"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/media?parent=4818"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/categories?post=4818"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/microsoft365dev\/wp-json\/wp\/v2\/tags?post=4818"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}