使用 Windows Communication Foundation (WCF) Web 服务

下载示例 下载示例

WCF 是 Microsoft 用于生成面向服务的应用程序的统一框架。 它使开发人员能够构建安全、可靠、交易和可互操作的分布式应用程序。 本文演示如何从 Xamarin.Forms 应用程序使用 WCF 简单对象访问协议 (SOAP) 服务。

WCF 描述具有各种不同协定的服务,包括:

  • 数据协定 – 定义构成消息中内容基础的数据结构。
  • 消息协定 - 从现有数据协定撰写消息。
  • 故障协定 - 允许指定自定义 SOAP 错误。
  • 服务协定 - 指定服务支持的操作以及与每个操作交互所需的消息。 它们还指定可与每个服务上的操作关联的任何自定义错误行为。

ASP.NET Web 服务 (ASMX) 和 WCF 之间存在差异,但 WCF 支持 ASMX 提供的相同功能 - 通过 HTTP 发送 SOAP 消息。 有关使用 ASMX 服务的详细信息,请参阅 使用 ASP.NET Web Services (ASMX)

重要

Xamarin 平台对 WCF 的支持仅限于使用 BasicHttpBinding 类通过 HTTP/HTTPS 进行文本编码的 SOAP 消息。

WCF 支持要求使用仅在 Windows 环境中可用的工具来生成代理并托管 TodoWCFService。 生成和测试 iOS 应用需要在 Windows 计算机上部署 TodoWCFService,或部署为 Azure Web 服务。

Xamarin Forms 本机应用通常与 .NET Standard 类库共享代码。 但是,.NET Core 当前不支持 WCF,因此共享项目必须是旧的可移植类库。 有关 .NET Core 中的 WCF 支持的信息,请参阅在服务器应用的 .NET Core 和.NET Framework之间进行选择

示例应用程序解决方案包括可在本地运行的 WCF 服务,如以下屏幕截图所示:

示例应用程序

注意

在 iOS 9 及更高版本中,应用传输安全 (ATS) 在 Internet 资源 ((如应用的后端服务器) )与应用之间强制实施安全连接,从而防止意外泄露敏感信息。 由于在为 iOS 9 生成的应用中默认启用 ATS,因此所有连接都将受到 ATS 安全要求的约束。 如果连接不满足这些要求,它们将失败并出现异常。

如果无法对 Internet 资源使用协议和安全通信, HTTPS 则可以选择退出 ATS。 这可以通过更新应用的 Info.plist 文件来实现。 有关详细信息,请参阅 应用传输安全性

使用 Web 服务

WCF 服务提供以下操作:

操作 说明 parameters
GetTodoItems 获取待办事项的列表
CreateTodoItem 创建新的“未办事项” XML 序列化的 TodoItem
EditTodoItem 更新待办事项 XML 序列化的 TodoItem
DeleteTodoItem 删除待办事项 XML 序列化的 TodoItem

有关应用程序中使用的数据模型的详细信息,请参阅 对数据建模

必须生成 代理 才能使用 WCF 服务,这样应用程序就可以连接到该服务。 代理是通过使用定义方法和关联服务配置的服务元数据构造的。 此元数据以 Web 服务生成的 Web 服务描述语言 (WSDL) 文档的形式公开。 可以使用 Visual Studio 2017 中的 Microsoft WCF Web Service Reference 提供程序将 Web 服务的服务引用添加到 .NET Standard 库来生成代理。 在 Visual Studio 2017 中使用 Microsoft WCF Web Service Reference提供程序创建代理的替代方法是使用 ServiceModel 元数据实用工具 (svcutil.exe) 。 有关详细信息,请参阅 ServiceModel 元数据实用工具 (Svcutil.exe)

生成的代理类提供使用异步编程模型 (APM) 设计模式的 Web 服务的方法。 在此模式中,异步操作作为两个名为 BeginOperationNameEndOperationName 的方法实现,该方法开始和结束异步操作。

BeginOperationName 方法开始异步操作,并返回实现 接口的对象IAsyncResult。 调用 BeginOperationName 后,应用程序可以在调用线程上继续执行指令,而异步操作发生在线程池线程上。

