0条留言

00_TitleImage

曾经不得不开发一款应用程序,以关注某物的位置。用户,商店,饭店或其他景点的地址?直接的版本可能是为用户提供一个简单的表格。

地址输入表格

虽然该表单易于实现且可靠,但它可能很麻烦。另外,如果您想导航到该位置怎么办?还是为什么不使用设备的位置?点击地图以获取地址?好了,使用Xamarin团队提供的一些有用的库可以很容易地实现所有这些目标。

像专业人士一样进行地理编码

每当您需要在坐标和地址之间转换时, 地理编码 是你最好的朋友。您可以显示一个地址或职位,然后反过来:

var location = (await 地理编码.GetLocationsAsync($"{street}, {city}, {country}")).FirstOrDefault();

if (location == null) return;

Position = new Position(location.Latitude, location.Longitude);

因此,如果我们有一个用户可以输入地址的表单,并且想导航到该位置,则可以将地址转换为地理坐标,然后使用 Xamarin Essentials,在平台上打开地图应用,然后导航到该位置。

var location = new Location(Position.Latitude, Position.Longitude);
var options = new MapLaunchOptions { NavigationMode = NavigationMode.Driving };
await Xamarin.Essentials.Map.OpenAsync(location, options);

一堆条目和几行代码非常令人印象深刻,对吗?

一键获取地址

但是输入地址可能很麻烦。为什么不只选择地图上的位置并从该位置获取地址呢? Xamarin Forms支持地图控件已有相当长的一段时间了。添加完后 NuGet包,它将允许您展示本地地图平台,即Android上的Google Maps。

<?xml version="1.0" encoding="utf-8" ?>
             ...
             xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
             ... >

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="2*" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <maps:Map x:Name="MapControl" />
        ...
    </Grid>

</ContentPage>


请注意,根据您要定位的平台,需要一些设置步骤。因此,请务必查看有关这些详细信息的Microsoft官方文档。在这里,我们将继续专注于有趣的东西

The map has an event MapClicked which fires when the user taps on the map with the geo-position.

Observable.FromEventPattern<MapClickedEventArgs>(
    mc => MapControl.MapClicked += mc, 
    mc => MapControl.MapClicked -= mc)
    .Subscribe(ev => ViewModel.ExecuteSetAddress.Execute(ev.EventArgs.Position));

如果您不熟悉Rx.Net,则下面是带有事件处理程序的等效代码:

protected override void OnAppearing()
{
   // ...

    MapControl.MapClicked += HandleMapClicked;
}

private void HandleMapClicked(object sender, MapClickedEventArgs e)
{
    var 发布ion = e.Position;
    // ...
}

为了给用户一些反馈,我们甚至可以用别针显示录音位置:

private Unit SetPin(Position position)
{
    Pin pin = new Pin
    {
        Label = "The Place",
        Address = $"{ViewModel.Street}, {ViewModel.City}, {ViewModel.Country}",
        Type = PinType.Place,
        Position = position
    };

    var latDegrees = MapControl.VisibleRegion?.LatitudeDegrees ?? 0.01;
    var longDegrees = MapControl.VisibleRegion?.LongitudeDegrees ?? 0.01;
    MapControl.MoveToRegion(new MapSpan(position, latDegrees, longDegrees));
    MapControl?.Pins?.Clear();
    MapControl?.Pins?.Add(pin);
    return Unit.Default;
}

该代码将始终将销钉放置在新位置。如果用户点击该图钉,它将显示提供给该图钉的信息。

旁注:在给出此代码的情况下,设法显示信息是一个很大的挑战,十分之九,我仅设法将销钉放置在新位置。但是也许就是我

使用地址解析器,我们现在可以获取这些坐标的地址:

private async Task SetAddress(Position p)
{
    var addrs = (await 地理编码.GetPlacemarksAsync(new Location(p.Latitude, p.Longitude))).FirstOrDefault();
    Street = $"{addrs.Thoroughfare} {addrs.SubThoroughfare}";
    City = $"{addrs.PostalCode} {addrs.Locality}";
    Country = addrs.CountryName;
    // ...
}

