教程:使用 Azure 通知中心和必应空间数据推送基于位置的通知Tutorial: Push location-based notifications with Azure Notification Hubs and Bing Spatial Data

本教程介绍如何使用 Azure 通知中心和必应空间数据来传送基于位置的推送通知。In this tutorial, you learn how to deliver location-based push notifications with Azure Notification Hubs and Bing Spatial Data.

在本教程中,我们将执行以下步骤:In this tutorial, you take the following steps:

  • 设置数据源Set up the data source
  • 设置 UWP 应用程序Set up the UWP application
  • 设置后端Set up the backend
  • 在通用 Windows 平台 (UWP) 应用中测试推送通知Test push notifications in the Universal Windows Platform (UWP) app

先决条件Prerequisites

设置数据源Set up the data source

  1. 登录到必应地图开发人员中心Log in to the Bing Maps Dev Center.

  2. 在顶部导航栏中选择“数据源”,然后选择“管理数据源”。 In the top navigation bar, select Data sources, and select Manage Data Sources.

  3. 如果你没有数据源,会看到用于创建数据源的链接。If you don't have an existing data source, you see a link to create a data source. 选择“将数据作为数据源上传”。 Select Upload data as a data source. 也可以使用“数据源” > “上传数据”菜单。 You can also use Data sources > Upload data menu.

  4. 在硬盘驱动器上创建包含以下内容的文件 NotificationHubsGeofence.pipe:本教程使用基于管道的示例文件框住旧金山海滨区域:Create a file NotificationHubsGeofence.pipe on your hard drive with the following content: In this tutorial, you use a sample pipe-based file that frames an area of the San Francisco waterfront:

    Bing Spatial Data Services, 1.0, TestBoundaries
    EntityID(Edm.String,primaryKey)|Name(Edm.String)|Longitude(Edm.Double)|Latitude(Edm.Double)|Boundary(Edm.Geography)
    1|SanFranciscoPier|||POLYGON ((-122.389825 37.776598,-122.389438 37.773087,-122.381885 37.771849,-122.382186 37.777022,-122.389825 37.776598))
    

    管道文件表示以下实体:The pipe file represents this entity:

  5. 在“上传数据源”页中执行以下操作: In the Upload a data source page, do the following actions:

    1. 为“数据格式”选择“管道”。 Select pipe for Data format.

    2. 浏览到在上一步骤中创建的 NotificationHubGeofence.pipe 文件并将其选中。Browse and select the NotificationHubGeofence.pipe file that you created in the previous step.

    3. 选择“上传”按钮。 Select Upload button.

      Note

      系统可能会提示你为“主密钥”指定不同于“查询密钥”的新密钥 。You might be prompted to specify a new key for the Master Key that is different from the Query Key. 只需通过仪表板创建新密钥,然后刷新数据源上传页。Simply create a new key through the dashboard and refresh the data source upload page.

  6. 上传数据文件后,需确保发布数据源。Once you upload the data file, you need to make sure that you publish the data source. 像前面一样,选择“数据源” -> “管理数据源”。 Select Data sources -> Manage Data Sources like you did before.

  7. 在列表中选择自己的数据源,然后在“操作”列中选择“发布”。 Select your data source in the list, and choose Publish in the Actions column.

  8. 切换到“发布数据源”选项卡,确认列表中显示了该数据源。 Switch to the Published Data Sources tab, and confirm that you see your data source in the list.

  9. 选择“编辑” 。Select Edit. 随后会看到数据中引入的位置(概览)。You see (at a glance) what locations you introduced in the data.

    此时,门户并未显示所创建的地理围栏的边界 - 我们只需确认指定的位置位于适当的邻近范围内。At this point, the portal does not show you the boundaries for the geofence that you created – all you need is confirmation that the location specified is in the right vicinity.

  10. 现在你已满足数据源的所有要求。Now you have all the requirements for the data source. 要获取有关 API 调用的请求 URL 的详细信息,请在必应地图开发人员中心内依次选择“数据源”、“数据源信息”。 To get the details on the request URL for the API call, in the Bing Maps Dev Center, choose Data sources and select Data Source Information.

    “查询 URL”是可对其执行查询的终结点,用于检查设备目前是否在某个位置的边界内。 The Query URL is the endpoint against which you can execute queries to check whether the device is currently within the boundaries of a location or not. 若要执行此检查,只需针对查询 URL 执行 GET 调用并追加以下参数:To perform this check, you just execute a GET call against the query URL, with the following parameters appended:

    ?spatialFilter=intersects(%27POINT%20LONGITUDE%20LATITUDE)%27)&$format=json&key=QUERY_KEY
    

    必应地图自动执行计算,以确定设备是否位于地域隔离区内。Bing Maps automatically performs the calculations to see whether the device is within the geofence. 通过浏览器(或 cURL)执行请求后,将收到标准 JSON 响应:Once you execute the request through a browser (or cURL), you get a standard JSON response:

    仅当位置点确实位于指定边界内时,才出现此响应。This response only happens when the point is actually within the designated boundaries. 如果不在边界内,将收到空白的 results 桶:If it is not, you get an empty results bucket:

设置 UWP 应用程序Set up the UWP application

  1. 在 Visual Studio 中,启动“空白应用(通用 Windows)”类型的新项目 。In Visual Studio, start a new project of type Blank App (Universal Windows).

    完成创建项目之后,应该可以控制应用本身。Once the project creation is complete, you should have the harness for the app itself. 现在让我们完成地域隔离基础结构所需的各项设置。Now let’s set up everything for the geo-fencing infrastructure. 由于我们要使用必应服务来运行此解决方案,因此可以使用公共 REST API 终结点来查询特定的位置框架:Because you are going to use Bing services for this solution, there is a public REST API endpoint that allows you to query specific location frames:

    http://spatial.virtualearth.net/REST/v1/data/
    

    指定以下参数,使终结点正常工作:Specify the following parameters to get it working:

    • 数据源 ID 和数据源名称 – 在必应地图 API 中,数据源包含各种分门别类的元数据,例如营业地点和营业时间 。Data Source ID and Data Source Name – in Bing Maps API, data sources contain various bucketed metadata, such as locations and business hours of operation.

    • 实体名称 – 要用作通知参照点的实体。Entity Name – the entity you want to use as a reference point for the notification.

    • 必应地图 API 密钥 – 前面在创建必应开发人员中心帐户时获取的密钥。Bing Maps API Key – The key that you obtained earlier when you created the Bing Dev Center account.

      现在我们已准备好数据源,接下来可以开始操作 UWP 应用程序。Now that you have the data source ready, you can start working on the UWP application.

  2. 启用应用程序的位置服务。Enable location services for your application. 在“解决方案资源管理器”中打开 Package.appxmanifest 文件。 Open the Package.appxmanifest file in Solution Explorer.

  3. 在刚刚打开的“包属性”选项卡中切换到“功能”选项卡,然后选择“位置”。 In the package properties tab that just opened, switch to the Capabilities tab, and select Location.

  4. 在解决方案中创建名为 Core 的新文件夹,并在其中添加名为 LocationHelper.cs 的新文件:Create a new folder in your solution named Core, and add a new file within it, named LocationHelper.cs:

    LocationHelper 类包含用于通过系统 API 获取用户位置的代码:The LocationHelper class has code to obtain the user location through the system API:

    using System;
    using System.Threading.Tasks;
    using Windows.Devices.Geolocation;
    
    namespace NotificationHubs.Geofence.Core
    {
        public class LocationHelper
        {
            private static readonly uint AppDesiredAccuracyInMeters = 10;
    
            public async static Task<Geoposition> GetCurrentLocation()
            {
                var accessStatus = await Geolocator.RequestAccessAsync();
                switch (accessStatus)
                {
                    case GeolocationAccessStatus.Allowed:
                        {
                            Geolocator geolocator = new Geolocator { DesiredAccuracyInMeters = AppDesiredAccuracyInMeters };
    
                            return await geolocator.GetGeopositionAsync();
                        }
                    default:
                        {
                            return null;
                        }
                }
            }
    
        }
    }
    

    若想详细了解如何在 UWP 应用中获取用户的位置,请参阅获取用户的位置To learn more about getting the user’s location in UWP apps, seeGet the user's location.

  5. 若要检查是否确实能够获取位置,请打开主页的代码端 (MainPage.xaml.cs)。To check that the location acquisition is actually working, open the code side of your main page (MainPage.xaml.cs). MainPage 构造函数中为 Loaded 事件创建新的事件处理程序。Create a new event handler for the Loaded event in the MainPage constructor.

    public MainPage()
    {
        this.InitializeComponent();
        this.Loaded += MainPage_Loaded;
    }
    

    该事件处理程序的实现如下:The implementation of the event handler is as follows:

    private async void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        var location = await LocationHelper.GetCurrentLocation();
    
        if (location != null)
        {
            Debug.WriteLine(string.Concat(location.Coordinate.Longitude,
                " ", location.Coordinate.Latitude));
        }
    }
    
  6. 运行应用程序,并允许它访问你的位置。Run the application and allow it to access your location.

  7. 启动应用程序后,应该可以在“输出”窗口中看到坐标: Once the application launches, you should be able to see the coordinates in the Output window:

    现在我们已经知道如何获取位置;如果不再需要使用 Loaded 事件处理程序,可将其删除。Now you know that location acquisition works, you can remove the Loaded event handler if you like because you won’t be using it anymore.

  8. 下一步是捕获位置更改。The next step is to capture location changes. LocationHelper 类中,添加 PositionChanged 的事件处理程序:In the LocationHelper class, add the event handler for PositionChanged:

    geolocator.PositionChanged += Geolocator_PositionChanged;
    

    实现会在“输出”窗口中显示位置坐标: The implementation shows the location coordinates in the Output window:

    private static async void Geolocator_PositionChanged(Geolocator sender, PositionChangedEventArgs args)
    {
        await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            Debug.WriteLine(string.Concat(args.Position.Coordinate.Longitude, " ", args.Position.Coordinate.Latitude));
        });
    }
    

