使用 ASP.NET Web 服务 (ASMX)

ASMX 提供了构建 Web 服务的功能,这些服务可使用简单对象访问协议 (SOAP) 发送消息。 SOAP 是一种独立于平台和语言的协议,用于构建和访问 Web 服务。 ASMX 服务的使用者不需要了解用于实现该服务的平台、对象模型或编程语言。 他们只需要了解如何发送和接收 SOAP 消息。 本文演示如何从 Xamarin.Forms 应用程序使用 ASMX SOAP 服务。

SOAP 消息是包含以下元素的 XML 文档:

  • 一个名为 Envelope 的根元素,它用于将 XML 文档标识为 SOAP 消息。
  • 一个可选的 Header 元素,它包含特定于应用程序的信息,例如身份验证数据。 如果 Header 元素存在,则它必须是 Envelope 元素的第一个子元素。
  • 一个必需的 Body 元素,它包含面向收件人的 SOAP 消息。
  • 一个可选的 Fault 元素,它用于指示错误消息。 如果 Fault 元素存在,则它必须是 Body 元素的子元素。

SOAP 可以通过许多传输协议(包括 HTTP、SMTP、TCP 和 UDP)来运行。 但是,ASMX 服务只能通过 HTTP 运行。 Xamarin 平台支持 HTTP 上的标准 SOAP 1.1 实现,这包括对许多标准 ASMX 服务配置的支持。

此示例包括在物理或模拟设备上运行的移动应用程序,以及一个可提供方法来获取、添加、编辑和删除数据的 ASMX 服务。 当移动应用程序运行时,它们会连接到本地托管的 ASMX 服务,如以下屏幕截图所示:

示例应用程序

注意

在 iOS 9 及更高版本中,应用传输安全性 (ATS) 会在 Internet 资源(如应用的后端服务器)与应用之间的强制实施安全连接,从而防止意外泄露敏感信息。 由于为 iOS 9 生成的应用中默认启用了 ATS,因此所有连接都受 ATS 安全要求的约束。 如果连接不符合这些要求,它们将失败并出现异常。 如果无法对 Internet 资源使用 HTTPS 协议和安全通信,则可以选择退出 ATS。 这可以通过更新应用的 Info.plist 文件来实现。 有关详细信息,请参阅应用传输安全性

使用 Web 服务

ASMX 服务提供以下操作:

操作 说明 参数
GetTodoItems 获取待办事项的列表
CreateTodoItem 新建待办事项 XML 序列化的 TodoItem
EditTodoItem 更新待办事项 XML 序列化的 TodoItem
DeleteTodoItem 删除待办事项 XML 序列化的 TodoItem

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

创建 TodoService 代理

名为 TodoService 的代理类扩展 SoapHttpClientProtocol,并提供用于通过 HTTP 来与 ASMX 服务通信的方法。 通过向 Visual Studio 2019 或 Visual Studio 2017 中每个特定于平台的项目添加 Web 引用来生成代理。 Web 引用为服务的 Web Services 描述语言 (WSDL) 文档中定义的每个操作生成方法和事件。

例如,GetTodoItems 服务操作在代理中生成 GetTodoItemsAsync 方法和 GetTodoItemsCompleted 事件。 生成的方法具有 void 返回类型,并调用父类 SoapHttpClientProtocol 上的 GetTodoItems 操作。 当被调用的方法从服务收到响应时,它会触发 GetTodoItemsCompleted 事件并在事件的 Result 属性中提供响应数据。

创建 ISoapService 实现

为了使共享的跨平台项目能够使用该服务,该示例定义了 ISoapService 接口,该接口遵循 C# 中的任务异步编程模型。 每个平台会实现 ISoapService 来公开特定于平台的代理。 该示例使用 TaskCompletionSource 对象将代理公开为任务异步接口。 有关使用 TaskCompletionSource 的详细信息,请参阅以下各部分中每种操作类型的实现。

示例 SoapService

  1. TodoService 实例化为类级实例
  2. 创建名为 Items 的集合来存储 TodoItem 对象
  3. TodoService 上的可选 Url 属性指定自定义终结点
public class SoapService : ISoapService
{
    ASMXService.TodoService todoService;
    public List<TodoItem> Items { get; private set; } = new List<TodoItem>();

    public SoapService ()
    {
        todoService = new ASMXService.TodoService ();
        todoService.Url = Constants.SoapUrl;
        ...
    }
}

创建数据传输对象

示例应用程序使用 TodoItem 类对数据进行建模。 要将 TodoItem 项存储在 Web 服务中,必须首先将其转换为代理生成的 TodoItem 类型。 可通过 ToASMXServiceTodoItem 方法完成此操作,如以下代码示例所示:

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

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

类似地,当从 Web 服务检索数据时,必须将其从代理生成的 TodoItem 类型转换为 TodoItem 实例。 这是通过 FromASMXServiceTodoItem 方法完成的,如以下代码示例所示:

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

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

