大多数前端应用程序使用HTTP协议与后端服务进行通信。 Dart网络应用程序通常使用XMLHttpRequest(XHR)API执行此操作,使用dart:html库中的HttpRequest或更高级别的API(例如http包提供的内容)。
以下演示使用http软件包来说明服务器通信:
此页的demo使用了http包的Client接口. 下面的代码为Client注册了一个 factory provider (创建了一个 BrowserClient 实例) :
web/main.dart (v1)
import 'package:angular/angular.dart';
import 'package:http/browser_client.dart';
import 'package:http/http.dart';
import 'package:server_communication/app_component.dart';
void main() {
bootstrap(AppComponent, [
provide(Client, useFactory: () => new BrowserClient(), deps: [])
]);
}
此demo是Tour of Heroes应用程序的缩小版本. 它从服务中接收heroes并且在列表中展示它们.用户可以添加一个新的Hero并且保存到服务端.
下面是应用程序的UI:
此demo有一个单独的组件, HeroListComponent. 下面是它的模板:
lib/src/toh/hero_list_component.html
<h1>Tour of Heroes</h1>
<h3>Heroes:</h3>
<ul>
<li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>
<label>New hero name: <input #newHeroName /></label>
<button (click)="addHero(newHeroName.value); newHeroName.value=''">Add Hero</button>
<p class="error" *ngIf="errorMessage != null">{{errorMessage}}</p>
模板的ngFor指令显示heroes列表.列表下面是输入框和Add Hero按钮,允许用户添加新的英雄.
一个模板引用变量, newHeroName, 赋予(click)事件绑定存取输入框的值. 当用户单击按钮时, 单击处理程序传递输入值到addHero()方法. 单击处理程序清空输入框.
按钮下面是错误消息区域.
这是组件类:lib/src/toh/hero_list_component.dart (class)
class HeroListComponent implements OnInit {
final HeroService _heroService;
String errorMessage;
List<Hero> heroes = [];
HeroListComponent(this._heroService);
Future<Null> ngOnInit() => getHeroes();
Future<Null> getHeroes() async {
try {
heroes = await _heroService.getHeroes();
} catch (e) {
errorMessage = e.toString();
}
}
Future<Null> addHero(String name) async {
name = name.trim();
if (name.isEmpty) return;
try {
heroes.add(await _heroService.create(name));
} catch (e) {
errorMessage = e.toString();
}
}
}
Angular 注入 HeroService 到构造器,组件调用服务提取和保存数据.
组件不直接与Client作用.替而代之,它委派数据到HeroService.
始终将数据访问权委派给支持的服务类。
虽然 在运行时组件在创建之后立即请求heroes, 此请求 不在组件的构造器内. 替而代之,请求在ngOnInit生命周期钩子.
保持构造器简单。 当组件的构造器很简单时,组件更容易测试和调试,而所有真正的工作(如调用远程服务器)都是由单独的方法处理的。
hero 服务中的异步方法, getHeroes() 和 create(), 返回Future值(当前英雄列表和最近添加的英雄), 各自地. 英雄列表组件中的方法, getHeroes() 和addHero(), 指定当异步方法调用成功或失败时采取的操作.
关于Future的更多信息,查看 futures tutorial 资源在指导的最后.
在之前的示例中,应用通过返回服务中的模拟英雄来伪造与服务器的交互:
import 'dart:async';
import 'package:angular/angular.dart';
import 'hero.dart';
import 'mock_heroes.dart';
@Injectable()
class HeroService {
Future<List<Hero>> getHeroes() async => mockHeroes;
}
是时候获得真实的数据了。 以下代码使HeroService从服务器获取英雄:
lib/src/toh/hero_service.dart (revised)
import 'dart:async';
import 'dart:convert';
import 'package:angular/angular.dart';
import 'package:http/http.dart';
import 'hero.dart';
@Injectable()
class HeroService {
static final _headers = {'Content-Type': 'application/json'};
static const _heroesUrl = 'api/heroes'; // URL to web API
final Client _http;
HeroService(this._http);
Future<List<Hero>> getHeroes() async {
try {
final response = await _http.get(_heroesUrl);
final heroes = _extractData(response)
.map((value) => new Hero.fromJson(value))
.toList();
return heroes;
} catch (e) {
throw _handleError(e);
}
}
Future<Hero> create(String name) async {
try {
final response = await _http.post(_heroesUrl,
headers: _headers, body: JSON.encode({'name': name}));
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
此demo 使用一个Client对象,注入到HeroService构造器中:
HeroService(this._http);
下面的代码使用client的get()方法取得数据:
lib/src/toh/hero_service.dart (getHeroes)
static const _heroesUrl = 'api/heroes'; // URL to web API
Future<List<Hero>> getHeroes() async {
try {
final response = await _http.get(_heroesUrl);
final heroes = _extractData(response)
.map((value) => new Hero.fromJson(value))
.toList();
return heroes;
} catch (e) {
throw _handleError(e);
}
}
get()方法取得资源URL, 用来连接返回英雄数据的服务器.
如果还没有服务器存在,或者想要在测试期间避免网络可靠性问题,请不要将BrowserClient作为Client对象。 相反,您可以通过使用内存中的Web API来模拟服务器,这是实例(源代码)的作用。
或者,使用JSON文件:
static const _heroesUrl = 'heroes.json'; // URL to JSON file
getHeroes()方法使用 _extractData() 助手方法映射 _http.get()响应对象到 heroes:
lib/src/toh/hero_service.dart (excerpt)
dynamic _extractData(Response resp) => JSON.decode(resp.body)['data'];
response对象不能在表单中持有数据应用程序能立即使用.使用响应数据, 首先要解码它.
响应数据采用JSON字符串形式。 您必须将该字符串反序列化为对象,您可以通过调用dart:convert库中的JSON.decode()方法来执行此操作。 有关解码和编码JSON的示例,请参阅Dart库游览的dart:convert部分。
码后的JSON不会列出英雄。 相反,服务器将JSON结果封装到具有数据属性的对象中。 这是传统的Web API行为,受安全问题驱动。
不要假设服务器API。 并非所有的服务器都返回一个带有数据属性的对象
尽管getHeroes()有可能返回HTTP响应,但这不是一个好习惯。 数据服务的重点在于隐藏消费者的服务器交互细节。 调用HeroService的组件只需要heroes。 它与负责获取数据的代码以及响应对象分离。
处理I / O的一个重要部分是通过准备捕捉它们并与它们做某些事情来预测错误。 处理错误的一种方法是将错误消息传回组件,以便呈现给用户,但前提是该消息是用户可以理解并采取行动的内容。
这个简单的应用程序处理getHeroes()错误,如下所示:
lib/src/toh/hero_service.dart (excerpt)
Future<List<Hero>> getHeroes() async {
try {
final response = await _http.get(_heroesUrl);
final heroes = _extractData(response)
.map((value) => new Hero.fromJson(value))
.toList();
return heroes;
} catch (e) {
throw _handleError(e);
}
}
Exception _handleError(dynamic e) {
print(e); // for demo purposes only
return new Exception('Server error; cause: $e');
}
在HeroListComponent中, _heroService.getHeroes()在一个try子句中, errorMessage 变量有条件的绑定在模板中.errorMessage 变量将被指定一个值:
lib/src/toh/hero_list_component.dart (getHeroes)
Future<Null> getHeroes() async {
try {
heroes = await _heroService.getHeroes();
} catch (e) {
errorMessage = e.toString();
}
}
要创建失败场景,请在HeroService中将API端点重置为错误值。 之后,请记住恢复其原始值。
已经知道了如何使用远程HTTP服务恢复数据.下一项任务是添加增加英雄并保存到后端的能力.
首先, 服务需要一个组件能够调用来创建和保存一个英雄的方法. 对于此demo, 方法叫做 create() 并且接收新英雄的name:
Future<Hero> create(String name) async {
实现这个方法,你需要知道创建英雄服务的API. 这个简单的数据服务遵循典型的REST指导方针. 它支持一个POST请求 和GET heroes使用了同样的端点. 新英雄数据必须在请求体中,结构如同一个Hero 实体但是没有id 属性.下面是例子的请求体:
{ "name": "Windstorm" }
服务器生成id并返回新英雄的JSON表示,包括生成的ID。 英雄在一个拥有自己data属性的响应对象中。
现在你已经知道了服务器的API,下面是create()的实现:
lib/src/toh/hero_service.dart (create)
Future<Hero> create(String name) async {
try {
final response = await _http.post(_heroesUrl,
headers: _headers, body: JSON.encode({'name': name}));
return new Hero.fromJson(_extractData(response));
} catch (e) {
throw _handleError(e);
}
}
在_headers对象中, Content-Type指定响应体使用JSON数据格式.
如同在getHeroes()中, _extractData() 帮助器从response中提取数据.
返回到HeroListComponent中, addHero() 方法 等待服务的异步方法create() 创建一个英雄. 当 create() 执行完成时, addHero() 添加一个新英雄到 heroes 列表:
lib/src/toh/hero_list_component.dart (addHero)
Future<Null> addHero(String name) async {
name = name.trim();
if (name.isEmpty) return;
try {
heroes.add(await _heroService.create(name));
} catch (e) {
errorMessage = e.toString();
}
}
尽管在Dart web 应用程序中使用XMLHttpRequests (通常使用助手API, 例如 BrowserClient)进行服务器通信是一种常见的方法,但此方法并不总是合适.
考虑到安全因素, 浏览器阻止XHR访问远程服务器(与web页不在同一个源). 源 是URI 方案, 主机名, 和端口号组成的. 被称作same-origin方针.
如果服务器支持CORS协议,现代浏览器允许来自不同来源的服务器的XHR请求。 您可以在请求标头中启用用户凭据。
一些服务器不支持CORS但支持旧的形式, 只读的JSONP.
有关JSONP的更多信息,请参阅Stack Overflow。
下面的例子展示Wikipedia用户在文本框中打字:
Wikipedia 提议了一个CORS API 和一个兼容的 JSONP 搜索 API.
本页面正在建设中。 现在,请参阅演示源代码以获取使用Wikipedia的JSONP API的示例。