Web 服务简介
本指南演示如何使用不同的 Web 服务技术。 涉及的主题包括与 REST 服务、SOAP 服务和 Windows Communication Foundation 服务进行通信。
为了正常运行,许多移动应用程序都依赖于云,因此,将 Web 服务集成到移动应用程序是一种常见方案。 Xamarin 平台支持使用不同的 Web 服务技术,并包括对使用 RESTful、ASMX 和 Windows Communication Foundation (WCF) 服务的内置和第三方支持。
对于使用 Xamarin.Forms 的客户,Xamarin.Forms Web 服务文档中提供了使用这些技术的完整示例。
重要
在 iOS 9 中,应用传输安全性 (ATS) 强制 Internet 资源(如应用的后端服务器)与应用之间的安全连接,从而防止意外泄露敏感信息。 由于为 iOS 9 生成的应用中默认启用了 ATS,因此所有连接都受 ATS 安全要求的约束。 如果连接不符合这些要求,它们将失败并出现异常。
如果无法对 Internet 资源使用 HTTPS
协议和安全通信,则可以选择退出 ATS。 这可以通过更新应用的 Info.plist 文件来实现。 有关详细信息,请参阅应用传输安全性。
REST
表述性状态转移 (REST) 是一种用于生成 Web 服务的体系结构样式。 Web 浏览器使用 HTTP 谓词来检索网页以及向服务器发送数据,REST 请求使用与此相同的 HTTP 谓词通过 HTTP 发出。 谓词如下:
- GET - 此操作用于从 Web 服务检索数据。
- POST - 此操作用于在 Web 服务上创建新的数据项。
- PUT - 此操作用于更新 Web 服务上的数据项。
- PATCH - 此操作用于更新 Web 服务上的数据项,方法是描述有关如何修改此项的一组说明。 示例应用程序中未使用此谓词。
- DELETE - 此操作用于删除 Web 服务上的数据项。
遵循 REST 的 Web 服务 API 称为 RESTful API,并使用以下资源来定义:
- 一个基 URI。
- HTTP 方法,例如 GET、POST、PUT、PATCH 或 DELETE。
- 数据的媒体类型,例如 JavaScript 对象表示法 (JSON)。
REST 的简单性使其成为在移动应用程序中访问 Web 服务的主要方法。
使用 REST 服务
有许多库和类可以用于 REST 服务,以下小节将对它们进行讨论。 有关使用 REST 服务的详细信息,请参阅使用 RESTful Web 服务。
HttpClient
Microsoft HTTP 客户端库提供用于通过 HTTP 发送和接收请求的 HttpClient
类。 它提供用于发送 HTTP 请求和从 URI 标识的资源接收 HTTP 响应的功能。 每个请求都作为异步操作发送。 有关异步操作的详细信息,请参阅异步支持概述。
HttpResponseMessage
类表示在发出 HTTP 请求后从 Web 服务接收到的 HTTP 响应消息。 它包含有关响应的信息,包括状态代码、标头和正文。 HttpContent
类表示 HTTP 正文和内容标头,例如 Content-Type
与 Content-Encoding
。 可使用任何 ReadAs
方法(如 ReadAsStringAsync
和 ReadAsByteArrayAsync
)读取内容,具体取决于数据的格式。
有关类 HttpClient
的详细信息,请参阅 创建 HTTPClient 对象。
HTTPWebRequest
使用 HTTPWebRequest
调用 Web 服务涉及:
- 为特定 URI 创建请求实例。
- 在请求实例上设置各种 HTTP 属性。
- 从请求中检索
HttpWebResponse
。 - 从响应中读取数据。
例如,以下代码从美国国家医学图书馆 Web 服务中检索数据:
var rxcui = "198440";
var request = HttpWebRequest.Create(string.Format(@"https://rxnav.nlm.nih.gov/REST/RxTerms/rxcui/{0}/allinfo", rxcui));
request.ContentType = "application/json";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
if (response.StatusCode != HttpStatusCode.OK)
Console.Out.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
var content = reader.ReadToEnd();
if(string.IsNullOrWhiteSpace(content)) {
Console.Out.WriteLine("Response contained empty body...");
}
else {
Console.Out.WriteLine("Response Body: \r\n {0}", content);
}
Assert.NotNull(content);
}
}
上面的示例创建了一个 HttpWebRequest
,它将返回格式化为 JSON 的数据。 数据在 HttpWebResponse
中返回,可从中获取 StreamReader
以读取数据。
RestSharp
使用 REST 服务的另一种方法是使用 RestSharp 库。 RestSharp 可封装 HTTP 请求,包括支持检索作为原始字符串内容或反序列化的 C# 对象的结果。 例如,以下代码向美国国家医学图书馆 Web 服务发出请求,并检索作为 JSON 格式化字符串的结果:
var request = new RestRequest(string.Format("{0}/allinfo", rxcui));
request.RequestFormat = DataFormat.Json;
var response = Client.Execute(request);
if(string.IsNullOrWhiteSpace(response.Content) || response.StatusCode != System.Net.HttpStatusCode.OK) {
return null;
}
rxTerm = DeserializeRxTerm(response.Content);
DeserializeRxTerm
是一种方法,该方法将从 RestSharp.RestResponse.Content
属性中提取原始 JSON 字符串并将其转换为 C# 对象。 本文稍后将讨论如何对 Web 服务返回的数据进行反序列化。
NSUrlConnection
除了 Mono 基类库 (BCL)(如 HttpWebRequest
)和第三方 C# 库(如 RestSharp)中提供的类外,平台特定类也可用于 Web 服务。 例如,在 iOS 中,可以使用 NSUrlConnection
和 NSMutableUrlRequest
类。
以下代码示例演示如何使用 iOS 类来调用美国国家医学图书馆 Web 服务:
var rxcui = "198440";
var request = new NSMutableUrlRequest(new NSUrl(string.Format("https://rxnav.nlm.nih.gov/REST/RxTerms/rxcui/{0}/allinfo", rxcui)),
NSUrlRequestCachePolicy.ReloadRevalidatingCacheData, 20);
request["Accept"] = "application/json";
var connectionDelegate = new RxTermNSURLConnectionDelegate();
var connection = new NSUrlConnection(request, connectionDelegate);
connection.Start();
public class RxTermNSURLConnectionDelegate : NSUrlConnectionDelegate
{
StringBuilder _ResponseBuilder;
public bool IsFinishedLoading { get; set; }
public string ResponseContent { get; set; }
public RxTermNSURLConnectionDelegate()
: base()
{
_ResponseBuilder = new StringBuilder();
}
public override void ReceivedData(NSUrlConnection connection, NSData data)
{
if(data != null) {
_ResponseBuilder.Append(data.ToString());
}
}
public override void FinishedLoading(NSUrlConnection connection)
{
IsFinishedLoading = true;
ResponseContent = _ResponseBuilder.ToString();
}
}
通常,用于 Web 服务的平台特定类应该局限于将本机代码移植到 C# 的场景。 如果可能,Web 服务访问代码应该是可移植的,以便能够跨平台共享。
ServiceStack
用于调用 Web 服务的另一个选项是服务堆栈库。 例如,以下代码演示如何使用服务堆栈的 IServiceClient.GetAsync
方法发出服务请求:
client.GetAsync<CustomersResponse>("",
(response) => {
foreach(var c in response.Customers) {
Console.WriteLine(c.CompanyName);
}
},
(response, ex) => {
Console.WriteLine(ex.Message);
});
重要
虽然可通过 ServiceStack 和 RestSharp 等工具轻松调用和使用 REST 服务,但使用不符合标准 DataContract 序列化约定的 XML 或 JSON 有时并不容易。 如有必要,请使用下面讨论的 ServiceStack.Text 库来调用请求并显式处理适当的序列化。
使用 RESTful 数据
RESTful Web 服务通常使用 JSON 消息将数据返回到客户端。 JSON 是一种基于文本的数据交换格式,可生成压缩负载,这可在发送数据时降低带宽需求。 在本部分,将探讨在 JSON 和纯旧式 XML (POX) 中使用 RESTful 响应的机制。
System.JSON
Xamarin 平台支持开箱即用的 JSON。 通过使用 JsonObject
,可以检索结果,如以下代码示例所示:
var obj = JsonObject.Parse(json);
var properties = obj["rxtermsProperties"];
term.BrandName = properties["brandName"];
term.DisplayName = properties["displayName"];
term.Synonym = properties["synonym"];
term.FullName = properties["fullName"];
term.FullGenericName = properties["fullGenericName"];
term.Strength = properties["strength"];
但是,请务必注意,System.Json
工具会将整个数据加载到内存中。
JSON.NET
NewtonSoft JSON.NET 库是一种广泛使用的库,用于序列化和反序列化 JSON 消息。 以下代码示例演示如何使用 JSON.NET 将 JSON 消息反序列化为 C# 对象:
var term = new RxTerm();
var properties = JObject.Parse(json)["rxtermsProperties"];
term.BrandName = properties["brandName"].Value<string>();
term.DisplayName = properties["displayName"].Value<string>();
term.Synonym = properties["synonym"].Value<string>();;
term.FullName = properties["fullName"].Value<string>();;
term.FullGenericName = properties["fullGenericName"].Value<string>();;
term.Strength = properties["strength"].Value<string>();
term.RxCUI = properties["rxcui"].Value<string>();
ServiceStack.Text
ServiceStack.Text 是一种 JSON 序列化库,专为与 ServiceStack 库配合使用而设计。 以下代码示例演示如何使用 ServiceStack.Text.JsonObject
分析 JSON:
var result = JsonObject.Parse(json).Object("rxtermsProperties")
.ConvertTo(x => new RxTerm {
BrandName = x.Get("brandName"),
DisplayName = x.Get("displayName"),
Synonym = x.Get("synonym"),
FullName = x.Get("fullName"),
FullGenericName = x.Get("fullGenericName"),
Strength = x.Get("strength"),
RxTermDoseForm = x.Get("rxtermsDoseForm"),
Route = x.Get("route"),
RxCUI = x.Get("rxcui"),
RxNormDoseForm = x.Get("rxnormDoseForm"),
});
System.Xml.Linq
使用基于 XML 的 REST Web 服务时,LINQ to XML 可用于分析 XML 并以内联方式填充 C# 对象,如以下代码示例所示:
var doc = XDocument.Parse(xml);
var result = doc.Root.Descendants("rxtermsProperties")
.Select(x=> new RxTerm()
{
BrandName = x.Element("brandName").Value,
DisplayName = x.Element("displayName").Value,
Synonym = x.Element("synonym").Value,
FullName = x.Element("fullName").Value,
FullGenericName = x.Element("fullGenericName").Value,
//bind more here...
RxCUI = x.Element("rxcui").Value,
});
ASP.NET Web 服务 (ASMX)
ASMX 提供了构建 Web 服务的功能,这些服务可使用简单对象访问协议 (SOAP) 发送消息。 SOAP 是一种独立于平台和语言的协议,用于构建和访问 Web 服务。 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 服务,它允许应用程序连接到该服务。 代理是通过使用定义方法和关联服务配置的服务元数据来构造的。 此元数据以 Web 服务生成的 Web Services 描述语言 (WSDL) 文档的形式公开。 代理是通过以下方法构建的,即使用 Visual Studio for Mac 或 Visual Studio 将 Web 服务的 Web 引用添加到特定于平台的项目。
Web 服务 URL 可以是托管的远程源,也可以是可通过 file:///
路径前缀访问的本地文件系统资源,例如:
file:///Users/myUserName/projects/MyProjectName/service.wsdl
这会在项目的 Web 或服务引用文件夹中生成代理。 由于代理是生成的代码,因此不应对其进行修改。
手动将代理添加到项目
如果你拥有使用兼容工具生成的现有代理,则当作为项目的一部分包含时,可以使用此输出。 在 Visual Studio for Mac 中,使用“添加文件...”菜单选项添加代理。 此外,这要求使用“添加引用...”对话框来显式引用 System.Web.Services.dll。
使用代理
生成的代理类提供了使用 Web 服务的方法,该服务使用异步编程模型 (APM) 设计模式。 在此模式中,异步操作实现为 BeginOperationName 和 EndOperationName 这两个方法,它们用于开始和结束异步操作。
BeginOperationName 方法用于开始异步操作,并返回实现 IAsyncResult
接口的对象。 调用 BeginOperationName 后,应用可以继续对调用线程执行指令,同时异步操作在线程池线程上运行。
每次调用 BeginOperationName 时,应用还应调用 EndOperationName,以获取操作结果。 EndOperationName 的返回值与同步 Web 服务方法返回的类型相同。 下面的代码示例演示了此示例:
public async Task<List<TodoItem>> RefreshDataAsync ()
{
...
var todoItems = await Task.Factory.FromAsync<ASMXService.TodoItem[]> (
todoService.BeginGetTodoItems,
todoService.EndGetTodoItems,
null,
TaskCreationOptions.None);
...
}
任务并行库 (TPL) 可以通过在同一 Task
对象中封装异步操作来简化使用 APM 开始/结束方法对的过程。 此封装由 Task.Factory.FromAsync
方法的多个重载提供。 此方法将创建一个 Task
,它在 TodoService.BeginGetTodoItems
方法完成后执行 TodoService.EndGetTodoItems
方法,其中 null
参数表示没有数据传递到 BeginGetTodoItems
委托中。 最后,TaskCreationOptions
枚举的值指定了为任务创建和执行使用的默认行为。
有关 APM 的详细信息,请参阅 MSDN 上的异步编程模型和 TPL 和传统 .NET Framework 异步编程。
有关使用 ASMX 服务的详细信息,请参阅使用 ASP.NET Web 服务 (ASMX)。
Windows Communication Foundation (WCF)
WCF 是 Microsoft 为生成面向服务的应用程序而提供的统一框架。 它使开发人员能够构建安全、可靠、可互操作的分布式应用程序。
WCF 描述了具有各种不同协定的服务,其中包括:
- 数据协定 – 定义构成消息中内容基础的数据结构。
- 消息协定 - 撰写来自现有数据协定的消息。
- 错误协定 – 允许指定自定义 SOAP 错误。
- 服务协定 – 指定服务支持的操作以及与每个操作进行交互所需的消息。 它们还指定可以与每个服务操作关联的任何自定义错误行为。
ASP.NET Web 服务 (ASMX) 与 WCF 之间存在差异,但重要的是要理解 WCF 支持 ASMX 提供的相同功能 – 通过 HTTP 发送的 SOAP 消息。
重要
Xamarin 平台对 WCF 的支持仅限于使用 BasicHttpBinding
类通过 HTTP/HTTPS 发送的文本编码的 SOAP 消息。 此外,WCF 支持要求使用仅在 Windows 环境中可用的工具来生成代理。
生成代理
必须生成代理才能使用 WCF 服务,它允许应用程序连接到该服务。 代理是通过使用定义方法和关联服务配置的服务元数据来构造的。 此元数据以 Web 服务生成的 Web Services 描述语言 (WSDL) 文档的形式公开。 可以使用 Visual Studio 2017 中的 Microsoft WCF Web Service Reference 提供程序来生成代理,以将 Web 服务的服务引用添加到 .NET 标准库。
在 Visual Studio 2017 中使用 Microsoft WCF Web Service Reference 提供程序创建代理的替代方法是使用 ServiceModel 元数据实用工具 (svcutil.exe)。 有关详细信息,请参阅 ServiceModel 元数据实用工具 (Svcutil.exe)。
配置代理
在初始化期间,配置生成的代理通常采用两个配置参数(具体取决于 SOAP 1.1/ASMX 或 WCF):EndpointAddress
和/或关联的绑定信息,如以下示例所示:
var binding = new BasicHttpBinding () {
Name= "basicHttpBinding",
MaxReceivedMessageSize = 67108864,
};
binding.ReaderQuotas = new System.Xml.XmlDictionaryReaderQuotas() {
MaxArrayLength = 2147483646,
MaxStringContentLength = 5242880,
};
var timeout = new TimeSpan(0,1,0);
binding.SendTimeout= timeout;
binding.OpenTimeout = timeout;
binding.ReceiveTimeout = timeout;
client = new Service1Client (binding, new EndpointAddress ("http://192.168.1.100/Service1.svc"));
使用绑定指定应用程序与服务彼此通信所需的传输、编码和协议详细信息。 BasicHttpBinding
指定将通过 HTTP 传输协议发送文本编码的 SOAP 消息。 指定终结点地址使应用程序能够连接到 WCF 服务的不同实例(如果存在多个已发布的实例)。
使用代理
生成的代理类提供了使用 Web 服务的方法,这些服务使用异步编程模型 (APM) 设计模式。 在此模式中,异步操作实现为 BeginOperationName 和 EndOperationName 这两个方法,它们用于开始和结束异步操作。
BeginOperationName 方法用于开始异步操作,并返回实现 IAsyncResult
接口的对象。 调用 BeginOperationName 后,应用可以继续对调用线程执行指令,同时异步操作在线程池线程上运行。
每次调用 BeginOperationName 时,应用还应调用 EndOperationName,以获取操作结果。 EndOperationName 的返回值与同步 Web 服务方法返回的类型相同。 下面的代码示例演示了此示例:
public async Task<List<TodoItem>> RefreshDataAsync ()
{
...
var todoItems = await Task.Factory.FromAsync <ObservableCollection<TodoWCFService.TodoItem>> (
todoService.BeginGetTodoItems,
todoService.EndGetTodoItems,
null,
TaskCreationOptions.None);
...
}
任务并行库 (TPL) 可以通过在同一 Task
对象中封装异步操作来简化使用 APM 开始/结束方法对的过程。 此封装由 Task.Factory.FromAsync
方法的多个重载提供。 此方法将创建一个 Task
,它在 TodoServiceClient.BeginGetTodoItems
方法完成后执行 TodoServiceClient.EndGetTodoItems
方法,其中 null
参数表示没有数据传递到 BeginGetTodoItems
委托中。 最后,TaskCreationOptions
枚举的值指定了为任务创建和执行使用的默认行为。
有关 APM 的详细信息,请参阅 MSDN 上的异步编程模型和 TPL 和传统 .NET Framework 异步编程。
有关使用 WCF 服务的详细信息,请参阅使用 Windows Communication Foundation (WCF) Web Service。
使用传输安全性
WCF 服务可以使用传输级安全性来防止消息被拦截。 Xamarin 平台支持采用传输级安全性(使用 SSL)的绑定。 但是,在某些情况下,堆栈可能需要验证证书,这会导致意外的行为。 通过在调用服务之前注册 ServerCertificateValidationCallback
委托来覆盖验证,如以下代码示例所示:
System.Net.ServicePointManager.ServerCertificateValidationCallback +=
(se, cert, chain, sslerror) => { return true; };
这将保持传输加密,同时忽略服务器端证书验证。 但是,此方法实际上会忽略与证书关联的信任问题,因此可能并不合适。 有关详细信息,请参阅 mono-project.com 上的“谨慎使用受信任的根”。
使用客户端凭据安全性
WCF 服务可能还要求服务客户端使用凭据进行身份验证。 Xamarin 平台不支持 WS-Security 协议,该协议允许客户端在 SOAP 消息信封内发送凭据。 但是,Xamarin 平台支持通过指定合适的 ClientCredentialType
方法将 HTTP 基本身份验证凭据发送到服务器:
basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
然后,可以指定基本身份验证凭据:
client.ClientCredentials.UserName.UserName = @"foo";
client.ClientCredentials.UserName.Password = @"mrsnuggles";
有关 HTTP 基本身份验证的详细信息,尽管在 REST Web 服务的上下文中,请参阅对 RESTful Web 服务进行身份验证。