現在,我們的Web API暴露資料庫實體給用戶端,而用戶端接收直接映射到你的資料庫表的資料。然而,這不永遠都是個好辦法。有時候你可以想要改變發送到用戶端的資料的形式。例如,你可以想要:
1, 移除環形引用(見上一章)
2, 隐藏用戶端不應該看到的特定屬性
3, 為了減少有效載荷而省略一些屬性
4, 拼接包含嵌套的對象圖,以使它們對用戶端更便利
5, 避免”over-posting”漏洞(檢視Model Validation(
http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api)關于over-posting的讨論)
6, 對你的服務層和資料層進行解耦
為了完成它,你應該定義一個資料傳輸對象(DTO,data transfer object)。DTO是一個定義了資料将會如何在網絡上傳輸的對象。讓我們來看看它如何工作于Book實體。在Models檔案夾下,添加兩個DTO類:
namespace BookService.Models
{
public class BookDTO
{
public int Id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
}
}
namespace BookService.Models
{
public class BookDetailDTO
{
public int Id { get; set; }
public string Title { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
public string AuthorName { get; set; }
public string Genre { get; set; }
}
}
BookDetailDTO類包含了Book模型的所有屬性,除了用于承載作者姓名的字元串AuthorName。BookDTO類包含了BookDetailDTO的屬性的子集。
下一步,在BooksController類中替換兩個傳回DTO的GET方法。我們将使用LINQ的Select語句将Book實體轉換到DTO。
// GET api/Books
public IQueryable<BookDTO> GetBooks()
{
var books = from b in db.Books
select new BookDTO()
{
Id = b.Id,
Title = b.Title,
AuthorName = b.Author.Name
};
return books;
}
// GET api/Books/5
[ResponseType(typeof(BookDetailDTO))]
public async Task<IHttpActionResult> GetBook(int id)
{
var book = await db.Books.Include(b => b.Author).Select(b =>
new BookDetailDTO()
{
Id = b.Id,
Title = b.Title,
Year = b.Year,
Price = b.Price,
AuthorName = b.Author.Name,
Genre = b.Genre
}).SingleOrDefaultAsync(b => b.Id == id);
if (book == null)
{
return NotFound();
}
return Ok(book);
}
這是新的GetBooks方法所生成的SQL查詢。你可以檢視EF從LINQ Select轉換到SQL Select的語句。
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Title] AS [Title],
[Extent2].[Name] AS [Name]
FROM [dbo].[Books] AS [Extent1]
INNER JOIN [dbo].[Authors] AS [Extent2] ON [Extent1].[AuthorId] = [Extent2].[Id]
最後,修改PostBook方法以傳回DTO。
[ResponseType(typeof(Book))]
public async Task<IHttpActionResult> PostBook(Book book)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Books.Add(book);
await db.SaveChangesAsync();
// New code:
// Load author name
db.Entry(book).Reference(x => x.Author).Load();
var dto = new BookDTO()
{
Id = book.Id,
Title = book.Title,
AuthorName = book.Author.Name
};
return CreatedAtRoute("DefaultApi", new { id = book.Id }, dto);
}
總結:在這個教程中,我們在代碼中手動地轉換到DTO。另一種方式是使用像AutoMapper這樣的庫來處理自動轉換。