Web API 2 支持一种新型的路由,称为属性路由。属性路由的一般概述,请参阅属性路由 Web API 2 中。在本教程中,您将使用属性路由创建一个 REST API 集合的书。API 将支持以下操作 ︰

行动 URI 的示例
得到的所有书的列表。 / api/书
得到一本书的 id。 /api/books/1
获得一本书的详细信息。 /api/books/1/details
按流派获得书籍的列表。 /api/books/fantasy
按出版日期获取书籍的列表。 /api/books/date/2013-02-16
/api/books/date/2013/02/16 (备用窗体)
获取一个特定作者的书籍列表。 /api/authors/1/books

所有方法都是只读的 (HTTP GET 请求)。

对于数据层,我们将使用实体框架。本书记录将有以下字段 ︰

  • ID
  • 标题
  • 体裁
  • 出版日期
  • 价格
  • 描述
  • 作者 Id (Authors 表的外键)

然而,对于大多数请求,API 将返回此数据 (标题、 作者和流派) 的一个子集。要获得完整的记录,客户端请求/api/books/{id}/details.

系统必备组件

视觉工作室 2013年视觉工作室表示 2013

创建 Visual Studio 项目

首先运行 Visual Studio。从文件菜单中,选择新建,然后选择项目.

模板窗格中,选择已安装的模板和展开Visual C#节点。在Visual C#,选择Web。在项目模板的列表中,选择ASP.NET MVC 4 Web 应用程序。"BooksAPI"为项目命名。

新的 ASP.NET 项目对话框中,选择的模板。在"添加文件夹和核心的参考文件"下选择Web API复选框。单击创建项目.

这将创建一个为 Web API 功能配置的骨架项目。

域模型

接下来,添加域模型的类。在解决方案资源管理器中,右键单击模型文件夹。选择添加,然后选择类别。名称类Author.

Author.cs 中的代码替换为以下内容 ︰

using System.ComponentModel.DataAnnotations;

namespace BooksAPI.Models
{
public class Author
{
public int AuthorId { get; set; }
[Required]
public string Name { get; set; }
}
}

现在,添加名为Book的另一个类.

using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; namespace BooksAPI.Models
{
public class Book
{
public int BookId { get; set; }
[Required]
public string Title { get; set; }
public decimal Price { get; set; }
public string Genre { get; set; }
public DateTime PublishDate { get; set; }
public string Description { get; set; }
public int AuthorId { get; set; }
[ForeignKey("AuthorId")]
public Author Author { get; set; }
}
}

添加 Web API 控制器

在此步骤中,我们将添加一个 Web API 控制器,使用实体框架和数据层。

按 CTRL + SHIFT + B 来生成项目。实体框架使用反射来发现性能的模型,所以它需要在编译的程序集来创建数据库模式。

在解决方案资源管理器中,右键单击控制器文件夹。选择添加,然后选择控制器.

添加脚手架对话框中,选择"Web API 2 控制器读取/写入操作,使用实体框架。"

