资源描述
创建ASP.NET Web API 2.0应用实例
由于ASP.NET Web API具有与ASP.NET MVC类似的编程方式,再加上目前市面上专门介绍ASP.NET Web API 的书籍少之又少(我们看到的相关内容往往是某本介绍ASP.NET MVC的书籍“额外奉送”的),以至于很多人会觉得ASP.NET Web API仅仅是ASP.NET MVC的一个小小的扩展而已,自身并没有太多“大书特书”的地方。而真实的情况下是:ASP.NET Web API不仅仅具有一个完全独立的消息处理管道,而且这个管道比为ASP.NET MVC设计的管道更为复杂,功能也更为强大。虽然被命名为“ASP.NET Web API”,但是这个消息处理管道却是独立于ASP.NET平台的,这也是为什么ASP.NET Web API支持多种寄宿方式的根源所在。
为 了让读者朋友们先对ASP.NET Web API具有一个感性认识,接下来我们以实例演示的形式创建一个简单的ASP.NET Web API应用。这是一个用于实现“联系人管理”的单页Web应用,我们以Ajax的形式调用Web API实现针对联系人的CRUD操作。
一、构建解决方案
Visual Studio为我们提供了专门用于创建ASP.NET Web API应用的项目模板,借助于此项目模板提供的向导,我们可以“一键式”创建一个完整的ASP.NET Web API项目。在项目创建过程中,Visual Studio会自动为我们添加必要的程序集引用和配置,甚至会为我们自动生成相关的代码,总之一句话:这种通过向导生成的项目在被创建之后其本身就是一个 可执行的应用。
对于IDE提供的这种旨在提高生产效率的自动化机制,我个人自然是推崇的,但是我更推荐读者朋友们去了解一下这些自动化机制具体为我们做了什么?做这些的目的何在?哪些是必需的,哪些又是不必要的?正是基于这样的目的,在接下来演示的实例中,我们将摒弃Visual Studio为我们提供的向导,完全在创建的空项目中编写我们的程序。这些空项目体现在如右图所示的解决方案结构中。
如右图所示,整个解决方案一共包含6个项目,上面介绍的作为“联系人管理器”的单页Web应用对应着项目WebApp,下面的列表给出了包括它在内的所有项目的类型和扮演的角色。
·Common:这是一个空的类库项目,仅仅定义了表示联系人的数据类型而已。之所以将数据类型定义在独立的项目中,只要是考虑到它会被多个项目(WebApi和ConsoleApp)所使用。
WebApi:这是一个空的类库项目,表现为HttpController类型的Web API就定义在此项目中,它具有对Common的项目引用。
WebHost:这是一个空的ASP.NET Web应用,它实现了针对ASP.NET Web API的Web Host寄宿,该项目具有针对WebApi的项目引用。
SelfHost:这是一个空的控制台应用,旨在模拟ASP.NET Web API的Self Host寄宿模式,它同样具有针对WebApi的项目引用。
WebApp:这是一个空的ASP.NET Web应用,代表“联系人管理器”的网页就存在于该项目之中,至于具体的联系人管理功能,自然通过以Ajax的形式调用Web API来完成。
ConsoleApp:这是一个空的控制台应用,我们用它来模拟如何利用客户端代理来实现对Web API的远程调用,它具有针对Common的项目引用。
二、定义Web API
在正式定义Web API之前,我们需要在项目Common中定义代表联系人的数据类型Contact。简单起见,我们仅仅为Contact定义了如下几个简单的属性,它们分别代表联系人的ID、姓名、联系电话、电子邮箱和联系地址。
1: public class Contact
2: {
3: public string Id { get; set; }
4: public string Name { get; set; }
5: public string PhoneNo { get; set; }
6: public string EmailAddress { get; set; }
7: public string Address { get; set; }
8: }
表现为HttpController的Web API定义在WebApi项目之中,我们一般将ApiController作为继承的基类。ApiController定义在 “System.Web.Http.dll”程序集中,我们可以在目录“%ProgramFiles%\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages\”中找到这个程序集。具体来说,该程序集存在于子目录“Microsoft.AspNet.WebApi.Core.5.0.0 \lib\net45”中。
Web API体现在如下所示的ContactsController类型中。在该类型中,我们定义了Get、Post、Put和Delete这4个Action 方法,它们分别实现了针对联系人的查询、添加、修改和删除操作。Action方法Get具有一个表示联系人ID的可缺省参数,如果该参数存在则返回对应的 联系人,否则返回整个联系人列表。由于ASP.NET Web API默认实现了Action方法与HTTP方法的映射,所以方法名也体现了它们各自所能处理请求必须采用的HTTP方法。
1: public class ContactsController: ApiController
2: {
3: static List<Contact> contacts;
4: static int counter = 2;
5:
6: static ContactsController()
7: {
8: contacts = new List<Contact>();
9: contacts.Add(new Contact { Id = "001", Name = "张三",
10: PhoneNo = "0512-12345678", EmailAddress = "zhangsan@",
11: Address = "江苏省苏州市星湖街328号" });
12: contacts.Add(new Contact { Id = "002", Name = "李四",
13: PhoneNo = "0512-23456789", EmailAddress = "lisi@",
14: Address = "江苏省苏州市金鸡湖大道328号" });
15: }
16:
17: public IEnumerable<Contact> Get(string id = null)
18: {
19: return from contact in contacts
20: where contact.Id == id || string.IsNullOrEmpty(id)
21: select contact;
22: }
23:
24: public void Post(Contact contact)
25: {
26: Interlocked.Increment(ref counter);
27: contact.Id = counter.ToString("D3");
28: contacts.Add(contact);
29: }
30:
31: public void Put(Contact contact)
32: {
33: contacts.Remove(contacts.First(c => c.Id == contact.Id));
34: contacts.Add(contact);
35: }
36:
37: public void Delete(string id)
38: {
39: contacts.Remove(contacts.First(c => c.Id == id));
40: }
41: }
简单起见,我们利用一个静态字段(contacts)表示存储的联系人列表。当ContactsController类型被加载的时候,我们添加了 两个ID分别为“001”和“002”的联系人记录。至于实现联系人CRUD操作的Action方法,我们也省略了必要的验证,对于本书后续的演示的实 例,我们基本上也会采用这种“简写”的风格。
三、以Web Host方式寄宿Web API
我们在上面已经提到过了,虽然被命名为ASP.NET Web API,但是其核心的消息处理管道却是独立于ASP.NET平台的,所以我们可以对相同的Web API实施不同的寄宿方式。寄宿的本质就是利用一个具体的应用程序为Web API提供一个运行的环境,并最终解决“请求的接收和响应的回复”问题。作为寄宿的一种主要形式,Web Host就是创建一个ASP.NET Web应用作为Web API的宿主。
采用Web Host方式寄宿Web API的宿主程序WebHost是一个空的ASP.NET应用。除了让它引用定义ContactsController的WebApi项目之外,我们还需 要为其添加如下这些必需的程序集引用。除了程序集“System.Net.Http.dll”(它属于.NET Framework 原生的程序集)之外,其余3个均可以在目录“%ProgramFiles%\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages\”中找到。
System.Web.Http.dll(\ Microsoft.AspNet.WebApi.Core.5.0.0\lib\net45\)
System.Net.Formatting.Http.dll(\Microsoft.AspNet.WebApi.Client.5.0.0\lib\net45\)
System.Web.Http.WebHost.dll(\Microsoft.AspNet.WebApi.WebHost.5.0.0\lib\net45\)
System.Net.Http.dll
与ASP.NET MVC一样,如果采用Web Host的方式来寄宿Web API,ASP.NET自身的路由系统会成为接收请求的第一道屏障。在将请求递交给ASP.NET Web API自己的消息处理管道之前,路由系统会解析出当前请求访问的目标HttpController和Action的名称。我们需要做的就是根据需求注册相 应的路由,这也是采用Web Host寄宿方式所需的唯一操作。
我们在WebHost项目中添加一个Global.asax文件,并按照如下的形式在其Application_Start方法中注册了一个模板为 “api/{controller}/{id}”的路由。此模板由3部分组成,静态文本“api”表示其前缀,后面是两个路由参数。前者 ({controller})表示目标HttpController的名称,后者({id})可以映射为目标Action方法的同名参数(比如 ContractsController的Get方法的参数id),这是一个可以缺省的路由参数(RouteParameter.Optional)。
1: public class Global : System.Web.HttpApplication
2: {
3: protected void Application_Start(object sender, EventArgs e)
4: {
5: GlobalConfiguration.Configuration.Routes.MapHttpRoute(
6: Name : "DefaultApi",
7: routeTemplate : "api/{controller}/{id}",
8: defaults : new { id = RouteParameter.Optional });
9: }
10: }
如上面的代码片断所示,路由注册是通过调用代表全局路由表的HttpRouteCollection对象的扩展方法MapHttpRoute来完成 的。GlobalConfiguration的静态属性Configuration返回一个代表当前配置的HttpConfiguration对象,全局 路由表就注册在它的Routes属性上。
如果你了解ASP.NET MVC的路由注册,可能觉得奇怪:注册路由的模板中并没有表示目标Action的路由参数,ASP .NET Web API如何根据请求确定哪个Action方法应该被调用呢?答案其实很简单:它能根据请求采用HTTP方法来确定目标Action方法。当然,在注册路由模板中提供代表Action名称的路由参数({action})也是支持的。
在默认情况下,通过Visual Studio(VS 2012或者VS 2013,本书采用的是后者)创建的Web应用总是使用IIS Express作为服务器,它会自动为我们指定一个可用的端口号。为了更好地模拟真实发布环境,同时避免“跨域资源共享”带来的困扰,我们采用本地IIS 作为服务器。如下图所示,WebHost项目在IIS中映射的Web应用采用的URL为“http://localhost/webhost”。
实际上到此为止,Web API的Web Host寄宿工作就已经完成,我们可以利用浏览器来调用寄宿的Web API来判断寄宿工作是否成功。由于浏览器在默认情况下访问我们在地址栏中输入的地址总是采用HTTP-GET请求,所以我们只能利用它来调用支持 HTTP-GET的Action方法,即定义在ContactsController中的Get方法。
根据我们注册的路由,如果我们访问目标地址“http://localhost/webhost/api/contacts”可以获得所有联系人列 表;如果目标地址为“http://localhost/webhost/api/contacts/001”,则可以得到ID为“001”的联系人信 息,右图证实了这一点。
从右图可以看到,我们采用的浏览器为Chrome,获取的联系人列表总是表示为XML,这是为什么呢?在前面介绍REST的时候,我们曾经提及一种 旨在识别客户端期望的资源表示形式并被称为“内容协商”的机制,它可以根据请求携带的相关信息来判断客户端所期望的响应资源表现形式。
对于ASP.NET Web API来说,它会优先利用请求报头“Accept”携带的媒体类型来确定响应内容采用的表现形式。如下所示的是Chrome访问 “http://localhost/webhost/api/contacts/001”发送请求的内容,它之所以会得到以XML表示的响应是因为 “Accept”报头指定的媒体类型列表中只有“application/xml”被ASP.NET Web API支持。如果我们采用IE,请求的“Accept”报头将携带不同的媒体类型列表,我们实际上会得到以JSON格式表示的响应结果。
1: GET http://localhost/webhost/api/contacts/001 HTTP/1.1
2: Host: localhost
3: Connection: keep-alive
4: Cache-Control: max-age=0
5: Accept: text/html,application/xhtml+xml,application/xml ;q=0.9,image/webp,*/*;q=0.8
6: User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36
7: Accept-Encoding: gzip,deflate,sdch
8: Accept-Language: en-US,en;q=0.8,zh-CN;q=0.6,zh-TW;q=0.4
为了进一步验证并演示ASP.NET Web API的内容协商机制,我们现在改用Fiddler来发送调用Web API的HTTP请求。如左图所示,我们利用Fiddler发送了一个针对目标地址“http://localhost/webhost/api /contacts/001”的HTTP-GET请求,并添加了一个值为“application/json”的“Accept”报头,请求发送之后确实 得到了以JSON格式表示的联系人列表。
支持PUT和DELETE请求
在定义ContactsController的时候,我们严格按照RESTful Web API关于“使用标准的HTTP方法”的指导方针,分别采用GET、POST、PUT和DELETE作为获取、创建、修改和删除联系人的操作所支持的 HTTP方法。但是IIS在默认情况下并不提供针对 PUT和DELETE请求的支持。
如右图所示,我们利用Fiddler发送了一个针对地址“http://localhost/webhost/api/contacts/001” 的HTTP-DELETE请求,旨在删除ID为“001”的联系人。但是遗憾的是,我们得到了一个状态为“405,Method Not Allowed”的响应,意味着服务端并不支持HTTP-DELETE方法。
IIS拒绝PUT和DELETE请求是由默认注册的一个名为“WebDAVModule”的自定义HttpModule导致的。WebDAV的全称 为“Web-based Distributed Authoring and Versioning”,它是一个在多用户之间辅助协同编辑和管理在线文档的HTTP扩展。该扩展使应用程序可以直接将文件写到 Web Server 上,同时支持文件的加锁和版本控制。
微软是推动WebDAV成为一个标准的主导力量,它自己利用自定义的HttpModule实现了IIS针对WebDAV的支持。但是这个默认注册 (注册名称为“WebDAVModule”)会拒绝HTTP方法为PUT和DELETE的请求,如果我们的站点不需要提供针对WebDAV的支持,解决这 个问题最为直接的方式就是利用如下的配置将注册的HttpModule移除。
1: <configuration>
2: ...
3: <system.webServer>
4: <modules runAllManagedModulesForAllRequests="true">
5: <remove name="WebDAVModule" />
6: </modules>
7: </system.webServer>
8: </configuration>
四、 以Self Host方式寄宿Web API
与WCF类似,寄宿Web API不一定需要IIS的支持,我们可以采用Self Host的方式使用任意类型的应用程序(控制台、Windows Forms应用、WPF应用甚至是Windows Service)作为宿主。对于我们演示的实例来说,项目SelfHost代表的控制台程序就是一个采用Self Host寄宿模式的宿主。
对于SelfHost这么一个空的控制台应用来说,除了需要添加针对WebApi的项目引用之外,还需要添加如下4个程序集引用。除了程序集 “System.Net.Http.dll”(它属于.NET Framework 原生的程序集)之外,其余3个均可以在目录“%ProgramFiles%\Microsoft ASP.NET\ASP.NET Web Stack 5\Packages\”中找到。
System.Web.Http.dll(\ Microsoft.AspNet.WebApi.Core.5.0.0\lib\net45\)
System.Net.Formatting.Http.dll(\Microsoft.AspNet.WebApi.Client.5.0.0\lib\net45\)
System.Web.Http.SelfHost.dll(\Microsoft.AspNet.WebApi.SelfHost.5.0.0\lib\net45\)
System.Net.Http.dll
通过上面的介绍我们可以看到以Web Host的方式寄宿Web API需要做的唯一一件事情是路由注册。但是对于Self Host来说,除了必需的路由注册外,我们还需要完成额外的一件事情,即手工加载定义了HttpController类型的程序集。整个寄宿工作通过如下 几行简单的代码就可以实现。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: Assembly.Load("WebApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
6:
7: HttpSelfHostConfiguration configuration = new HttpSelfHostConfiguration("http://localhost/selfhost");
8: using (HttpSelfHostServer httpServer = new HttpSelfHostServer(configuration))
9: {
10: httpServer.Configuration.Routes.MapHttpRoute(
11: name : "DefaultApi",
12: routeTemplate : "api/{controller}/{id}",
13: defaults : new { id = RouteParameter.Optional });
14:
15: httpServer.OpenAsync();
16: Console.Read();
17: }
18: }
19: }
ASP.NET Web API的Self Host寄宿方式通过HttpSelfHostServer来完成。如上面的代码片断所示,在手工加载了定义ContactsController类型的 程序集“WebApi.dll”之后,我们根据指定的基地址(“http://localhost/selfhost”),注册路由的URL模板将是以此 作为基地址的相对地址)创建了一个HttpSelfHostConfiguration对象,HttpSelfHostServer由该对象创建。接下 来,我们利用创建的HttpSelfHostConfiguration对象(对应着HttpSelfHostServer的Configuration 属性)的Routes得到全局路由表,并调用扩展方法MapHttpRoute注册了与Web Host寄宿方式一样的路由。当我们调用OpenAsync方法成功开启HttpSelfHostServer之后,服务器开始监听来自网络的调用请求。
如果读者朋友们对WCF比较熟悉的话,应该清楚在进行WCF服务寄宿的时候我们必须指定寄宿服务的类型,但是对于ASP.NET Web API的寄宿来说,不论是Web Host还是Self Host,我们都无需指定HttpController的类型。换句话说,WCF服务寄宿是针对具体某个服务类型的,而ASP.NET Web API的寄宿则是批量进行的。
ASP.NET Web API的批量寄宿源自它对HttpController类型的智能解析,它会从“提供的”的程序集列表中解析出所有HttpController类型(所 有实现了IHttpController接口的类型)。对于Web Host来说,它会利用BuildManager获得当前项目直接或者间接引用的程序集,但是对于Self Host来说,HttpController类型的解析在默认情况下只会针对加载到当前应用程序域中的程序集列表,这也是我们为何需要手工加载定义了 ContactsController类型的程序集的原因所在。
如果现在运行这个作为宿主的控制台程序,我们依然可以对寄宿其中的Web API发起调用。同样采用浏览器作为测试工具,在分别访问目标地址“http://localhost/selfhost/api/contacts”和 “http://localhost/selfhost/api/contacts/001”后,我们依然会得到上面的结果。
五、利用HttpClient调用Web API
对于一个.NET客户端程序,它可以利用HttpClient来进行Web API的调用。由于Web API的调用本质上就是一次普通的发送请求/接收响应的过程,所以HttpClient其实可以作为一般意义上发送HTTP请求的工具。在 ConsoleApp代表的控制台应用中,我们利用HttpClient来调用以Self Host方式寄宿的Web API。
由于我们需要使用到代表联系人的数据类型Contact,所以需要为该项目添加针对Common的项目引用。HttpClient定义在程序集 “System.Net.Http.dll”中,所以针对该程序集的引用也是必需的。除此之外,我们还需要添加针对程序集 “System.Net.Formatting.Http.dll”的引用,因为序列化请求和反序列化响应的相关类型定义在此程序集中。
如下所示的是整个Web API调用程序的定义,我们利用HttpClient调用Web API实现了针对联系人的获取、添加、修改和删除。由于HttpClient提供的大部分方法都采用针对Task的异步编程形式,所以我们将所有的操作定 义在一个标记为“async”的静态方法Process中,以便我们可以使用“await”关键字编写同步代码。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: Process();
6: Console.Read();
7: }
8:
9: private async static void Process()
10: {
11: //获取当前联系人列表
12: HttpClient httpClient = new HttpClient();
13: HttpResponseMessage response = await httpClient.GetAsync("http://localhost/selfhost/api/contacts");
14: IEnumerable<Contact> contacts = await response.Content.ReadAsAsync<IEnumerable<Contact>>();
15: Console.WriteLine("当前联系人列表:");
16: ListContacts(contacts);
17:
18: //添加新的联系人
19: Contact contact = new Contact { Name = "王五", PhoneNo = "0512-34567890", EmailAddress = "wangwu@" };
20: await httpClient.PostAsJsonAsync<Contact>("http://localhost/selfhost/api/contacts", contact);
21: Console.WriteLine("添加新联系人“王五”:");
22: response = await httpClient.GetAsync("http://localhost/selfhost/api/contacts");
23: contacts = await response.Content.ReadAsAsync<IEnumerable<Contact>>();
24: ListContacts(contacts);
25:
26: //修改现有的某个联系人
27: response = await httpClient.GetAsync("http://localhost/selfhost/api/contacts/001");
28: contact = (await response.Content.ReadAsAsync<IEnumerable<Contact>>()).First();
29: contact.Name = "赵六";
30: contact.EmailAddress = "zhaoliu@";
31: await httpClient.PutAsJsonAsync<Contact>("http://localhost/selfhost/api/contacts/001", contact);
32: Console.WriteLine("修改联系人“001”信息:");
33: response = await httpClient.GetAsync("http://localhost/selfhost/api/contacts");
34: contacts = await response.Content.ReadAsAsync<IEnumerable<Contact>>();
35: ListContacts(contacts);
36:
37: //删除现有的某个联系人
38: await httpClient.DeleteAsync("http://localhost/selfhost/api/contacts/002");
39: Console.WriteLine("删除联系人“002”:");
40: response = await httpClient.GetAsync("http://localhost/selfhost/api/contacts");
41: contacts = await response.Content.ReadAsAsync<IEnumerable<Contact>>();
42: ListContacts(contacts);
43: }
44:
45: private static void ListContacts(IEnumerable<Contact> contacts)
46: {
47: foreach (Contact contact in contacts)
48: {
49: Console.WriteLine("{0,-6}{1,-6}{2,-20}{3,-10}", contact.Id, contact.Name, contact.EmailAddress, contact.PhoneNo);
50: }
51: Console.WriteLine();
52: }
53: }
如上面的代码片段所示,我们创建了一个HttpClient对象并调用其GetAsync方法向目标地址“http://localhost /selfhost/api/contacts”发送了一个GET请求,返回的对象HttpResponseMessage表示接收到的响应。该 HttpResponseMessage对象的Content属性返回一个表示响应主体内容的HttpContent对象,我们调用其 ReadAsAsync<T>方法读取响应主体内容并将其反序列化成一个Contact集合。我们将表示当前联系人列表的Contact集合 输出在控制台上。
我们接下来调用HttpClient的PostAsJsonAsync<T>方法向目标地址“http://localhost /selfhost/api/contacts”发送一个POST请求以添加一个新的联系人。正如方法名称所体现的,作为参数的Contact对象将以 JSON格式被写入请求的主体部分。请求被正常发送并接收到响应之后,我们会打印出当前联系人列表。
在此之后,我们向目标地址“http://localhost/selfhost/api/contacts/001”发送一个GET请求以获取 ID为“001”的联系人。
展开阅读全文