但是现在您有了它,从坐标到地址再以另一种方式返回。容易吧?好吧,还有一件事。地图的默认坐标指向罗马。我明白了。罗马提供了很多服务,但是如果您的用户当前不在罗马,则可能要花很多力气才行,直到地图有用为止。长话短说,为什么不使用用户的位置?

马可?马球!

多亏了 Xamarin Essentials。这就是您需要所有权限后,如 链接。仅需几行,我们就可以获取地址并设置位置:

try
{
    var location = await Geolocation.GetLastKnownLocationAsync()
	var position = new Position(location.Latitude, location.Longitude);
	Position = position;
	// ... set address and stuff
}
// many exception handlers according to docs 这里


如果他们想分享自己的位置,将在第一次运行此代码时提示用户。就像一个旁注,如果您不希望通过权限对话框来破坏电池的用户权限,则可能需要在首次运行此代码时考虑一下。

因此,当视图出现时,我们可以尝试获取用户的位置并告诉地图控件要显示的是世界的哪一部分:

Geolocation.GetLastKnownLocationAsync()
            .ToObservable()
            .Catch(Observable.Return(new Location()))
            .SubscribeOn(RxApp.MainThreadScheduler)
            .Subscribe(async location => { 
                var position = new Position(location.Latitude, location.Longitude);
                Position = position;
                await SetAddress(position);
            });

希望您能看到,从烦人的形式转变为“有趣”的地图控件不需要太多。

GifRecordingSmall

所有这些都使用Microsoft提供给您的标准库。您可以在以下位置找到完整的示例 的GitHub.

高温超导

0条留言

TitleImage

不久前,我使用F#类型提供程序创建了一个转换表。那篇文章让我想到了是否有可能编写一个从网站获取数据的应用程序。也许您也收到了有关应用程序的请求。网上没有任何昂贵的东西,实际上应该显示的所有数据都已经存在于网络上,也就是这个网站就在这里。所以问题是:是否可以在不必编写任何后端代码的情况下创建这样的应用程序?

在这篇博客文章中,我将尝试为我最喜欢的Xamarin会议之一创建一个应用程序-the Xamarin专家日。因此,让我们看看是否可以创建我们的神话般的Xamarin Expert Day应用程序。

获取数据

类型提供程序是F#的令人愉快的功能。在编译类型提供程序期间,提供程序会生成数据源中表示的数据模型。我写了 博客文章 在分析HTML的主题之前。这次,我们将使用F#数据随附的另一个工具集 NuGet 包。

由于我们想获取有关Xamarin专家日会议的信息,因此我们可以尝试直接解析该网站。因此,我们可以使用以下行执行此操作:

HtmlDocument.Load "//expertday.forxamarin.com/"

不幸的是,我们生活在JavaScript的现代时代。我不想在这里切线,只是声明一个事实,即Xamarin专家日网站似乎在加载初始HTML之后正在加载有关对话和曲目的信息。幸运的是,当在浏览器中加载页面时,我们将获得一个HTML版本,其中包含了我们正在寻找的所有信息。因此,我们可以直接从文件中加载数据,而不是直接从网站中加载数据。预算项目有其局限性...-

HtmlDocument.Parse("ExpertXamarin.html")

当我们查看网站的HTML(在浏览器中)时,可以看到发言人在以下HTML结构下列出:

<li data-speakerid="df2bc5ca-5a6b-48a9-87ac-71c817d7b240" class="sz-speaker sz-speaker--compact ">
<div class="sz-speaker__photo">
  <a href="#" onclick="return ...');">
    <img src="...894db1.jpg">
  </a>
</div>
<h3 class="sz-speaker__name">
  <a href="#" onclick="return ...');">Mark Allibone</a>