对于每次调用 BeginOperationName,应用程序还应调用 EndOperationName 以获取操作的结果。 EndOperationName 的返回值与同步 Web 服务方法返回的类型相同。 例如, EndGetTodoItems 方法返回 实例的 TodoItem 集合。 EndOperationName 方法还包括一个IAsyncResult参数,该参数应设置为由对 BeginOperationName 方法的相应调用返回的实例。

任务并行库 (TPL) 可以通过在同 Task 一对象中封装异步操作来简化使用 APM 开始/结束方法对的过程。 此封装由 方法的 TaskFactory.FromAsync 多个重载提供。

有关 APM 的详细信息,请参阅 MSDN 上的异步编程模型TPL 和传统.NET Framework异步编程

创建 TodoServiceClient 对象

生成的代理类提供 TodoServiceClient 类,该类用于通过 HTTP 与 WCF 服务通信。 它提供从 URI 标识的服务实例调用 Web 服务方法作为异步操作的功能。 有关异步操作的详细信息,请参阅 异步支持概述

实例 TodoServiceClient 在类级别声明,以便只要应用程序需要使用 WCF 服务,对象就可生存,如以下代码示例所示:

public class SoapService : ISoapService
{
  ITodoService todoService;
  ...

  public SoapService ()
  {
    todoService = new TodoServiceClient (
      new BasicHttpBinding (),
      new EndpointAddress (Constants.SoapUrl));
  }
  ...
}

实例 TodoServiceClient 配置了绑定信息和终结点地址。 绑定用于指定应用程序和服务相互通信所需的传输、编码和协议详细信息。 BasicHttpBinding指定将通过 HTTP 传输协议发送文本编码的 SOAP 消息。 指定终结点地址使应用程序能够连接到 WCF 服务的不同实例,前提是有多个已发布的实例。

有关配置服务引用的详细信息,请参阅 配置服务引用

创建数据传输对象

示例应用程序使用 TodoItem 类为数据建模。 若要将项 TodoItem 存储在 Web 服务中,必须先将其转换为代理生成的 TodoItem 类型。 这由 ToWCFServiceTodoItem 方法完成,如以下代码示例所示:

TodoWCFService.TodoItem ToWCFServiceTodoItem (TodoItem item)
{
  return new TodoWCFService.TodoItem
  {
    ID = item.ID,
    Name = item.Name,
    Notes = item.Notes,
    Done = item.Done
  };
}

此方法只创建一个新 TodoWCFService.TodoItem 实例,并将每个属性设置为实例中的相同属性 TodoItem

同样,从 Web 服务检索数据时,必须将其从代理生成的 TodoItem 类型转换为 TodoItem 实例。 这是使用 FromWCFServiceTodoItem 方法完成的,如以下代码示例所示:

static TodoItem FromWCFServiceTodoItem (TodoWCFService.TodoItem item)
{
  return new TodoItem
  {
    ID = item.ID,
    Name = item.Name,
    Notes = item.Notes,
    Done = item.Done
  };
}

此方法只是从代理生成的 TodoItem 类型检索数据,并在新创建的 TodoItem 实例中设置它。

检索数据

TodoServiceClient.BeginGetTodoItemsTodoServiceClient.EndGetTodoItems 方法用于调用 GetTodoItems Web 服务提供的操作。 这些异步方法封装在 对象中 Task ,如以下代码示例所示:

public async Task<List<TodoItem>> RefreshDataAsync ()
{
  ...
  var todoItems = await Task.Factory.FromAsync <ObservableCollection<TodoWCFService.TodoItem>> (
    todoService.BeginGetTodoItems,
    todoService.EndGetTodoItems,
    null,
    TaskCreationOptions.None);

  foreach (var item in todoItems)
  {
    Items.Add (FromWCFServiceTodoItem (item));
  }
  ...
}

方法Task.Factory.FromAsync创建一个 ,Task该方法完成后执行TodoServiceClient.EndGetTodoItemsTodoServiceClient.BeginGetTodoItems该方法,并使用 null 参数指示未向BeginGetTodoItems委托传递任何数据。 最后, 枚举的值 TaskCreationOptions 指定应使用创建和执行任务的默认行为。

方法TodoServiceClient.EndGetTodoItems返回 ObservableCollection 实例的 TodoWCFService.TodoItem ,然后将其转换为 实例的 TodoItem 以便List显示。

