使用属性在 ASP.NET Web API 2 路由创建一个 REST API
Web API 2 支持一种新型的路由,称为属性路由。属性路由的一般概述,请参阅属性路由 Web API 2 中。在本教程中,您将使用属性路由创建一个 REST API 集合的书。API 将支持以下操作︰
所有方法都是只读的 (HTTP GET 请求)。
对于数据层,我们将使用实体框架。本书记录将有以下字段︰
- ID
- 标题
- 体裁
- 出版日期
- 价格
- 描述
- 作者 Id (Authors 表的外键)
然而,对于大多数请求,API 将返回此数据 (标题、 作者和流派) 的一个子集。要获得完整的记录,客户端请求/api/books/{id}/details
.
系统必备组件
Visual Studio 2013 or Visual Studio Express 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 来生成项目。实体框架使用反射来发现性能的模型,所以它需要在编译的程序集来创建数据库模式。
在解决方案资源管理器中,右键单击控制器文件夹。选择添加,然后选择控制器.
在添加控制器对话框中,为控制器的名称,输入"例子"。选中"使用异步控制器操作"复选框。对于模型类,请选择"书"。(如果你看不到下拉列表中列出的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 Initialupdate-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/Bookspublic 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);}}
}
现在如果您运行该应用程序,并要求 /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 值匹配。
获取书详细信息
要获得书的详细信息,客户端将发送一个 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 == idselect 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 == idselect 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")]public IQueryable<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})}")]public IQueryable<BookDto> GetBooks(DateTime pubdate){return db.Books.Include(b => b.Author).Where(b => DbFunctions.TruncateTime(b.PublishDate)== DbFunctions.TruncateTime(pubdate)).Select(AsBookDto);}protected override void Dispose(bool disposing){db.Dispose();base.Dispose(disposing);}}
}
使用属性在 ASP.NET Web API 2 路由创建一个 REST API相关推荐
- 【已解决】请在位于当前 Web 应用程序根目录下的“web.config”配置文件中创建一个 <customErrors> 标记
问题 详细信息: 若要使他人能够在远程计算机上查看此特定错误信息的详细信息,请在位于当前 Web 应用程序根目录下的"web.config"配置文件中创建一个 标记.然后应将此 标 ...
- 使用PHP创建一个REST API(译)
最近API在网络领域有些风靡,明确的说是REST的影响力.这实在没什么好惊讶的,因为在任何编程语言中,消费REST API都是非常的容易.构建它也非常的简单,因为本质上你不会用到任何那些已存在很久的H ...
- aws api gateway 创建一个REST API作为Amazon S3代理
你可能想把样本API导入为Amazon S3的代理,如样本API作为Amazon S3代理的OpenAPI定义所示.关于如何使用OpenAPI定义导入API的说明,请参阅使用OpenAPI配置REST ...
- 在SAP云平台的API portal里创建和管理API
登录API Portal,创建一个新的API: API Provider字段,选择之前创建好的API provider,SAPDeveloperSystemES5,点击Discover按钮后,能看到该 ...
- ASP.NET没有魔法——开篇-用VS创建一个ASP.NET Web程序
本文是本系列文章第一篇,主要通过建立一个默认ASP.NET MVC项目来引出与ASP.NET MVC相关的功能,由于ASP.NET MVC一个简单的模板就具备了数据库操作.身份验证.输入数据校验等功能 ...
- 创建一个提供数据 API 的 Node.js 网站
创建站点目录 首先,创建一个文件夹用来保存你的站点文件,使用 mkdir 就可以了 PS C:\> mkdir mysite 然后,进入到这个文件夹进行下一步的操作. 创建包说明 使用记事本或者 ...
- 使用PHP创建一个REST API(Create a REST API with PHP)
译者前言: 首先这是一篇国外的英文文章,非常系统.详尽的介绍了如何使用PHP创建REST API,国内这方面的资料非常非常的有限,而且基本没有可操作性.这篇文章写的非常好,只要对PHP稍有了解的程序员 ...
- Windows API 编程起始——创建一个窗口
最初了解Windows api编程呢,就是先创建出一个最简洁的窗口,就如我们学习C/C++时的"Helloword"一样,这是进入windows编程大门的重要一个步,下面就开始吧. ...
- rest php,使用php创建一个Rest Api
译者前言: 首先这是一篇国外的英文文章,非常系统.详尽的介绍了如何使用PHP创建RESTAPI,国内这方面的资料非常非常的有限,而且基本没有可操作性.这篇文章写的非常好,只要对PHP稍有了解的程序员, ...
- 创建api java,如何创建静态Java API?
我看到有人在编写Java LOG API,例如下面的代码. 这个想法是客户端不必每次都调用LogUtil.getInstance(). 但是我的感觉是这不是惯用的Java吗? public class ...
最新文章
- 规格表管理之删除规格表数据
- python使用imbalanced-learn的NearMiss方法进行下采样处理数据不平衡问题
- 015_CSS伪元素选择器
- go mysql教学_Go语言之对Mysql简单操作
- Debian7.2上一键安装Jetty
- Express + Element-ui 实现图片/文件上传
- 红橙Darren视频笔记 view的绘制流程(上) onMeasure测量代码分析 基于API27
- 【Spark】Spark基本概念
- 02_入门程序(非注解方式,了解)
- Vulnhub-THE PLANETS: EARTH
- 免费实时汇率查询接口
- Windows 7下手动搭建Asp和PHP集成环境
- freyja 功能基本完毕
- GNSS/INS组合导航(九):三维简化的INS/GPS组合导航系统
- deprecate(反对) 关于依赖版本低的问题
- 持续更新 iText in Action 2nd Edition中文版 个人翻译
- web SEO简单优化
- 8个最好用的H5页面制作工具
- 【LOJ6570】毛毛虫计数
- python中科学计数法怎么表示_python科学计数法转换
热门文章
- Python:监控键盘输入、鼠标操作,并将捕获到的信息记录到文件中 (转)
- 视比特“AI+3D视觉”核心产品 | 智能下料分拣产线
- 推荐一款适合苹果电脑小白使用的BT下载器
- BZOJ3202 [Sdoi2013]项链
- Real-Time Rendering——5.2.2 Punctual Lights精准光
- 《道德经》马王堆出土帛书版
- java 跳转url_URL跳转的几种方式
- python基础之模块
- Vue图表(v-charts, e-charts)入门安装使用
- 【二分查找】详细讲解(C语言折半查找)