はつねの日記

Kinect, Windows 10 UWP, Windows Azure, IoT, 電子工作

Let's use Azure Communication Services from the client

この記事は「Azure Communication Servicesをクライアントから使おう - はつねの日記」の英語翻訳となります。
This article is an English translation of "https://hatsune.hatenablog.jp/entry/2020/09/29/004734".
azure.microsoft.com
I'm looking into various ways to use Azure Commucication Services, which was announced for preview release at Microsoft Ignite 2020, through WPF apps (.NET Core).

What is Azure Communication Services?

Azure Communication Services is a service on Azure that enables communication between devices with video, voice, chat and text messaging.

It uses the same reliability and security-conscious infrastructure as Microsoft Teams, and the cloud side can add features like Teams' communication features to the app without writing any specific code.

Starting in October 2020, you'll be able to communicate via SMS and capture phone numbers to make and receive calls, but as of now, chat (remember Teams' chat) and voice and video calls (remember Teams' video conferencing) are still available in preview.

Try Chat first.

Configure the cloud side

If you want to use Azure Communication Services, create "Communication Services" in Azure Portal.
f:id:hatsune_a:20200928231054p:plain
Just specify a subscription, resource group, and name the resource to quickly create it.
f:id:hatsune_a:20200928231242p:plain
Unfortunately, you can only select the location (location) in the US yet, but we hope to be able to specify the Japanese region when the GA.
Once the creation is complete, you'll be able to create a
Resource name.communication.azure.com
An "endpoint" called "Endpoint" and a "key" to access it will be generated.
From the app, connect to Azure Communication Services with the endpoint + key.

Create the client side.

Now that we have the cloud side ready, let's create a client app. Sample apps and sample code are all examples of web apps and it's unknown how far we can go with just a Windows app, but let's try to do it as far as we can.

New Projector and Creation in WPF App (.NET Core)

Launch Visual Studio 2019 and create a new app from a WPF app (.NET Core) template. NET Core should be 3.1.

The main reason we're using .NET Core instead of .NET Framework is that we want to use a C# 8.0 asynchronous stream called await foreach. I'll explain why we want to use it later.

C# 8.0 is compatible with .NET Standard 2.1, so with the .NET Framework, there is no .NET Standard 2.1 implementation version, and .NET Core must be 3.0 or higher.

Let's nuget!

Once the new project is created, add the SDK in nuget.
f:id:hatsune_a:20200928233503p:plain
Check the Include Preview Release checkbox to add the following two SDKs

  • Azure.Communication.Administration
  • Azure.Communication.Chat

The SDK itself can be used without C# 8.0 and has been verified to work with .NET Framework 4.6.2.

Writing Code (Part 1) - Getting an access token

Get the resource name and key from Azure Portal and store them under the names ResourceName and Key.

private async Task GetAccessTokenAsync()
{
    var ConnectionString = $"endpoint=https://{this.ResourceName}.communication.azure.com/;accesskey={this.Key}";
    this.Client = new CommunicationIdentityClient(ConnectionString);
    var userResponse = await this.Client.CreateUserAsync();
    this.User = userResponse.Value;
    var tokenResponse = await this.Client.IssueTokenAsync(this.User, scopes: new[] { CommunicationTokenScope.Chat });
    this.AccessToken = tokenResponse.Value.Token;
    var expiresOn = tokenResponse.Value.ExpiresOn;

    System.Diagnostics.Debug.WriteLine($"Created a user with ID: {this.User.Id}");
    System.Diagnostics.Debug.WriteLine($"Issued a token with 'chat' scope that expires at {expiresOn}:");
    System.Diagnostics.Debug.WriteLine(this.AccessToken);
}

"Azure.Communication.Administration.CommunicationIdentityClient" with the connection string.
Then execute "CreateUserAsync" method to create the connection user.
Then, specify the connecting user and chat in the "IssueTokenAsync" method to get the access talk.

Writing Code (Part 2) - Creating Threads
var chatClient = new ChatClient(new Uri($"https://{this.ResourceName}.communication.azure.com"), new CommunicationUserCredential(this.AccessToken));
var chatThreadMember = new ChatThreadMember(this.User)
{
    DisplayName = "UserDisplayName",
    ShareHistoryTime = DateTime.UtcNow,
};
this.Thread = await chatClient.CreateChatThreadAsync(groupId, new[] { chatThreadMember });
this.ThreadId = this.Thread.Id;

Once you have obtained the access token, new "Azure.Communication.Chat.ChatClient" specifying the access token and the endpoint of Azure.Connection.Services.
Then new "Azure.Communication.Chat.ChatThreadMember" specifying the user at the time the access token was created.
Finally, execute the CreateChatThreadAsync method and you can create a new conversation thread.

Writing Code (Part 2´) - Getting the Thread

If there is already a thread, you can also get the thread by specifying the thread ID.

var item = await chatClient.GetChatThreadAsync(this.ThreadId);
Writing Code (Part 3) - Send to Chat

You can send a message to a chat thread by executing the SendMessageAsync method of the chat thread.

internal async Task SendAsync(string content)
{
    var priority = ChatMessagePriority.Normal;
    var senderDisplayName = "sender name";

    var sendChatMessageResult = await this.Thread.SendMessageAsync(content, priority, senderDisplayName);
    System.Diagnostics.Debug.WriteLine($"Send Message: {content}");
}
Writing Code (Part 4) - Receiving from Chat

Receiving messages from the chat is a bit tricky.
I'm currently trying and running a receiving loop in another thread as follows.

this.TokenSource = new CancellationTokenSource();
Task.Factory.StartNew(async () =>
{
    DateTimeOffset? startTime = DateTimeOffset.MinValue;
    while (!this.TokenSource.IsCancellationRequested)
    {
        try
        {
            var allMessages = this.Thread.GetMessagesAsync(startTime, this.TokenSource.Token);
            await foreach (var message in allMessages)
            {
                System.Diagnostics.Debug.WriteLine($"{message.Id}:{message.Sender.Id}:{message.Content}");
                startTime = (startTime < message.CreatedOn ? message.CreatedOn : startTime).GetValueOrDefault().AddSeconds(1);
            }
        }
        catch { }
    }
});

You can receive the message by executing the GetMessagesAsync method on the thread.
The result is asynchronous streaming, so we use the C# 8.0 writing style called "await foreach (var message in allMessages)" to retrieve the received messages.
When the GetMessagesAsync method is executed, the value of the first parameter- is very important.
You can specify which time you want to get the messages after the first parameter, but you can't specify the time, so you can't specify the next message after the last received message. If you specify the latest time when you got last message, you get the last message.
So we use the following method to specify last time + 1 second.

startTime = (startTime < message.CreatedOn ? message.CreatedOn : startTime).GetValueOrDefault().AddSeconds(1);

However, there is a danger that there will be an undoing when the message arrives between the last time and +1 second.

What we've learned so far (what we don't know).

I don't know how to get the differences since the last time I received a chat message (they don't exist).
I still don't know what to specify between apps to send and receive messages in the same thread between client apps.
If you look at the Azure.Communication.Services workflow, the diagram shows that even client apps have to share information to specify the same thread between apps using AppService and so on.
f:id:hatsune_a:20200929083246p:plain
Maybe it might be difficult to achieve the cloud side with just Azure.Communication.Services, but I'll look into it some more.