Создание объектов передачи данных (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 , которая обрабатывает преобразование автоматически.