Microsoft Graph for Office 365 - 用例:Microsoft Teams相关的交互

本篇我们介绍Microsoft Teams相关的内容。
在这里插入图片描述

Microsoft Teams介绍

Microsoft Teams是用于企业沟通协作的软件,可以即时消息,语音通话,在线会议等。它以团队为基础单位,每个团队都有一些团队成员和团队所有者,不同的频道,频道中会包含聊天消息、文件(默认存储在SharePoint)、应用和标签。应用和标签可以是OneNote的笔记本、Planner的计划和SharePoint的列表。
在这里插入图片描述

团队和(Office 365)组

每个团队都是组的一部分,但并不是每个组都有团队。事实上有些我们以为是团队的信息其实是组对象的一部分,如团队的名字,成员和所有者。当一个新的团队被创建时,其实也创建了一个组(当然,将团队添加到现有的组也是可以的)。

创建组需要Group.ReadWrite.All权限,应用程序权限或用户托管权限都行。

克隆示例代码

示例代码的地址为
https://github.com/microsoftgraph/contoso-airlines-teams-sample
读者可以参照上面readme.md文件的介绍去注册应用程序和编译项目,主要的代码在GraphService.cs这个文件中。个人觉得这个示例代码的场景很不错,因此直接用它做介绍。另外需要说明的是,请重新在应用程序注册那里创建一个应用程序,因为之前创建的时候我们选择的帐户类型是组织内的,后期无法修改为允许个人微软帐户的设置。

在示例中,会每天为这个虚拟的Contoso航空的每个航班创建一个团队,并在航班结束后对团队进行归档。
在这里插入图片描述

创建团队

在创建团队之前,我们需要团队成员的ID。通过用户的UPN名称可以获取用户的数据,包括ID:

String userId = (await HttpGet<User>($"/users/{upn}")).Id;

当我们向团队添加用户时,Graph需要知道指向该用户的完整URL:

String fullUrl = $"https://graph.microsoft.com/v1.0/users/{userId}";

向所有要添加的用户应用一下步骤。

List<string> ownerIds = await GetUserIds(ownerUpns);
List<string> memberIds = await GetUserIds(flight.crew);

有了这些用户列表,,现在我们可以根据输入参数 (如显示名、邮箱昵称、描述、可见性、所有者和成员等)创建一个组。

            Group group =
                (await HttpPost($"/groups",
                            new Group()
                            {
                                DisplayName = "Flight " + flight.number,
                                MailNickname = "flight" + GetTimestamp(),
                                Description = "Everything about flight " + flight.number,
                                Visibility = "Private",
                                Owners = ownerIds,
                                Members = memberIds,
                                GroupTypes = new string[] { "Unified" }, // same for all teams
                                MailEnabled = true,                      // same for all teams
                                SecurityEnabled = false,                 // same for all teams
                            }))
                .Deserialize<Group>();

创建成功会返回JSON格式的结果。

{
    "displayName":"Flight 157",
    "mailNickname":"flight157",
    "description":"Everything about flight 157",
    "visibility":"Private",
    "groupTypes":["Unified"],
    "mailEnabled":true,
    "securityEnabled":false,
    "members@odata.bind":[
        "https://graph.microsoft.com/v1.0/users/bec05f3d-a818-4b58-8c2e-2b4e74b0246d",
        "https://graph.microsoft.com/v1.0/users/ae67a4f4-2308-4522-9021-9f402ff0fba8",
        "https://graph.microsoft.com/v1.0/users/eab978dd-35d0-4885-8c46-891b7d618783",
        "https://graph.microsoft.com/v1.0/users/6a1272b5-f6fc-45c4-95fe-fe7c5a676133"
    ],
    "owners@odata.bind":[
        "https://graph.microsoft.com/v1.0/users/6a1272b5-f6fc-45c4-95fe-fe7c5a676133",
        "https://graph.microsoft.com/v1.0/users/eab978dd-35d0-4885-8c46-891b7d618783"
    ]
}

注意结果中members@data.bind部分的语法,它提供了对现有资源的引用,在本例中的话就是用户。
下一步就是基于这个组创建团队了。

await HttpPut($"/groups/{group.Id}/team",
    new Team()
    {
        GuestSettings = new TeamGuestSettings()
        {
            AllowCreateUpdateChannels = false,
            AllowDeleteChannels = false
        }
    },
retries: 3, retryDelay: 10);

如果以最简单的方式,本来是可以直接用PUT方法发送空的BODY到/groups/{group.Id}/team的,但我们想要更改一些默认的设置,比如我们不希望访客具有创建、更新和删除频道的权限,因此使用上面的代码,对属性进行了设置。