</h3>
<h4 class="sz-speaker__tagline">Lead Mobile Developer Rey Automation, Microsoft MVP</h4>
</li>

因此,我们知道我们可以获取每个发言人的图像,名称,标语和ID。因此,让我们创建一条记录来存储该信息:

type Speaker = {Id:string; Name:string; Photo:string; Tagline:string}


创建记录并非绝对必要。但这确实使以后处理数据变得容易一些。另一个优点是,我们可以将类型提供程序代码封装在.Net标准库中,然后与非F#.Net代码共享。不,您不能直接从C#访问类型提供程序数据类型,而C#中的某些功能受F#启发。据我所知,我不会屏住呼吸,希望很快能看到C#中的类型提供程序...

有了适当的记录后,剩下要做的就是从HTML中提取数据。 F#Data随HTML CSS选择器一起带来的好处。 HTML CSS选择器允许对ID,类和标记类型进行过滤。因此,如果我们想获取发言人的姓名,我们可以过滤组件,然后按如下所示提取值:

// .. other parsing methods
let private getName (htmlNode:HtmlNode) =
    htmlNode.CssSelect("h3.sz-speaker__name > a") |> Seq.map (fun h -> h.DirectInnerText()) |> Seq.head

let getSpeakers (html:string) =
    HtmlDocument.Parse(html)
        .CssSelect("li.sz-speaker")
        |> Seq.map (fun s -> {Id = (getId s); Name = (getName s); Photo = (getPhoto s); Tagline = (getTagline s)})

同样,可以访问其余数据以填充记录中的其他字段。曲目同样如此,我们将再次创建一个记录,在其中存储每个曲目的名称,时间,房间和发言人ID。我们将能够将曲目链接到ID为:

type Track = {Room:string; Time:string; Title:string; SpeakerId:string option}

let getTracks (html:string) =
    HtmlDocument.Parse(html)
        .CssSelect("div.sz-session__card")
        |> Seq.map(fun s -> {Room = (getRoom s); Time = (getTime s); Title = (getTitle s); SpeakerId = (getSpeakerId s) })

现在我们已经拥有了所有数据,是时候开始破解应用程序了。

神话般的Xamarin Experts App

Before we start writing our UI code, there is still that shortcut we took above with loading the information out of a file. While this works great when using a script in the mobile world, this means we have to pack that HTML doc into the app. There are two approaches: either put it into the Assets folder on Android and in the Resources folder (you can also use XCAssets...) on iOS or make an Embedded Resource in the .Net Standard library. While the first option would be what Apple and Google intended you to use when adding docs, you want to ship with your app. You will have to jump through some hoops to access the document. So let's again save some time and just pack the file as an Embedded Resource in our .Net Standard project. Embedded Resources are packed into your apps binary. This results in an awkward fashion of accessing the data. While described in the official docs 这里,这是在Xamarin专家日会议应用程序中实现的方式(我们需要使用更短的名称...):

let loadFile filename =
    let assembly = IntrospectionExtensions.GetTypeInfo(typedefof<Model>).Assembly;
    let stream = assembly.GetManifestResourceStream(filename);
    use streamReader = new StreamReader(stream)
    streamReader.ReadToEnd()

有了这种方式。让我们创建一个包含标题,时间和房间的所有对话的列表。选择曲目时,我们将显示演示文稿的信息以及演讲者信息。

因此,我们可以将清单汇总如下:

let showTrackCell track =
    View.ViewCell( view =
        View.StackLayout(children = [
            View.Label (text = track.Title, 
                        fontSize = FontSize 22.)
            View.Label (text = track.Time + " in " + track.Room, 
                        fontSize = FontSize 14.,
                        fontAttributes = FontAttributes.Italic)
            ]))