创建数据

TodoServiceClient.BeginCreateTodoItemTodoServiceClient.EndCreateTodoItem 方法用于调用 CreateTodoItem Web 服务提供的操作。 这些异步方法封装在 对象中 Task ,如以下代码示例所示:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  ...
  var todoItem = ToWCFServiceTodoItem (item);
  ...
  await Task.Factory.FromAsync (
    todoService.BeginCreateTodoItem,
    todoService.EndCreateTodoItem,
    todoItem,
    TaskCreationOptions.None);
  ...
}

方法Task.Factory.FromAsync创建一个 ,Task该方法在TodoServiceClient.BeginCreateTodoItem方法完成后执行TodoServiceClient.EndCreateTodoItem该方法,参数todoItem是传递到BeginCreateTodoItem委托的数据,以指定要TodoItem由 Web 服务创建的 。 最后, 枚举的值 TaskCreationOptions 指定应使用创建和执行任务的默认行为。

如果 Web 服务无法创建TodoItem由应用程序处理的 ,则会引发 FaultException

更新数据

TodoServiceClient.BeginEditTodoItemTodoServiceClient.EndEditTodoItem 方法用于调用 EditTodoItem Web 服务提供的操作。 这些异步方法封装在 对象中 Task ,如以下代码示例所示:

public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
{
  ...
  var todoItem = ToWCFServiceTodoItem (item);
  ...
  await Task.Factory.FromAsync (
    todoService.BeginEditTodoItem,
    todoService.EndEditTodoItem,
    todoItem,
    TaskCreationOptions.None);
  ...
}

方法Task.Factory.FromAsync创建一个 ,Task该方法在TodoServiceClient.BeginCreateTodoItem方法完成后执行TodoServiceClient.EndEditTodoItem该方法,参数todoItem是传递到BeginEditTodoItem委托的数据,以指定要TodoItem由 Web 服务更新的 。 最后, 枚举的值 TaskCreationOptions 指定应使用创建和执行任务的默认行为。

如果 Web 服务无法找到或更新TodoItem由应用程序处理的 ,则会引发 FaultException

删除数据

TodoServiceClient.BeginDeleteTodoItemTodoServiceClient.EndDeleteTodoItem 方法用于调用 DeleteTodoItem Web 服务提供的操作。 这些异步方法封装在 对象中 Task ,如以下代码示例所示:

public async Task DeleteTodoItemAsync (string id)
{
  ...
  await Task.Factory.FromAsync (
    todoService.BeginDeleteTodoItem,
    todoService.EndDeleteTodoItem,
    id,
    TaskCreationOptions.None);
  ...
}

方法Task.Factory.FromAsync创建一个 ,Task该方法在方法TodoServiceClient.BeginDeleteTodoItem完成后执行TodoServiceClient.EndDeleteTodoItem方法,参数id是传递到BeginDeleteTodoItem委托中的数据,以指定要TodoItem由 Web 服务删除的 。 最后,枚举的值 TaskCreationOptions 指定应使用创建和执行任务的默认行为。

如果 Web 服务找不到或删除TodoItem由应用程序处理的 ,则会引发 FaultException

配置对 IIS Express 的远程访问

在 Visual Studio 2017 或 Visual Studio 2019 中,你应该能够在无需其他配置的情况下在电脑上测试 UWP 应用程序。 测试 Android 和 iOS 客户端可能需要本部分中的其他步骤。 有关详细信息 ,请参阅从 iOS 模拟器和 Android 模拟器连接到本地 Web 服务