另外为什么要使用重试逻辑并延迟10秒呢?这是因为如果在组创建之后立即创建团队的话,由于一些必要的数据可能没有复制到所有的数据中心,我们会得到404的错误。

还有就是团队跟组的ID是一样的。

string teamId = group.Id; // always the same

频道

团队包含多个频道,团队的聊天消息就存在于这些频道中。频道中还有文件选项卡 (通常是链接到SharePoint) 和其他我们自行添加的选项卡。下面的代码示例我们创建一个用于飞行员沟通的频道。

Channel channel = (await HttpPost(
    $"/teams/{teamId}/channels",
    new Channel()
    {
        DisplayName = "Pilots",
        Description = "Discussion about flightpath, weather, etc."
    }
)).Deserialize<Channel>();

当我们发送POST请求到/teams/{teamId}/channels去创建频道时,Graph会返回被创建的频道,因此我们可以得到频道的ID。

选项卡

接下来我们向频道添加一个选项卡,该选项卡用于展示机场的地图。每个选项卡都有一个与之关联的应用,对于地图我们可以使用网站类型应用,即
com.microsoft.teamspace.tab.web

var appReference = $"https://graph.microsoft.com/v1.0/appCatalogs/teamsApps/{appid}";

创建选项卡我们可以使用如下的代码。

await HttpPost($"/teams/{teamId}/channels/{channel.Id}/tabs",
new TeamsTab()
{
    DisplayName = "Map",
    TeamsApp = appReference, // It's serialized as "teamsApp@odata.bind" : appReference
    Configuration = new TeamsTabConfiguration()
    {
        EntityId = null,
        ContentUrl = "https://www.bing.com/maps/embed?h=800&amp;w=800&amp;cp=47.640016~-122.13088799999998&amp;lvl=16&amp;typ=s&amp;sty=r&amp;src=SHELL&amp;FORM=MBEDV8",
        WebsiteUrl = "https://binged.it/2xjBS1R",
        RemoveUrl = null,
    }
});

选项卡实际上是HTML渲染出来的。在选项卡的配置部分,有四个选项卡的通用属性:

  • ContentURL - 要渲染的内容的URL
  • WebsiteUrl - 在用户点击访问网站按钮时要打开链接的URL
  • RemoveUrl - 在用户点击删除按钮时要打开链接的URL
  • EntityId - 字符串,表示应用的标识

更多关于选项卡的类型信息,访问下面的链接查看。
https://docs.microsoft.com/en-us/graph/teams-configuring-builtin-tabs

创建SharePoint列表

下面我们创建一个SharePoint列表用于记录航行过程中出现问题的乘客,并将它贴到一个选项卡上。
要使用SharePoint我们需要额外的权限:

  • Sites.ReadWrite.All
  • Sites.Manage.All

我们可以通过组获取到SharePoint网站。

var teamSite = await HttpGet<Site>($"/groups/{groupId}/sites/root",
retries: 3, retryDelay:30);

这里用重试逻辑和延迟的原因跟上面一样。
得到网站对象之后,我们可以创建一个新的SharePoint列表,这个列表有三个字段:
Name、SeatNumber和Notes

var list = (await HttpPost($"/sites/{teamSite.Id}/lists",
new SharePointList
{
    DisplayName = "Challenging Passengers",
    Columns = new List<ColumnDefinition>()
    {
        new ColumnDefinition
        {
            Name = "Name",
            Text = new TextColumn()
        },
        new ColumnDefinition
        {
            Name = "SeatNumber",
            Text = new TextColumn()
        },
        new ColumnDefinition
        {
            Name = "Notes",
            Text = new TextColumn()
        }
    }
}))
.Deserialize<SharePointList>();

列表创建好之后,我们添加一些示例数据。

await HttpPost($"/sites/{teamSite.Id}/lists/{list.Id}/items",
    challengingPassenger
    );

最后我们添加一个选项卡到之前创建的频道上。

await HttpPost($"/teams/{groupId}/channels/{channelId}/tabs",
    new TeamsTab
    {
        DisplayName = "Challenging Passengers",
        TeamsApp = appReference, // It's serialized as "teamsApp@odata.bind" : appReference
        Configuration = new TeamsTabConfiguration
        {
            ContentUrl = list.WebUrl,
            WebsiteUrl = list.WebUrl
        }
    });

到现在,我们就有了一个完整功能的团队了,有成员、有频道、还有选项卡。
在这里插入图片描述

应用