检索数据

ISoapService 接口要求 RefreshDataAsync 方法返回包含项集合的 Task。 但是,TodoService.GetTodoItemsAsync 方法将返回 void。 若要满足接口模式,必须调用 GetTodoItemsAsync,等待 GetTodoItemsCompleted 事件触发,然后填充集合。 这样就可以将有效的集合返回到 UI。

以下示例创建新的 TaskCompletionSource,在 RefreshDataAsync 方法中开始异步调用,并等待 TaskCompletionSource 提供的 Task。 调用 TodoService_GetTodoItemsCompleted 事件处理程序时,它会填充 Items 集合并更新 TaskCompletionSource

public class SoapService : ISoapService
{
    TaskCompletionSource<bool> getRequestComplete = null;
    ...

    public SoapService()
    {
        ...
        todoService.GetTodoItemsCompleted += TodoService_GetTodoItemsCompleted;
    }

    public async Task<List<TodoItem>> RefreshDataAsync()
    {
        getRequestComplete = new TaskCompletionSource<bool>();
        todoService.GetTodoItemsAsync();
        await getRequestComplete.Task;
        return Items;
    }

    private void TodoService_GetTodoItemsCompleted(object sender, ASMXService.GetTodoItemsCompletedEventArgs e)
    {
        try
        {
            getRequestComplete = getRequestComplete ?? new TaskCompletionSource<bool>();

            Items = new List<TodoItem>();
            foreach (var item in e.Result)
            {
                Items.Add(FromASMXServiceTodoItem(item));
            }
            getRequestComplete?.TrySetResult(true);
        }
        catch (Exception ex)
        {
            Debug.WriteLine(@"\t\tERROR {0}", ex.Message);
        }
    }

    ...
}

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

创建或编辑数据

创建或编辑数据时,必须实现 ISoapService.SaveTodoItemAsync 方法。 此方法检测 TodoItem 是新项还是更新的项,并调用 todoService 对象上的相应方法。 此外,还应实现 CreateTodoItemCompletedEditTodoItemCompleted 事件处理程序,以便知道 todoService 何时从 ASMX 服务收到了响应(这些处理程序可以合并为一个处理程序,因为它们执行相同的操作)。 以下示例演示了接口和事件处理程序实现,以及用于异步操作的 TaskCompletionSource 对象:

public class SoapService : ISoapService
{
    TaskCompletionSource<bool> saveRequestComplete = null;
    ...

    public SoapService()
    {
        ...
        todoService.CreateTodoItemCompleted += TodoService_SaveTodoItemCompleted;
        todoService.EditTodoItemCompleted += TodoService_SaveTodoItemCompleted;
    }

    public async Task SaveTodoItemAsync (TodoItem item, bool isNewItem = false)
    {
        try
        {
            var todoItem = ToASMXServiceTodoItem(item);
            saveRequestComplete = new TaskCompletionSource<bool>();
            if (isNewItem)
            {
                todoService.CreateTodoItemAsync(todoItem);
            }
            else
            {
                todoService.EditTodoItemAsync(todoItem);
            }
            await saveRequestComplete.Task;
        }
        catch (SoapException se)
        {
            Debug.WriteLine("\t\t{0}", se.Message);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("\t\tERROR {0}", ex.Message);
        }
    }

    private void TodoService_SaveTodoItemCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        saveRequestComplete?.TrySetResult(true);
    }

    ...
}

删除数据

删除数据需要类似的实现。 定义 TaskCompletionSource,实现事件处理程序和 ISoapService.DeleteTodoItemAsync 方法:

public class SoapService : ISoapService
{
    TaskCompletionSource<bool> deleteRequestComplete = null;
    ...

    public SoapService()
    {
        ...
        todoService.DeleteTodoItemCompleted += TodoService_DeleteTodoItemCompleted;
    }

    public async Task DeleteTodoItemAsync (string id)
    {
        try
        {
            deleteRequestComplete = new TaskCompletionSource<bool>();
            todoService.DeleteTodoItemAsync(id);
            await deleteRequestComplete.Task;
        }
        catch (SoapException se)
        {
            Debug.WriteLine("\t\t{0}", se.Message);
        }
        catch (Exception ex)
        {
            Debug.WriteLine("\t\tERROR {0}", ex.Message);
        }
    }

    private void TodoService_DeleteTodoItemCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
    {
        deleteRequestComplete?.TrySetResult(true);
    }

    ...
}

测试 Web 服务

使用本地托管的服务测试物理或模拟设备需要准备好自定义 IIS 配置、终结点地址和防火墙规则。 有关如何设置测试环境的更多详细信息,请参阅配置对 IIS Express 的远程访问。 WCF 和 ASMX 测试的唯一差别在于 TodoService 的端口号。