相关文章推荐
发财的李子  ·  TestRestTemplate ...·  4 月前    · 
完美的硬盘  ·  AsyncRestTemplate ...·  4 月前    · 
八块腹肌的生菜  ·  Uses of Class ...·  4 月前    · 
讲道义的小熊猫  ·  postgres array ...·  3 年前    · 
打篮球的汽水  ·  oracle 工作日计算-掘金·  3 年前    · 
  • AuthorID (Author 表的外键)
  • 但是,对于大多数请求,API 将返回此数据的子集 (标题、作者和流派) 。 若要获取完整记录,客户端请求 /api/books/{id}/details

    Visual Studio 2017 社区版、专业版或企业版。

    创建 Visual Studio 项目

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

    展开 “已安装 > 的 Visual C# ”类别。 在 Visual C# 下,选择 “Web ”。 在项目模板列表中,选择 ASP.NET Web 应用程序 (.NET Framework) 。 将项目命名为“BooksAPI”。

    “新建 ASP.NET Web 应用程序 ”对话框中,选择 “空 ”模板。 在“添加文件夹和核心引用”下,选中 “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 控制器,该控制器使用 Entity Framework 作为数据层。

    按 Ctrl+Shift+B 生成项目。 Entity Framework 使用反射来发现模型的属性,因此它需要编译的程序集来创建数据库架构。

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

    “添加基架 ”对话框中, 使用 Entity Framework 选择包含操作的 Web API 2 控制器

    “添加控制器 ”对话框中,对于 控制器名称,输入“BooksController”。 选中“使用异步控制器操作”复选框。 对于 Model 类,请选择“Book”。 (如果未看到 Book 下拉列表中列出的类,请确保生成 project.) 然后单击“+”按钮。

    单击“新建数据上下文”对话框中的“添加”。

    单击“添加控制器”对话框中的“添加”。 基架添加一 BooksController 个名为定义 API 控制器的类。 它还在 Models 文件夹中添加一 BooksAPIContext 个名为的类,该类定义 Entity Framework 的数据上下文。

    设定数据库种子

    在“工具”菜单中,选择 “NuGet 包管理器”,然后选择“ 包管理器控制台”。

    在“Package Manager Console”窗口中,输入以下命令:

    Add-Migration
    

    此命令创建迁移文件夹,并添加名为 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
    

    这些命令创建本地数据库并调用 Seed 方法以填充数据库。

    添加 DTO 类

    如果立即运行应用程序并将 GET 请求发送到 /api/books/1,响应将如下所示。 (添加了可读性的缩进。)

    "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 方法将实例投影 BookBookDto 实例。 下面是控制器类的更新代码。

    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);
    

    我删除了PutBookPostBookDeleteBook方法,因为它们不需要本教程。

    现在,如果运行应用程序并请求 /api/books/1,响应正文应如下所示:

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

    添加路由属性

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

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

    然后将 [Route] 属性添加到控制器操作,如下所示:

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

    每个控制器方法的路由模板是前缀加上 Route 属性中指定的字符串。 GetBook对于该方法,路由模板包含参数化字符串“{id:int}”,如果 URI 段包含整数值,则匹配该字符串。

    示例 URI

    获取书籍详细信息

    若要获取书籍详细信息,客户端将向其中 {id} 发送 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.BookId == 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/details,响应如下所示:

    "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,其中 流派 是流派的名称。 (例如:/api/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:int}/books")]
    public IQueryable<BookDto> GetBooksByAuthor(int authorId)
        return db.Books.Include(b => b.Author)
            .Where(b => b.AuthorId == authorId)
            .Select(AsBookDto);
    

    此示例很有趣,因为“book”被视为“作者”的子资源。 此模式在 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} 受约束以匹配 DateTime 值。 这很有效,但实际上比我们想要的更宽松。 例如,这些 URI 也将匹配路由:

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

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

    允许这些 URI 没有任何问题。 但是,可以通过向路由模板添加正则表达式约束来限制路由到特定格式:

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

    现在,只有格式为“yyyy-mm-dd”的日期才会匹配。 请注意,我们不使用正则表达式来验证我们是否获得了真实日期。 当 Web API 尝试将 URI 段转换为 DateTime 实例时,将进行处理。 “2012-47-99”等无效日期将无法转换,客户端将收到 404 错误。

    还可以通过添加具有不同正则表达式的另一个 [Route] 属性来支持斜杠分隔符 (/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} 参数的开头具有通配符 (*) :

    {*pubdate: ... }
    

    这会告知路由引擎 {pubdate} 应与 URI 的其余部分匹配。 默认情况下,模板参数与单个 URI 段匹配。 在这种情况下,我们希望 {pubdate} 跨多个 URI 段:

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

    控制器代码

    下面是 BooksController 类的完整代码。

    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")]
            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);
    

    在为 API 设计 URI 时,属性路由可提供更多控制和更大的灵活性。