使用用户托管权限,我们也可以向团队添加应用。比如我们将SurveyMonkey这个应用加到团队。我们需要知道应用的ID,在Graph Explorer中使用类似下面的链接可以获取到。

<strong>GET</strong> https://graph.microsoft.com/beta/teams/{sampleTeam.id}/installedApps?$expand=teamsAppDefinition&amp;filter=teamsAppDefinition/displayName eq 'SurveyMonkey'

记得将团队的id换成我们自己的。
接下来用这个id在我们的新团队中安装SurveyMonkey应用。

await HttpPost($"/teams/{team.Id}/installedApps",
    "{ \"teamsApp@odata.bind\" : \"" + graphV1Endpoint + "/appCatalogs/teamsApps/" + teamsAppId + "\" }");

在这里插入图片描述

克隆团队

比起从无到有创建一个团队,我们还可以克隆现有的团队。对于管理员和终端用户来说,这可以改变我们创建的团队的体系结构而不需要更新代码,是个很好的方式。克隆团队也支持应用程序权限,试想我们可以直接做出一个安装好应用的团队是多么棒的体验。

在本文的示例中,对于每个航班都是结构相同的团队,因此我们理所当然地采用克隆操作。克隆的强大之处还在于我们可以选择想要克隆的部分,包括应用、设置、频道、选项卡和成员。这里我们选择应用、设置和频道。

var response = await HttpPostWithHeaders($"/teams/{flight.prototypeTeamId}/clone",
    new Clone()
    {
        displayName = "Flight 4" + flight.number,
        mailNickName = "flight" + GetTimestamp(),
        description = "Everything about flight " + flight.number,
        teamVisibilityType = "Private",
        partsToClone = "apps,settings,channels"
    });

克隆需要花费一些时间,因此我们这里创建一个异步的克隆请求,在回调中检查它的状态。响应的关键部分是Location,我们就是通过它来查询克隆是不是完成了,如果没完成则等待10秒然后再查询。

string operationUrl = response.Headers.Location.ToString();
for (; ; )
{
    TeamsAsyncOperation operation = await HttpGet<TeamsAsyncOperation>(operationUrl);
    if (operation.Status == AsyncOperationStatus.Failed)
        throw new Exception();

    if (operation.Status == AsyncOperationStatus.Succeeded)
    {
        teamId = operation.targetResourceId;
        break;
    }

    Thread.Sleep(10000); // wait 10 seconds between polls
}

由于我们在克隆时没有选择克隆成员关系,因此这里还需要添加一些成员。

// Add the crew to the team
foreach (string id in flight.crew)
{
    string payload = $"{{ '@odata.id': '{graphV1Endpoint}/users/{id}' }}";
    await HttpPost($"/groups/{teamId}/members/$ref", payload);
    if (upn == flight.captain)
        await HttpPost($"/groups/{teamId}/owners/$ref", payload);
}

归档

正如开篇说的,航班结束之后要对团队进行归档。归档操作会使团队变为只读状态并在团队列表中被隐藏。我们可以通过右下角的齿轮图标读取归档团队的内容。
在这里插入图片描述
跟克隆类似,归档也需要一些时间。

HttpResponse response = await HttpPostWithHeaders($"/teams/{teamId}/archive", "{}");
string operationUrl = response.Headers.Location.ToString();
for (; ; )
{
    var operation = await HttpGet<TeamsAsyncOperation>(operationUrl);

    if (operation.Status == AsyncOperationStatus.Failed)
        throw new Exception();

    if (operation.Status == AsyncOperationStatus.Succeeded)
        break;

    Thread.Sleep(10000); // wait 10 seconds between polls
}

更多关于Graph中Teams API的内容,访问下面的链接。
https://aka.ms/teamsgraph/v1

这就是今天要介绍的全部内容了,enjoy!

备注 Demo中的提示

#1 Could not find a part of the path ‘…contoso-airlines-teams\project\bin\roslyn\csc.exe’
[Solution]
https://stackoverflow.com/questions/32780315/could-not-find-a-part-of-the-path-bin-roslyn-csc-exe

#2 Code中有写死的Tenant,需要更改为自己的,格式为yourdomain.onmicrosoft.com

#3 Code中有写死的用户名,用于指定机长和工作人员,记得在Flight构造函数处修改为自己租户中存在的用户。

captain = $"aaa@{tenantName}";
            crew = new string[] {
                $"bbb@{tenantName}",
                $"ccc@{tenantName}",
            };
            this.admin = admin;

#4 如果要使用克隆功能的话,记得将代码中hard code的prototypeTeamId改为自己创建出来的team的。可以直接通过Graph浏览器获取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值