默认情况下,IIS Express将仅响应对 localhost的请求。 (远程设备(如 Android 设备、iPhone 甚至模拟器) )将无法访问本地 WCF 服务。 需要知道本地网络上Windows 10工作站 IP 地址。 就此示例而言,假设工作站的 IP 地址 192.168.1.143为 。 以下步骤说明如何配置Windows 10和IIS Express以接受远程连接并从物理或虚拟设备连接到服务:

  1. 向 Windows 防火墙添加例外。 必须通过 Windows 防火墙打开一个端口,子网上的应用程序可以使用该端口与 WCF 服务通信。 在防火墙中创建一个打开端口 49393 的入站规则。 在管理命令提示符下,运行以下命令:

    netsh advfirewall firewall add rule name="TodoWCFService" dir=in protocol=tcp localport=49393 profile=private remoteip=localsubnet action=allow
    
  2. 将IIS Express配置为接受远程连接。 可以通过在 [solution directory].vs\config\applicationhost.config处编辑IIS Express的配置文件来配置IIS Express。site查找名为 TodoWCFService的元素。 它应类似于以下 XML:

    <site name="TodoWCFService" id="2">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
            <virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:49393:localhost" />
        </bindings>
    </site>
    

    需要添加两个 binding 元素,以便向外部流量和 Android 模拟器打开端口 49393。 绑定使用指定[IP address]:[port]:[hostname]IIS Express响应请求的方式的格式。 外部请求将具有必须指定为 的 binding主机名。 将以下 XML 添加到 元素, bindings 将 IP 地址替换为自己的 IP 地址:

    <binding protocol="http" bindingInformation="*:49393:192.168.1.143" />
    <binding protocol="http" bindingInformation="*:49393:127.0.0.1" />
    

    更改后, bindings 元素应如下所示:

    <site name="TodoWCFService" id="2">
        <application path="/" applicationPool="Clr4IntegratedAppPool">
            <virtualDirectory path="/" physicalPath="C:\Users\tom\TodoWCF\TodoWCFService\TodoWCFService" />
        </application>
        <bindings>
            <binding protocol="http" bindingInformation="*:49393:localhost" />
            <binding protocol="http" bindingInformation="*:49393:192.168.1.143" />
            <binding protocol="http" bindingInformation="*:49393:127.0.0.1" />
        </bindings>
    </site>
    

    重要

    默认情况下,出于安全原因,IIS Express不接受来自外部源的连接。 若要启用来自远程设备的连接,必须运行具有管理权限IIS Express。 执行此操作的最简单方法是使用管理权限运行 Visual Studio 2017。 运行 TodoWCFService 时,这将启动具有管理权限的IIS Express。

    完成这些步骤后,应能够运行 TodoWCFService 并从子网上的其他设备进行连接。 可以通过运行应用程序并访问 http://localhost:49393/TodoService.svc来对此进行测试。 如果在访问该 URL 时收到“错误请求”错误,bindings则IIS Express配置中可能 (请求已到达IIS Express但) 被拒绝。 如果收到其他错误,可能是应用程序未运行或防火墙配置不正确。

    若要允许IIS Express继续运行和提供服务,请关闭项目属性 > Web > 调试器中的“编辑并继续”选项。

  3. 自定义用于访问服务的终结点设备。 此步骤涉及配置在物理或模拟设备上运行的客户端应用程序以访问 WCF 服务。

    Android 模拟器利用内部代理来阻止模拟器直接访问主机的 localhost 地址。 而是通过内部代理将模拟器上的地址 10.0.2.2 路由到 localhost 主机上。 这些代理的请求将作为127.0.0.1请求标头中的主机名,这就是在上述步骤中为此主机名创建IIS Express绑定的原因。

    iOS 模拟器在 Mac 生成主机上运行,即使使用的是 适用于 Windows 的远程 iOS 模拟器。 来自模拟器的网络请求将具有本地网络上的工作站 IP 作为主机名 (在本示例中为 192.168.1.143,但实际 IP 地址可能) 不同。 这就是在上述步骤中为此主机名创建IIS Express绑定的原因。

    SoapUrl确保 TodoWCF (Portable) 项目中 Constants.cs 文件中的属性具有正确的网络值:

    public static string SoapUrl
    {
        get
        {
            var defaultUrl = "http://localhost:49393/TodoService.svc";
    
            if (Device.RuntimePlatform == Device.Android)
            {
                defaultUrl = "http://10.0.2.2:49393/TodoService.svc";
            }
            else if (Device.RuntimePlatform == Device.iOS)
            {
                defaultUrl = "http://192.168.1.143:49393/TodoService.svc";
            }
    
            return defaultUrl;
        }
    }
    

    使用适当的终结点配置 Constants.cs 后,应能够从物理或虚拟设备连接到Windows 10工作站上运行的 TodoWCFService。