如何在Xamarin.Forms项目中使用Bot Service(WebView版)

背景

现在微软主推的就是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的方法

  1. 登录到Azure portal

  2. 从resource中找到创建的Web App Bot,点击它进入详情页。

  3. 从左侧的菜单中找到Channels,点击进入

  4. 找到你需要的secret key所对应的channel,这边我使用的是Web Chat的channel,点击Edit。

  5. 找到你需要使用的secret key,注意默认会有两个key,并且可以重新生成。

整合到Xamarin中的详细步骤

首先介绍下,本文中的内容我们使用上面内容中的Option 1,也就是获取token的方式来生成url。

  1. 创建Xamarin的基于.NET Standard的cross platform的项目。

  2. 我们使用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>
  3. 在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
    21
    private 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;
    }
  4. 接着我们继续定义WebViewButton的Click事件,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    private 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的时候,应该希望刚才聊天的内容还在。

  5. 接下来新建一个页面(ContentPage)叫BotWebView,并且页面中的Content内容就只有一个web view,代码如下:

    1
    2
    <WebView x:Name="BotWebView" VertialOptions="FillAndExpand" HorizontalOptions="FillAndExpand">
    </WebView>

    注意这里我们并没有给这个Web View赋Source的值,原因是它的source需要动态获取,所以我们在后台完成。

  6. 我们在BotWebView的后台页面中,重新定义一个构造函数,一来是获取刚才Navigation.PushAsync()方法中调用BotWebView的时候需要给这个页面传递的token的值,另一方面我们直接给这个WebView的Source赋值,代码如下:

    1
    2
    3
    4
    5
    6
    7
    public 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;
    }
  1. 完成了上述步骤后,这个demo已经可以运作了,但是其中有一个潜在的小bug,就是在Android的情况下,如果你点击输入框,当键盘跳出来的时候并不会将WebView resize,这个是不合理,但是是by-design的,因为默认行为确实不会。

    为了fix这个,我们需要在App.xaml.cs文件中的App的构造函数中添加如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public 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的手机应用中。

如何在Xamarin.Forms项目中使用Bot Service(DirectLine版) 介绍如何调试Xamarin.Android的binding项目
You need to set install_url to use ShareThis. Please set it in _config.yml.

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×