let view (model: Model) dispatch =

    View.ContentPage(
        content = match model.SelectedTrack with 
                    | Some track -> showTrackInfo track model dispatch
                    | None -> View.ListView(
                                    rowHeight = 80,
                                    hasUnevenRows = true,
                                    margin = Thickness(8.,0.,0.,0.),
                                    items = (model.Tracks |> List.map showTrackCell),
                                    selectionMode = ListViewSelectionMode.Single,
                                    itemSelected = (fun args -> dispatch (TrackSelected args))
                                    )
        )

而“详细视图”将像这样完成:

let showTrackInfo track (model:Model) dispatch =
    let speaker = match track.SpeakerId with
                  | Some speakerId -> model.Speakers |> Seq.tryPick(fun s -> if s.Id = speakerId then Some s else None)
                  | None -> None

    let addSpeakerInfo (speaker:Speaker) =
        View.StackLayout(margin = Thickness(0.,32.,0.,0.), children = [
                View.Label (text = "Speaker", fontSize = FontSize 22. )
                View.Image (source = (Image.Path speaker.Photo))
                View.Label (text = "Presenter: " + speaker.Name)
                View.Label (text = "Tagline: " + speaker.Tagline)
            ])
        
    let speakerViewElements = match (speaker |> Option.map addSpeakerInfo) with
                              | Some speakerInfo -> speakerInfo
                              | None -> View.Label(text = "Brought to you by the Organizers");

    View.Grid (margin = Thickness(8.,8.,8.,16.),
                rowdefs = [Star; Auto],
                children = [
                    View.StackLayout(children = [
                        View.Label (text = track.Title, fontSize = FontSize 22.)
                        View.Label (text = "In: " + track.Room, fontSize = FontSize 14.)
                        View.Label (text = "At: " + track.Time, fontSize = FontSize 14., margin = Thickness(0.,-4.,0.,0.))
                        speakerViewElements
                        ])
                    (View.Button (text = "Back", command = (fun () -> dispatch (TrackSelected None)))).Row(1)
                ])

您可能已经注意到谈话说明丢失了。该网站具有JavaScript函数,可检索该附加信息。我认为可以将JavaScript调用复制到后端,然后解析答案

JSON / HTML答案。但是,插入莱姆声明为什么我不是懒惰的人。

该应用程序仍然感觉有些虚张声势,我想我可能必须在另一篇博客文章中进行跟进并发布它 漂亮 -

结论

在这个小实验中,我们着手研究是否有可能编写一个移动应用程序,以显示与网站上已经存在的信息相同的信息。虽然路上遇到了一些坎bump,但我在看着JavaScript。确实有可能为Xamarin专家日编写一个可在Android和iOS上运行的应用程序。

虽然我真的应该开始我的下一篇文章,并使该应用程序更漂亮-

您可以在上签出整个应用 的GitHub.

这篇文章是F#出现日历的一部分。一定要看看其他 帖子.

高温超导

0条留言

因此,不久前,我发布了一个关于我正在努力的小宠物项目的书,它的难度有多大。要查看大图,可以阅读概述 这里。第一步是编写在设备上运行并将传感器读数发送到云的物联网(IoT)客户端应用程序。在云中,Azure IoT中心管理客户端,还从客户端接收数据。

PCB板