设置后端Set up the backend

  1. 从 GitHub 下载 .NET 后端示例Download the .NET Backend Sample from GitHub.

  2. 下载完成后,打开 NotifyUsers 文件夹,然后在 Visual Studio 中打开 NotifyUsers.sln 文件。Once the download completes, open the NotifyUsers folder, and then open NotifyUsers.sln file in Visual Studio.

  3. AppBackend 项目设置为“启始项目”并将它启动 。Set the AppBackend project as the StartUp Project and launch it.

    项目已配置为将推送通知发送到目标设备,因此我们只需要做两件事 – 指定通知中心的适当连接字符串,并添加边界标识以便仅当用户位于地域隔离区内时才发送通知。The project is already configured to send push notifications to target devices, so you need to do only two things – specify right connection string for the notification hub and add boundary identification to send the notification only when the user is within the geofence.

  4. 若要配置连接字符串,请打开 Models 文件夹中的 Notifications.csTo configure the connection string, in the Models folder open Notifications.cs. NotificationHubClient.CreateClientFromConnectionString 函数应该包含可在 Azure 门户中获取的通知中心的相关信息(查看“设置”中的“访问策略”页)。 The NotificationHubClient.CreateClientFromConnectionString function should contain the information about your notification hub that you can get in the Azure portal (look inside the Access Policies page in Settings). 保存更新的配置文件。Save the updated configuration file.

  5. 为必应地图 API 结果创建模型。Create a model for the Bing Maps API result. 执行此操作的最简单方法是打开 Models 文件夹,并选择“添加” > “类”。 The easiest way to do that is to open the Models folder and choose Add > Class. 将它命名为 GeofenceBoundary.csName it GeofenceBoundary.cs. 完成后,从第一部分获取的 API 响应中复制 JSON。Once done, copy the JSON from the API response that you got in the first section. 在 Visual Studio 中使用“编辑” > “选择性粘贴” > “将 JSON 粘贴为类”。 In Visual Studio, use Edit > Paste Special > Paste JSON as Classes.

    这样,就能确保对象完全按预期反序列化。This way you ensure that the object is deserialized exactly as it was intended. 生成的类集应类似于以下类:Your resulting class set should resemble the following class:

    namespace AppBackend.Models
    {
        public class Rootobject
        {
            public D d { get; set; }
        }
    
        public class D
        {
            public string __copyright { get; set; }
            public Result[] results { get; set; }
        }
    
        public class Result
        {
            public __Metadata __metadata { get; set; }
            public string EntityID { get; set; }
            public string Name { get; set; }
            public float Longitude { get; set; }
            public float Latitude { get; set; }
            public string Boundary { get; set; }
            public string Confidence { get; set; }
            public string Locality { get; set; }
            public string AddressLine { get; set; }
            public string AdminDistrict { get; set; }
            public string CountryRegion { get; set; }
            public string PostalCode { get; set; }
        }
    
        public class __Metadata
        {
            public string uri { get; set; }
        }
    }
    
  6. 接下来,打开 Controllers > NotificationsController.csNext, open Controllers > NotificationsController.cs. 更新 Post 调用以考虑目标经度和纬度。Update the Post call to account for the target longitude and latitude. 为此,只需将以下两个字符串添加到函数签名 – latitudelongitudeTo do so, add two strings to the function signature – latitude and longitude.

    public async Task<HttpResponseMessage> Post(string pns, [FromBody]string message, string to_tag, string latitude, string longitude)
    
  7. 在名为 ApiHelper.cs 的项目中创建一个新类,我们将使用它来连接到必应,以检查位置点边界的交叉点。Create a new class within the project called ApiHelper.cs – you use it to connect to Bing to check point boundary intersections. 如以下代码中所示实现 IsPointWithinBounds 函数:Implement a IsPointWithinBounds function as shown in the following code:

    public class ApiHelper
    {
        public static readonly string ApiEndpoint = "{YOUR_QUERY_ENDPOINT}?spatialFilter=intersects(%27POINT%20({0}%20{1})%27)&$format=json&key={2}";
        public static readonly string ApiKey = "{YOUR_API_KEY}";
    
        public static bool IsPointWithinBounds(string longitude,string latitude)
        {
            var json = new WebClient().DownloadString(string.Format(ApiEndpoint, longitude, latitude, ApiKey));
            var result = JsonConvert.DeserializeObject<Rootobject>(json);
            if (result.d.results != null && result.d.results.Count() > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    

    Important

    请务必将 API 终结点替换为前面从必应开发人员中心获取的查询 URL(这一点同样适用于 API 密钥)。Make sure to substitute the API endpoint with the query URL that you obtained earlier from the Bing Dev Center (same applies to the API key).

    如果查询返回了结果,则表示指定位置点位于地域隔离边界内,因此函数返回了 trueIf there are results to the query, that means that the specified point is within the boundaries of the geofence, so the function returns true. 如果未返回结果,必应将告知位置点位于查找框架外部,因此函数返回了 falseIf there are no results, Bing is telling you that the point is outside the lookup frame, so the function returns false.

  8. NotificationsController.cs 中的 switch 语句前面创建检查:In NotificationsController.cs, create a check right before the switch statement:

    if (ApiHelper.IsPointWithinBounds(longitude, latitude))
    {
        switch (pns.ToLower())
        {
            case "wns":
                //// Windows 8.1 / Windows Phone 8.1
                var toast = @"<toast><visual><binding template=""ToastText01""><text id=""1"">" +
                            "From " + user + ": " + message + "</text></binding></visual></toast>";
                outcome = await Notifications.Instance.Hub.SendWindowsNativeNotificationAsync(toast, userTag);
    
                // Windows 10 specific Action Center support
                toast = @"<toast><visual><binding template=""ToastGeneric""><text id=""1"">" +
                            "From " + user + ": " + message + "</text></binding></visual></toast>";
                outcome = await Notifications.Instance.Hub.SendWindowsNativeNotificationAsync(toast, userTag);
    
                break;
        }
    }
    

在 UWP 应用中测试推送通知Test push notifications in the UWP app

  1. 在 UWP 应用中,现在应该可以测试通知。In the UWP app, you should now be able to test notifications. LocationHelper 类中创建新函数 SendLocationToBackendWithin the LocationHelper class, create a new function – SendLocationToBackend:

    public static async Task SendLocationToBackend(string pns, string userTag, string message, string latitude, string longitude)
    {
        var POST_URL = "http://localhost:8741/api/notifications?pns=" +
            pns + "&to_tag=" + userTag + "&latitude=" + latitude + "&longitude=" + longitude;
    
        using (var httpClient = new HttpClient())
        {
            try
            {
                await httpClient.PostAsync(POST_URL, new StringContent("\"" + message + "\"",
                    System.Text.Encoding.UTF8, "application/json"));
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
    }
    

    Note

    POST_URL 设置为已部署的 Web 应用程序的位置。Set the POST_URL to the location of your deployed web application. 现在,可以在本地运行该应用,但是由于要着手部署公共版本,因此需要使用一个外部提供程序来托管该应用。For now, it’s OK to run it locally, but as you work on deploying a public version, you need to host it with an external provider.

  2. 为推送通知注册 UWP 应用。Register the UWP app for push notifications. 在 Visual Studio 中,选择“项目” > “应用商店” > “将应用与应用商店关联”。 In Visual Studio, choose Project > Store > Associate app with the store.

  3. 登录到开发人员帐户后,请务必选择现有应用或创建新应用,并让包与它相关联。Once you sign in to your developer account, make sure you select an existing app or create a new one and associate the package with it.

  4. 转到开发人员中心,打开创建的应用。Go to the Dev Center and open the app that you created. 选择“服务” > “推送通知” > “Live 服务站点”。 Choose Services > Push Notifications > Live Services site.

  5. 记下站点上的“应用程序密钥”和“包 SID” 。On the site, take note of the Application Secret and the Package SID. 在 Azure 门户中需要用到这两项信息 - 打开通知中心,选择“设置” > “Notification Services” > “Windows (WNS)”,并在必填字段中输入信息。 You need both in the Azure portal – open your notification hub, choose Settings > Notification Services > Windows (WNS) and enter the information in the required fields.

  6. 选择“保存” 。Choose Save.

  7. 在“解决方案资源管理器”中打开“引用”,并选择“管理 NuGet 包”。 Open References in Solution Explorer and select Manage NuGet Packages. 添加对 Azure 服务总线托管库的引用 - 只需搜索 WindowsAzure.Messaging.Managed 并将它添加到项目即可。Add a reference to the Azure Service Bus managed library – simply search for WindowsAzure.Messaging.Managed and add it to your project.

  8. 为了进行测试,可以再次创建 MainPage_Loaded 事件处理程序,并在其中添加以下代码片段:For testing purposes, create the MainPage_Loaded event handler once again, and add this code snippet to it:

    var channel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
    
    var hub = new NotificationHub("HUB_NAME", "HUB_LISTEN_CONNECTION_STRING");
    var result = await hub.RegisterNativeAsync(channel.Uri);
    
    // Displays the registration ID so you know it was successful
    if (result.RegistrationId != null)
    {
        Debug.WriteLine("Reg successful.");
    }
    

    该代码会将应用注册到通知中心。The code registers the app with the notification hub. 一切准备就绪!You are ready to go!

  9. LocationHelperGeolocator_PositionChanged 处理程序中,可以添加一段测试代码以强制将位置放入地域隔离区:In LocationHelper, inside the Geolocator_PositionChanged handler, you can add a piece of test code that forcefully puts the location inside the geofence:

    await LocationHelper.SendLocationToBackend("wns", "TEST_USER", "TEST", "37.7746", "-122.3858");
    
  10. 由于我们未传递实际坐标(目前这可能不在边界内),并且使用的是预定义测试值,因此更新时会看到显示的通知:Because you are not passing the real coordinates (which might not be within the boundaries at the moment) and are using predefined test values, you see a notification show up on update:

后续步骤Next steps

可能还需要执行几个步骤才能使解决方案可用于生产。There are a couple of steps that you might need to follow to make the solution production-ready.

  1. 首先,需要确保地域隔离区是动态的。First, you need to ensure that geofences are dynamic. 需要对必应 API 进行一些额外的处理,才能在现有数据源内上传新边界。It requires some extra work with the Bing API to be able to upload new boundaries within the existing data source. 有关详细信息,请参阅必应空间数据服务 API 文档For more information, see Bing Spatial Data Services API documentation.
  2. 其次,由于要确保向正确的参与者执行传送,因此可以通过 标记来锁定这些人。Second, as you are working to ensure that the delivery is done to the right participants, you might want to target them via tagging.

本教程中所示的解决方案描述了一种场景,其中可能有各种不同的目标平台,因此未限制只有系统特定的功能才能使用地域隔离。The solution shown in this tutorial describes a scenario in which you might have a wide variety of target platforms, so it does not limit the geofencing to system-specific capabilities. 也就是说,通用 Windows 平台可以提供现成的 地域隔离区检测功能。That said, the Universal Windows Platform offers capabilities to detect geofences right out-of-the-box.