添加控制器对话框中,为控制器的名称,输入"例子"。选中"使用异步控制器操作"复选框。对于模型类,请选择"书"。(如果你看不到下拉列表中列出的Book类,请确保生成项目时。然后单击"< 新数据上下文...>"按钮。

新的数据上下文的对话框中,单击添加

添加控制器对话框中,单击添加。脚手架将添加一个名为BooksController定义 API 控制器类。它还添加了一个名为BooksAPIContext模型文件夹,为实体框架定义的数据上下文类。

种子的数据库

从工具菜单中,选择库软件包管理器,然后选择程序包管理器控制台.

在程序包管理器控制台窗口中,输入以下命令 ︰

enable-migrations

此命令创建迁移文件夹并添加一个名为 Configuration.cs 的新代码文件。打开此文件并将下面的代码添加到Configuration.Seed方法。

protected override void Seed(BooksAPI.Models.BooksAPIContext context)
{
context.Authors.AddOrUpdate(new Author[] {
new Author() { AuthorId = 1, Name = "Ralls, Kim" },
new Author() { AuthorId = 2, Name = "Corets, Eva" },
new Author() { AuthorId = 3, Name = "Randall, Cynthia" },
new Author() { AuthorId = 4, Name = "Thurman, Paula" }
}); context.Books.AddOrUpdate(new Book[] {
new Book() { BookId = 1, Title= "Midnight Rain", Genre = "Fantasy",
PublishDate = new DateTime(2000, 12, 16), AuthorId = 1, Description =
"A former architect battles an evil sorceress.", Price = 14.95M }, new Book() { BookId = 2, Title = "Maeve Ascendant", Genre = "Fantasy",
PublishDate = new DateTime(2000, 11, 17), AuthorId = 2, Description =
"After the collapse of a nanotechnology society, the young" +
"survivors lay the foundation for a new society.", Price = 12.95M }, new Book() { BookId = 3, Title = "The Sundered Grail", Genre = "Fantasy",
PublishDate = new DateTime(2001, 09, 10), AuthorId = 2, Description =
"The two daughters of Maeve battle for control of England.", Price = 12.95M }, new Book() { BookId = 4, Title = "Lover Birds", Genre = "Romance",
PublishDate = new DateTime(2000, 09, 02), AuthorId = 3, Description =
"When Carla meets Paul at an ornithology conference, tempers fly.", Price = 7.99M }, new Book() { BookId = 5, Title = "Splish Splash", Genre = "Romance",
PublishDate = new DateTime(2000, 11, 02), AuthorId = 4, Description =
"A deep sea diver finds true love 20,000 leagues beneath the sea.", Price = 6.99M},
});
}

在程序包管理器控制台窗口中,键入以下命令。

add-migration Initial
update-database

这些命令创建一个本地数据库,并调用该种子方法来填充该数据库。

添加 DTO 类

如果您运行该应用程序现在并向 /api/books/1 发送一个 GET 请求,响应看起来类似于以下内容。(我添加可读性的缩进)。

{
"BookId": 1,
"Title": "Midnight Rain",
"Genre": "Fantasy",
"PublishDate": "2000-12-16T00:00:00",
"Description": "A former architect battles an evil sorceress.",
"Price": 14.95,
"AuthorId": 1,
"Author": null
}

相反,我希望这一请求返回的字段的子集。此外,我想它返回作者的名字,而不是作者 id。为了实现这个目标,我们将修改控制器方法返回数据传输对象(DTO) 而不是 EF 模型。DTO 是只为了进行数据的对象。

在解决方案资源管理器中,右键单击该项目并选择添加|新的文件夹。"Dto"将该文件夹命名。添加一个名为BookDto到 Dto 文件夹,以下定义类 ︰

namespace BooksAPI.DTOs
{
public class BookDto
{
public string Title { get; set; }
public string Author { get; set; }
public string Genre { get; set; }
}
}

添加另一个名为BookDetailDto的类.

using System;

namespace BooksAPI.DTOs
{
public class BookDetailDto
{
public string Title { get; set; }
public string Genre { get; set; }
public DateTime PublishDate { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Author { get; set; }
}
}

下一步,更新BooksController的类,以返回BookDto实例。我们将使用Queryable.Select方法对项目Book实例到BookDto实例。这里是控制器类的更新的代码。

using BooksAPI.DTOs;
using BooksAPI.Models;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description; namespace BooksAPI.Controllers
{
public class BooksController : ApiController
{
private BooksAPIContext db = new BooksAPIContext(); // Typed lambda expression for Select() method.
private static readonly Expression<Func<Book, BookDto>> AsBookDto =
x => new BookDto
{
Title = x.Title,
Author = x.Author.Name,
Genre = x.Genre
}; // GET api/Books
public IQueryable<BookDto> GetBooks()
{
return db.Books.Include(b => b.Author).Select(AsBookDto);
} // GET api/Books/5
[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
BookDto book = await db.Books.Include(b => b.Author)
.Where(b => b.BookId == id)
.Select(AsBookDto)
.FirstOrDefaultAsync();
if (book == null)
{
return NotFound();
} return Ok(book);
} protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}

我删除了PutBook、 PostBookDeleteBook方法,因为他们不需要在本教程中。

现在如果您运行该应用程序,并要求 /api/books/1,响应正文应该如下所示 ︰

{"Title":"Midnight Rain","Author":"Ralls, Kim","Genre":"Fantasy"}

添加路由属性

接下来,我们会将转换要使用属性路由的控制器。首先,先向控制器添加一个RoutePrefix属性。该属性定义所有方法的初始 URI 的段在此控制器上。

[RoutePrefix("api/books")]
public class BooksController : ApiController
{
// ...

然后将添加[路径]属性到控制器操作,如下 ︰

[Route("")]
public IQueryable<BookDto> GetBooks()
{
// ...
} [Route("{id:int}")]
[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
// ...
}

每个控制器方法的工艺路线模板是前缀加上路由属性中指定的字符串。对于GetBook方法,路线模板包含的参数化的字符串"{id: int}",如果 URI 段包含一个 integet 值匹配。

方法 工艺路线模板 URI 的示例
GetBooks "api/书" http://localhost/api/books
GetBook "api/书 / {id: int}" http://localhost/api/books/5

获取书详细信息

要获得书的详细信息,客户端将发送一个 GET 请求到/api/books/{id}/details,其中{id}是这本书的 ID。

将以下方法添加到BooksController类。

[Route("{id:int}/details")]
[ResponseType(typeof(BookDetailDto))]
public async Task<IHttpActionResult> GetBookDetail(int id)
{
var book = await (from b in db.Books.Include(b => b.Author)
where b.AuthorId == id
select new BookDetailDto
{
Title = b.Title,
Genre = b.Genre,
PublishDate = b.PublishDate,
Price = b.Price,
Description = b.Description,
Author = b.Author.Name
}).FirstOrDefaultAsync(); if (book == null)
{
return NotFound();
}
return Ok(book);
}

如果您请求/api/books/1/detail,反应看起来像这样 ︰

{
"Title": "Midnight Rain",
"Genre": "Fantasy",
"PublishDate": "2000-12-16T00:00:00",
"Description": "A former architect battles an evil sorceress.",
"Price": 14.95,
"Author": "Ralls, Kim"
}

按类型排列的书

要获得书籍列表中特定的体裁,客户端将发送一个 GET 请求到/api/books/genre,其中体裁是流派的名称。(例如, /get/books/fantasy.)

将以下方法添加到BooksController.

[Route("{genre}")]
public IQueryable<BookDto> GetBooksByGenre(string genre)
{
return db.Books.Include(b => b.Author)
.Where(b => b.Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))
.Select(AsBookDto);
}

在这里我们定义的路由包含 URI 模板中的 {体裁} 参数。请注意,Web API 能够区分这两个 Uri 并将它们路由到不同的方法 ︰

/api/books/1

/api/books/fantasy

这是因为GetBook方法包括"id"部分必须是整数值的约束 ︰

[Route("{id:int}")]
public BookDto GetBook(int id)
{
// ...
}

如果您请求 /api/books/fantasy,反应看起来像这样 ︰

[ { "Title": "Midnight Rain", "Author": "Ralls, Kim", "Genre": "Fantasy" }, { "Title": "Maeve Ascendant", "Author": "Corets, Eva", "Genre": "Fantasy" }, { "Title": "The Sundered Grail", "Author": "Corets, Eva", "Genre": "Fantasy" } ]

通过作者的书

若要获得特定作者的书籍列表,客户端将发送一个 GET 请求到/api/authors/id/books, id在哪里作者的 ID。

将以下方法添加到BooksController.

[Route("~api/authors/{authorId}/books")]
public IQueryable<BookDto> GetBooksByAuthor(int authorId)
{
return db.Books.Include(b => b.Author)
.Where(b => b.AuthorId == authorId)
.Select(AsBookDto);
}

这个例子是有趣的因为"书"治疗"作者"子资源。这种模式是很常见的 RESTful Api。

工艺路线模板以颚化符 (~) 替代路由前缀RoutePrefix属性中。

把书按出版日期

若要按出版日期获取的书籍列表,客户端将发送一个 GET 请求到/api/books/date/yyyy-mm-dd, yyyy mm dd在哪里日期。

这里是一个办法做到这一点 ︰

[Route("date/{pubdate:datetime}")]
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
return db.Books.Include(b => b.Author)
.Where(b => DbFunctions.TruncateTime(b.PublishDate)
== DbFunctions.TruncateTime(pubdate))
.Select(AsBookDto);
}

{pubdate:datetime}参数约束相匹配的日期时间值。这工作,但它是比我们想的其实更加宽容。例如,这些 Uri 也将匹配的路由 ︰

/api/books/date/Thu, 01 May 2008

/api/books/date/2000-12-16T00:00:00

没什么毛病允许这些 Uri。但是,可以通过将正则表达式约束添加到工艺路线模板,路线限制对特定的格式 ︰

[Route("api/books/date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
// ...
}

现在只在窗体中的日期,"yyyymmdd"将相匹配。请注意,我们不使用正则表达式来验证我们得到了真正的约会。这被处理时 Web API 试图将 URI 段转换成一个DateTime实例。无效的日期如 ' 2012年-47-99' 将无法转换,并且客户端将会得到一个 404 错误。

您也可以通过添加另一个[路径]属性与一个不同的正则表达式支持斜杠分隔符 (/api/books/date/yyyy/mm/dd)。

[Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
[Route("date/{*pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")] // new
public IQueryable<BookDto> GetBooks(DateTime pubdate)
{
// ...
}

还有一个微妙但重要的细节。第二个路由模板 {强加} 参数开头有通配符 (*) ︰

{*pubdate: ... }

这就告诉路由引擎那 {强加} 应配合其余的 URI。默认情况下,模板参数匹配一个单一的 URI 部分。在这种情况下,我们想 {强加} 要跨几个 URI 片段 ︰

/api/books/date/2013/06/17

控制器代码

这里是像下面这样类的完整代码。

using BooksAPI.DTOs;
using BooksAPI.Models;
using System;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Description; namespace BooksAPI.Controllers
{
[RoutePrefix("api/books")]
public class BooksController : ApiController
{
private BooksAPIContext db = new BooksAPIContext(); // Typed lambda expression for Select() method.
private static readonly Expression<Func<Book, BookDto>> AsBookDto =
x => new BookDto
{
Title = x.Title,
Author = x.Author.Name,
Genre = x.Genre
}; // GET api/Books
[Route("")]
public IQueryable<BookDto> GetBooks()
{
return db.Books.Include(b => b.Author).Select(AsBookDto);
} // GET api/Books/5
[Route("{id:int}")]
[ResponseType(typeof(BookDto))]
public async Task<IHttpActionResult> GetBook(int id)
{
BookDto book = await db.Books.Include(b => b.Author)
.Where(b => b.BookId == id)
.Select(AsBookDto)
.FirstOrDefaultAsync();
if (book == null)
{
return NotFound();
} return Ok(book);
} [Route("{id:int}/details")]
[ResponseType(typeof(BookDetailDto))]
public async Task<IHttpActionResult> GetBookDetail(int id)
{
var book = await (from b in db.Books.Include(b => b.Author)
where b.AuthorId == id
select new BookDetailDto
{
Title = b.Title,
Genre = b.Genre,
PublishDate = b.PublishDate,
Price = b.Price,
Description = b.Description,
Author = b.Author.Name
}).FirstOrDefaultAsync(); if (book == null)
{
return NotFound();
}
return Ok(book);
} [Route("{genre}")]
public IQueryable<BookDto> GetBooksByGenre(string genre)
{
return db.Books.Include(b => b.Author)
.Where(b => b.Genre.Equals(genre, StringComparison.OrdinalIgnoreCase))
.Select(AsBookDto);
}[Route("~api/authors/{authorId}/books")]publicIQueryable<BookDto>GetBooksByAuthor(int authorId){return db.Books.Include(b => b.Author).Where(b => b.AuthorId== authorId).Select(AsBookDto);}[Route("date/{pubdate:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")][Route("date/{*pubdate:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]publicIQueryable<BookDto>GetBooks(DateTime pubdate){return db.Books.Include(b => b.Author).Where(b =>DbFunctions.TruncateTime(b.PublishDate)==DbFunctions.TruncateTime(pubdate)).Select(AsBookDto);}protectedoverridevoidDispose(bool disposing){
db.Dispose();base.Dispose(disposing);}}}

摘要

属性路由给你更多的控制和更大的灵活性时设计您的 API 的 Uri。

这篇文章的初衷是在 2013 年 6 月 26 日

作者信息

2.4使用属性在 ASP.NET Web API 2 路由创建一个 REST API的更多相关文章

  1. 2.3属性在 ASP.NET Web API 2 路由

    路由是 Web API 如何匹配 URI 的行动.Web API 2 支持一种新型的路由,称为属性路由.顾名思义,属性路由使用属性来定义路由.属性路由给你更多的控制 Uri 在您的 web API.例 ...

  2. ASP.NET没有魔法——开篇-用VS创建一个ASP.NET Web程序

    为什么写这一系列文章? 本系列文章基于ASP.NET MVC,在ASP.NET Core已经发布2.0版本,微服务漫天的今天为什么还写ASP.NET?. 答:虽然现在已经有ASP.NET Core并且 ...

  3. Asp.NetCore Web开发之路由

    接着讲asp.net core web开发,这节讲路由系统(Route). 在asp.net core中通过路由来将请求映射到对应的action,主要用到两个中间件,UseRouting()和UseE ...

  4. 使用ASP.NET web API创建REST服务(二)

    Creating a REST service using ASP.NET Web API A service that is created based upon the architecture ...

  5. 使用ASP.NET web API创建REST服务(三)

    本文档来源于:http://www.cnblogs.com/madyina/p/3390773.html Creating a REST service using ASP.NET Web API A ...

  6. ASP.NET Web Service如何工作(1)

    ASP.NET Web Service如何工作(1) [日期:2003-06-26] 来源:CSDN  作者:sunnyzhao(翻译) [字体:大 中 小] Summary ASP.NET Web ...

  7. 使用 C# 编写简易 ASP.NET Web 服务器

    原文 http://www.cnblogs.com/lcomplete/p/use-csharp-write-aspnet-web-server.html 如果你想获得更好的阅读体验,可以前往我在 g ...

  8. 【ASP.NET Web API教程】2.4 创建Web API的帮助页面

    原文:[ASP.NET Web API教程]2.4 创建Web API的帮助页面 注:本文是[ASP.NET Web API系列教程]的一部分,如果您是第一次看本博客文章,请先看前面的内容. 2.4 ...

  9. Asp.Net Web API 2(入门)第一课

    Asp.Net Web API 2(入门)第一课   前言 Http不仅仅服务于Web Pages.它也是一个创建展示服务和数据的API的强大平台.Http是简单的.灵活的.无处不在的.你能想象到几乎 ...

随机推荐

  1. hibernate 中的 lazy=”proxy” 和 lazy=”no-proxy” 的区别

    网上找到个描述的很精妙的例子 Child   <-   many-to-one   ->Parent         class   Child   {         private   ...

  2. Linux提权(2)-高级版

    当你在攻击受害者的电脑时即使你拥有了一个shell,依然可能会有一些拒绝执行指令的限制.为了获得目标主机的完整控制权限,你需要在未授权的地方绕过权限控制.这些权限可以删除文件,浏览私人信息,或者安装并 ...

  3. Object.assign方法复制或合并对象

    Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象 var obj = { a: 1 }; var copy = Object.assign({ ...

  4. 用延迟加载解决CNZZ加载慢的问题

    我是不太喜欢CNZZ的, 不过既然公司要用, 还是得加, 这个公司不知道为什么不好好优化一下, 这么多功能都做了, 难道不愿意多费几分钟优化一下这个特别影响用户体验的统计代码? 现在的移动站页面, 是 ...

  5. MVC之前的那点事儿系列(8):UrlRouting的理解

    文章内容 根据对Http Runtime和Http Pipeline的分析,我们知道一个ASP.NET应用程序可以有多个HttpModuel,但是只能有一个HttpHandler,并且通过这个Http ...

  6. 拿什么拯救你,我的代码--c#编码规范实战篇

    此文为译文,原文地址请点击. 本文通过重构一个垃圾代码,阐述了如何写出优秀的代码.开发人员及代码审核人员需按照此规范开发和审核代码.此规范以C#为例,JAVA的童鞋一并参考,C++的童鞋自行脑补吧. ...

  7. Android BLE 蓝牙编程(三)

    上节我们已经可以连接上蓝牙设备了. 本节我们就要获取手环的电池电量和计步啦. 在介绍这个之前我们需要先了解下什么是 服务 什么是 UUID 我们记得上节中我们item监听事件的回调的返回值是Bluet ...

  8. iOS项目常用效果方法注意点集锦

    移动中隐藏tabBar,静止显示tabbar - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { // 隐藏tabbar ...

  9. XUtils框架之初步探索

    Xutils分为四大模块. BitmapUtils  DBUtils ViewUtils HttpUtils

  10. 通过Knockout.js + ASP.NET Web API构建一个简单的CRUD应用

    REFERENCE FROM : http://www.cnblogs.com/artech/archive/2012/07/04/Knockout-web-api.html 较之面向最终消费者的网站 ...