顾名思义,物联网设备连接到互联网。因此,与您一开始可能会想到的客户端应用程序没什么不同。但是,当您开始考虑如何在野外部署和运行IoT设备时,会有很多区别将它们区分开。首先,这些设备通常不是由人操作的。这不是自带设备(BYOD)场景,IoT设备的创建者通常可以控制设备上运行的软件。受到控制还意味着该设备由制造商设置并连接到后端,而无需或很少需要人工干预。由于设备是开放的,并且已连接到Internet。如果不是强制性的,如果有安全漏洞(例如, 伤心欲绝 曾经发生。虽然一般来说问题通常是自发产生的 发布特洛伊·亨特 很好地总结。物联网解决方案经常出现的另一方面是必须处理的数据量。尽管您可以在边缘(现场)处理数据,但是通常需要在一个中心点聚合数据并根据实时数据采取行动,或者事后分析数据以寻找见解。通常,数据处理方案会带来最重要的业务价值,因此是人们尝试使用IoT解决方案创建的解决方案的重要组成部分。开发人员面临的挑战是创建一个系统,该系统可以按比例扩展以满足系统中运行的高数据量。简而言之,它是与传统Xamarin,WPF,WinForms或Web客户端应用程序不同的世界。

后端在物联网设备场景中起着重要作用。因此,毫不奇怪,您会发现很多公司希望为您的物联网工作提供帮助。解决方案之一是 蔚蓝物联网中心 它是在考虑到IoT挑战的基础上创建的。它提供了许多出色的功能,例如可伸缩性以同时从数百万个设备中接收数据,不同的消息传递模式以适应始终连接的物联网设备与可能只有一次又一次连接的物联网设备。您不仅可以通过以下方式从设备接收数据 蔚蓝物联网中心 而且还会向设备发送信息。此外,它还提供了通知IoT设备可用的新软件/固件更新的方法。由于它在云中运行,因此可伸缩性已融入产品中。

对于设置IoT中心,建议您遵循以下步骤 指示.

您可以为每个帐户创建一个免费的IoT中心实例,这是我将在此博客文章中使用的实例。

通过IoT中心设置,让我们设置本地环境。您可以在PowerShell中全部管理IoT中心-哦,是的,您感觉到外壳的强大功能(试图在这里改善我的教父的笑话...),您所需要做的就是安装 蔚蓝 CLI。然后安装 物联网扩展。如果尚未安装工具,请确保首先使用以下命令在PowerShell上登录Azure:

az login

登录并拥有扩展名后,您可以在PowerShell中直接进行各种操作。例如,我们可以创建一个新设备,如下所示:

az iot hub device-identity create --hub-name IoTEndpoint --device-id test-device-01

Note that the --hub-name must equal the name you gave your IoT Hub on 蔚蓝. You are free to choose a different name to register your device after --device-id. Once you have a device registered. You can send messages as the registered device:

az iot device send-d2c-message -n IoTEndpoint -d test-device-01 --data 'Hello IoTHub'

没有错误就意味着成功,但是另一个方便的命令正在查看IoT中心正在接收哪些消息:

az iot hub monitor-events -n IoTEndpoint

如果现在打开第二个PowerShell以将消息作为注册设备发送,您将看到IoT中心收到消息。

显示收到的消息

安装了所有工具并注册了设备后,我们准备实施我们的客户。 蔚蓝 CLI可以使用更多命令,您可以找到命令的完整列表 这里.

实施物联网客户端

蔚蓝物联网中心提供了一个 开发包 客户可以用来交流的内容。该SDK适用于.Net,Node.js,Python,C和Java。但是,不能或不想使用SDK,可以 手动地 通过HTTP,MQTT或AMQP连接到IoT中心。

为了我的第一个努力,我使用了 蔚蓝物联网开发套件。这真是物超所值-也许您甚至在参加的活动中也收到了礼物作为礼物?开发套件带有许多传感器,以及RGB LED,显示器,AUX,USB和WiFi连接。话虽如此,如果您没有设备,仍然可以使用.Net 开发包。该SDK在.Net Standard上运行。因此,您可以编写一个.Net Core客户端,使用 NuGet 包。

因此,我们可以实施.Net Core Console应用来实施我们的解决方案。请注意,由于该库可以安装在.Net Standard项目中,因此您可以将所有IoT逻辑代码提取到.Net Standard库中。懒惰又使这个博客的解决方案保持简单,我将直接在.Net Core项目中实现所有代码。我们要对应用程序进行的第一件事是连接到后端。我们可以通过不同的方式使用IoT中心来实现这一目标。首先,我们将采用最简单的方法,通常这不是您要部署到生产环境的方法-即使用带有API密钥的连接字符串作为身份验证机制:

var device = DeviceClient.CreateFromConnectionString("HostName=IoTEndpoint.azure-devices.net;DeviceId=test-device-01;SharedAccessKey=THIS-IS-WHERE-THE-SHARED-KEY-WOULD-BE-DISPLAYED");

您可以从IoT中心,IoT设备(在我的情况下为test-device-01)的Azure门户中检索此连接字符串,然后选择“主要”或“辅助”连接字符串。

连接后,我们可以开始发送序列化为JSON的传感器读数:

var json = JsonConvert.SerializeObject(measurement);
var message = new Message(Encoding.UTF8.GetBytes(json));

await device.SendEventAsync(message);

由于.Net Standard几乎可以在您能想到的所有操作系统上运行,因此您可以提取以上代码,以使用实际传感器在Raspberry Pie或Android或INSERT-YOUR-TARGET-HERE上运行。而且由于 开发包 也可用于其他语言(例如C版本),我们可以将其用于编写Azure开发工具包的应用程序。

我可以拥有其余的客户端代码吗?是的-如果您一直坚持到最后,您将在GitHub上找到指向客户端完整源代码的链接。

蔚蓝物联网开发套件客户端

我必须建议遵循Microsoft的官方文档,以了解如何设置 带有Visual Studio代码的Azure IoT开发套件,这将向您展示如何初始设置设备并将其连接到IoT中心。建议您确保遵循文档中的所有步骤,并且不要遗漏任何部件,例如在Windows计算机上安装USB驱动程序。当然是从朋友那里说出经验

微软为开发套件提供了许多示例。可以使用以下方法将样品直接加载到设备上 Visual Studio程式码。我从远程监控中获得了上述.Net Core Client的启发。 教程 -我跳过了Azure的内容,因为我想以不同的方式处理数据。我对C代码所做的更改只是将大气压(atm)的单位更改为hPa。哦,当然要取消从摄氏到华氏度的转换-我的大脑不会计算英制

请注意,由于设备上可用的资源有限,许多IoT设备未安装操作系统。这些限制通常是使用C编写许多程序的原因。如果您是像我这样的.Net开发人员,则可能会错过很多便利。再一次,您可能会发现一些合并,因为您的代码将非常有效

我希望很快签出的另一台设备是 荒野实验室的草地。 Meadow的优点在于,您可以使用C#编写设备的IoT客户端代码。好消息是它们已经开放接受预订,但是您可能必须排队等候,直到所有Kickstarter广告系列支持者都收到了他们的支持。

将应用程序安装在开发套件上并将其连接到WiFi后,您将能够看到消息到达IoT中心仪表板。

该图显示了Azure IoT中心正在接收的消息。

通过使用CLI工具命令,从开始之前,我们可以启动侦听器,该侦听器将接收发送到Azure IoT中心的每条消息:

ReceivingDeviceMessages

您将能够看到IoT中心中的原始JSON。

结论

设置第一个物联网设备可能有些艰巨,因为其中有一些活动的部分。首先了解客户端,甚至只是知道如何在.Net Core中实现客户端,然后了解Azure IoT中心。对我来说,很高兴注意到从小处着手并逐渐了解Azure IoT中心提供的可能性是可以的。但是,Azure IoT中心还有很多东西。例如更新设备,设备孪生或从云与设备通信-如果您对详细列表感兴趣,请务必查看官方页面 这里.

在该系列的下一篇博客文章中,我们将研究如何在云中处理数据。以及如何将其转发给客户。我们将在哪里为用户显示数据。因此,请继续关注并检查所有客户端代码。 的GitHub.

高温超导

0条留言

黑金属挂锁

更新: 所以张贴这个后我的同事和朋友 丹尼尔走近我,向我展示了 蔚蓝 Artifacts凭据提供程序 由Microsoft自动执行以下步骤。请务必检查一下。谢谢,丹尼尔(Daniel)向我展示了这一点,并使我的生活更轻松-

因此,最近我正在使用Azure DevOps的许多功能。即将新创建的NuGet软件包推送到您的 私人饲料. Bringing up the question how can I access the feed and authenticate during a NuGet restore process via dotnet restore?

While this blog 发布 shows steps to be taken for 蔚蓝开发运营 - the same actions are required in the NuGet.config for other sources.

虽然我知道如何在Visual Studio中单击我的方法来执行此操作。在我的Ubuntu Shell下,这不是一个选择。幸运的是,添加NuGet提要是非常常识,而在Windows和Unix系统中,路径却有所不同,您可以在主目录下找到它:

~/.nuget/NuGet/NuGet.config

或对于Windows将是:

%appdata%\NuGet\

You can add the feed to your NuGet.config file:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <add key="nuget.org" value="//api.nuget.org/v3/index.json" protocolVersion="3" />
        <add key="NameOfYourFeed" value="path to your nuget/index.json" />
    </packageSources>
</configuration>

现在,要访问私有的NuGet提要,您将必须提供用户名和密码。您可以将它们添加到配置文件中:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <!-- packageSources -->
    <packageSourceCredentials>
        <NameOfYourFeed>
            <add key="Username" value="gnabber"/>
            <add key="ClearTextPassword" value="YourPassword"/>
        </NameOfYourFeed>
    </packageSourceCredentials>
</configuration>

唯一的问题是您可能不想将纯文本的Azure DevOps密码存储在计算机上。而且您也不应该这样做。因此,让我们回到Azure DevOps,单击您的个人资料图片并选择“安全性”。现在生成一个新令牌。确保选择“显示所有范围”,然后在“打包”下选择“读取”权限。

该图显示了为生成对令牌包只读访问的令牌而生成的对话框的图像

Copy the generated token and store it in the NuGet.config within the PlainTextPassword field. You can now dotnet restore your packages 从 the private Package feed.

高温超导

1条评论

pexels-photo-292426

无论是使用Xamarin Forms还是本机Xamarin.iOS / Xamarin.Android编写移动应用程序,有时要求都要求在服务器上发生某些更改时尽快更新您的应用程序。这可能是聊天应用程序,股票报价器或任何监视应用程序,向后端当前正在运行的进程的用户显示实时数据。

假设我们要创建一个简单的聊天应用程序。如果我们沿HTTP走传统的“创建读取更新删除(CRUD)”应用程序的路径,则可能选择每隔几秒钟轮询一次服务器以读取最新值。尽管此方法可提供结果,但它也有一些缺点。仅举几例:即使没有任何变化也可以发出请求,攀升电池使用量类别的排行榜,而且您的服务器会因请求而烦恼,只能告诉他们:“对不起,还没有消息...”-因此,如果不进行民意调查,请推送。这就是WebSocket进入的地方。

WebSockets的唯一问题是.Net中的实现与金属接近。这导致开发人员必须进行额外的实施工作。幸运的是,.Net开发人员可以使用SignalR随WebSockets附带所需的所有样板代码。 Web开发人员还将告诉您有关支持SingalR的后备选项的信息。作为Xamarin开发人员,您很可能永远不会使用这些功能。但是,通过处理连接,渠道或写给特定的已连接客户端的便捷性,您很可能会感到高兴。

SignalR已经存在了一段时间。它已经被移植到 .Net核心 并与.Net标准兼容。这是个好消息,因为我们可以将SignalR客户端直接添加到我们的Xamarin Forms应用程序中-无需平台特定/包装代码。但是在开始实现客户端之前,我们首先必须创建一个启用SignalR的后端。

如果您不熟悉SignalR,请注意SignalR和SignalR Core之间有很大的不同。如果您今天使用ASP.NET Core或Azure Functions编写新应用程序。您将要使用 SignalR核心 否则,您将进行令人讨厌的错误搜寻,最后以手掌拍打额头为名

服务器

在实现后端时,我们可以在两个选项之间进行选择。我们要么使用ASP.NET Core来实现SignalR,要么决定使用Azure SignalR核心服务。后者可以集成到ASP.NET Core或Azure Function应用中。 蔚蓝选项还具有扩展功能-换句话说,您可以立即使用SignalR获得多达100万个同时连接。

有关如何设置Azure Functions和SignalR组合的更多详细信息,请在官方网站上找到说明 文件资料.

对于我们简单的聊天应用程序,我们需要一种让客户发送和接收消息的方法。为此,我们将必须创建一个SignalR Hub,它提供一种发送消息的方法:

[FunctionName("SendMessage")]
public static Task SendMessage(
    [HttpTrigger(AuthorizationLevel.Anonymous, "发布")]string message,
    [SignalR(HubName = "SignalRDemo")]IAsyncCollector<SignalRMessage> signalRMessages)
{
    return signalRMessages.AddAsync(
        new SignalRMessage
        {
            Target = "NewMessage",
            Arguments = new[] { message }
        });
}

调用时,该方法会将消息发送到所有连接的客户端。这使我们进入了下一个观点。每个聊天参与者都必须首先连接到集线器,以便可以接收消息。因此,让我们实现该注册方法:

[FunctionName("Negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo(HubName = "SignalRDemo")] SignalRConnectionInfo connectionInfo)
{
    // connectionInfo contains an access key token with a name identifier claim set to the authenticated user
    return connectionInfo;
}

The naming of the method is a convention 从 SignalR. In other words, you must name the method Negotiate, or your code will not work. No, I do not want to elaborate on how I found this one out the hard way 😉

有了功能和SignalR服务,我们现在可以将重点转移到客户身上。

移动客户端

在移动客户端上,我们希望能够接收消息并键入对该组的响应。我们简单的应用程序将不得不承受在连接时仅接收消息的局限性。至少目前是这样。但是这就是聊天的全部荣耀。

SignalRChat

Now let's have a look at the ChatService which connects us to the backend and receives messages:

public async Task Connect()
{
    if (_connection.State == HubConnectionState.Connected) return;

    _connection.On<string>("NewMessage", (messageString) =>
    {
        var message = JsonConvert.DeserializeObject<Message>(messageString);
        _newMessage.OnNext(message);
        Debug.WriteLine(messageString);
    });

    await _connection.StartAsync();
}

请注意,在连接到后端之前,我们先注册了接收器方法。这样,我们一旦连接到SignalR服务就开始接收更新。现在,在实现接收方方法时,必须确保类型签名与我们先前在服务器上定义的方法匹配。如果类型或名称不匹配,您将永远不会收到任何消息。

由于阅读只是乐趣的一半,所以让我们实现发送消息:

public async Task Send(Message message)
{
    var serializedPayload = JsonConvert.SerializeObject(message);

    var response = await _httpClient.PostAsync("//gnabbersignalr.azurewebsites.net/api/SendMessage", new StringContent(serializedPayload));
    Debug.WriteLine(await response.Content.ReadAsStringAsync());
}

如果您从消息流中获得了足够的信息,但是想让您的眼睛凝视一个裸露的应用程序,那么这里就是断开连接的方法:

public async Task Disconnect()
{
    await _connection.DisposeAsync();

    _connection = new HubConnectionBuilder()
        .WithUrl(backendUrl)
        .Build();
}

我希望您能看到使用SignalR可以轻松实现与服务器的双向通信层。这将使您的(移动)客户端能够近乎实时地发送和接收数据。使用SignalR的另一个副作用是您可以轻松地通过Web客户端扩展应用程序。由于您最喜欢的JavaScript框架将允许您使用 SignalR客户端。如果您准备开始使用SignalR,请务必查看 docs.

您可以找到整个示例,包括上的所有UI代码。 的GitHub.

该博客是 十月Xamarin挑战。因此,在编写Xamarin应用程序时,请务必查看其他文章以获得更多最佳实践。

高温超导