背景
现在微软主推的就是Azure平台的产品,这篇文档的内容就是如何将Azure的Bot framework使用到Xamarin中。写这篇文档的原因也很简单,我support Xamarin,并且想学习Bot,之后会陆陆续续再有许多文章结合各种技术,比如如何在Xamarin中使用Azure Cognitive Service,等等。
用到的技术
- Xamarin (WebView)
- Bot Framework (Web Chat)
说明
阅读这篇文档首先要有一定的关于Xamarin和Bot framework的基本认识。本文是记录如何使用Xamarin的Web View来host一个bot service,所以这个bot的channel是web chat,顾名思义,web chat的bot是基于一个url来访问的,这也是为什么需要用Web View来作为容器的原因。
之后还会有一篇文档,是关于如何使用Direct Line API来与bot通信,该文档中的bot的channel就必须是Direct Line了。
实现
前提步骤
照着微软的官方文档:Create a bot with Bot Service 来建立一个最简单的web app bot。
注意,建立的时候如果只是为了测试需要,选择F0(免费)的方案,其他可以一路默认到底。
核心概念
这篇文档中使用的方案其实并不适合于Xamarin,我们使用的是将Web chat bot嵌入一个网站中的技术。这里的网站无非也就是Xamarin的Web View。使用这个技术也会导致这个demo有一些缺陷,之后会提到。
为了嵌入Web View中,我们有两种方案:
- 使用secret key获取token,并用token生成url中嵌入 (Option 1)
- 直接使用web chat bot的secret key来生成url并且嵌入 (Option 2)
Option 1 - Keep your secret hidden, exchange your secret for a token, and generate the embed
首先介绍一下使用这个方法的优缺点。
优点:
由于token是通过GET请求获取来的,所以可以确保唯一性和随机性,这样也就导致了为了该bot生成url唯一。所以这个方法的最大优点是:其他人无法轻易地获取到你的bot的url来随意使用在自己的应用程序中。
还有一个好处,基于我们的测试,如果在浏览器中开不同的tab,但是url中的token使用同一个的话,那么各个tab之间的通信记录是同步的,也就是存在一个保留当前会话的功能。
但是其实Bot是有一个State Service用来存取当前会话的状态,并且在Xamarin中不存在开另一个tab的可能性,所以这个点可以基本忽略不看。
缺点:
流程比较麻烦,由于要获取token,所以发送GET请求并且等待token返回然后生成url嵌入Web View中这个整个过程的耗时会比直接用Option 2中的secret key来生成url要长。
Option 2 - Embed the web chat control in your website using the secret
优点:
简单直接,url唯一,从读取到加载bot页面的时间要比Option 1短,原因是不用发请求拿token。
缺点:只要获到url,任何人都可以用这个url将该bot嵌入到自己的页面中或者Web View中。并且经过测试,我发现哪怕保持原来的bot的页面开着,直接打开个新的tab继续使用这个url,还是会变成一个新的session。这个问题也可以通过State Service来解决。
获取bot的secret key的方法
登录到Azure portal
从resource中找到创建的Web App Bot,点击它进入详情页。
从左侧的菜单中找到Channels,点击进入
找到你需要的secret key所对应的channel,这边我使用的是Web Chat的channel,点击Edit。
找到你需要使用的secret key,注意默认会有两个key,并且可以重新生成。
整合到Xamarin中的详细步骤
首先介绍下,本文中的内容我们使用上面内容中的Option 1,也就是获取token的方式来生成url。
创建Xamarin的基于.NET Standard的cross platform的项目。
我们使用MainPage.xaml页面作为起始页面,里面的元素放入一个label,表示欢迎使用Web View的bot,另外一个button点击之后用来跳转到bot的Web View页面。
页面代码如下:
1
2
3
4
5<!-- Below code is inside the ContentPage -->
<StackLayout>
<Label Text="Welcome to WebView Bot!" VerticalOptions="Center" HorizontalOptions="Center" />
<Button Text="Go to Bot!" x:Name="WebViewButton" VerticalOptions="Center" HorizontalOptions="Center" />
</StackLayout>在MainPage.xaml.cs文件中,我们会做几件事。首先是定义获取token的方法,这边根据文档我们是使用HTTP GET的方式来发送请求并且调用,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21private async Task<string> GetBotServiceToken()
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("BotConnector", "YOUR_BOT_SECRET");
var url = new Uri("https://webchat.botframework.com/api/tokens");
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var content = response.Content.ReadAsStringAsync();
string token = JsonConvert.Deserialize<string>(content);
Application.Current.Properties["botToken"] = token;
return token;
}
return String.Empty;
}接着我们继续定义WebViewButton的Click事件,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private async void WebViewButton_Clicked(object sender, EventArgs e)
{
string token = String.Empty;
if (String.IsNullOrEmpty(Application.Current.Properties["botToken"].ToString()))
{
// Get the token from HTTP Request
token = await GetBotServiceToken();
}
else
{
token = Application.Current.Properties["botToken"].ToString();
}
await Navigation.PushAsync(new BotWebViewPage(token));
}注意到在这里我可能会从Application的全局变量属性中获取token,原因是这样可以保持会话一段时间。当时我的理解是,使用手机的用户经常会返回去或者跳去其他应用做一些事情,当再点击那个button的时候,应该希望刚才聊天的内容还在。
接下来新建一个页面(ContentPage)叫BotWebView,并且页面中的Content内容就只有一个web view,代码如下:
1
2<WebView x:Name="BotWebView" VertialOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
</WebView>注意这里我们并没有给这个Web View赋Source的值,原因是它的source需要动态获取,所以我们在后台完成。
我们在BotWebView的后台页面中,重新定义一个构造函数,一来是获取刚才
Navigation.PushAsync()
方法中调用BotWebView的时候需要给这个页面传递的token的值,另一方面我们直接给这个WebView的Source赋值,代码如下:1
2
3
4
5
6
7public BotWebView(string token)
{
InitializeComponent();
// Notice to replace the YOUR_BOT_NAME
string botWebViewUrl = "https://webchat.botframework.com/embed/YOUR_BOT_NAME?s" + token;
this.BotWebView.Source = botWebViewUrl;
}
完成了上述步骤后,这个demo已经可以运作了,但是其中有一个潜在的小bug,就是在Android的情况下,如果你点击输入框,当键盘跳出来的时候并不会将WebView resize,这个是不合理,但是是by-design的,因为默认行为确实不会。
为了fix这个,我们需要在App.xaml.cs文件中的App的构造函数中添加如下代码:
1
2
3
4
5
6
7
8
9public App()
{
InitializeComponent();
// Need to add below code
Xamarin.Forms.PlatformConfiguration.AndroidSpecific.Application.SetWindowSoftInputModeAdjust(this, Xamarin.Forms.PlatformConfiguration.AndroidSpecific.WindowSoftInputModeAdjust.Resize);
MainPage = new NavigationPage(new XAMBotWebView.MainPage());
}
至此为止这个demo彻底完成。
补充内容
刚才提到这个demo会有一些缺陷,是因为现在经过我的测试,发现使用token的方式在web中比较正常,但是在手机中如果来回切换个2,3次,就必定出问题,整个bot都会卡掉无法运作。所以我得出的结论是这个方法还是应该内嵌在网页中为最合适。
但是这个方法有得天独厚的优势就是简单,实现步骤很少,对知识的要求也最低,如果真的入门做练习,可以使用这个方法来快速实现将一个bot service嵌入Xamarin的手机应用中。
评论