Создание объектов передачи данных (DTO)
Скачивание завершенного проекта
Сейчас наш веб-API предоставляет клиенту сущности базы данных. Клиент получает данные, которые сопоставляют непосредственно с таблицами базы данных. Однако это не всегда хорошая идея. Иногда требуется изменить форму данных, отправляемых клиенту. Например, можно сделать следующее:
- Удалите циклические ссылки (см. предыдущий раздел).
- Скрытие определенных свойств, которые клиенты не должны просматривать.
- Пропуск некоторых свойств, чтобы уменьшить размер полезной нагрузки.
- Плоские графы объектов, содержащие вложенные объекты, чтобы сделать их более удобными для клиентов.
- Избегайте уязвимостей с избыточной публикацией. (Обсуждение чрезмерной публикации см. в разделе Проверка модели .)
- Отделите уровень служб от уровня базы данных.
Для этого можно определить объект передачи данных (DTO). 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
.
Затем замените два метода GET в BooksController
классе версиями, возвращающими DTO. Мы будем использовать оператор 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);
}
Ниже приведен SQL, созданный новым GetBooks
методом . Вы видите, что 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(BookDto))]
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 , которая обрабатывает преобразование автоматически.