Criar DTOs (objetos de transferência de dados)

por Mike Wasson

Baixar projeto concluído

No momento, nossa API Web expõe as entidades de banco de dados ao cliente. O cliente recebe dados que mapeiam diretamente para suas tabelas de banco de dado. No entanto, isso nem sempre é uma boa ideia. Às vezes, você deseja alterar a forma dos dados que você envia para o cliente. Por exemplo, você pode querer:

  • Remova as referências circulares (consulte a seção anterior).
  • Oculte propriedades específicas que os clientes não devem exibir.
  • Omita algumas propriedades para reduzir o tamanho da carga.
  • Nivelar grafos de objeto que contêm objetos aninhados, para torná-los mais convenientes para os clientes.
  • Evite vulnerabilidades de "excesso de postagens". (Consulte validação de modelo para uma discussão de excesso de postagem.)
  • Desassocie sua camada de serviço da camada de banco de dados.

Para fazer isso, você pode definir um DTO ( objeto de transferência de dados ). Um DTO é um objeto que define como os dados serão enviados pela rede. Vamos ver como isso funciona com a entidade de livro. Na pasta modelos, adicione duas classes 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; }
    }
}

A classe BookDetailDto inclui todas as propriedades do modelo de livro, exceto que AuthorName é uma cadeia de caracteres que conterá o nome do autor. A classe BookDto contém um subconjunto de propriedades de BookDetailDto.

Em seguida, substitua os dois métodos GET na classe BooksController, por versões que retornam DTOs. Usaremos a instrução LINQ Select para converter de entidades de livro em DTOs.

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

Aqui está o SQL gerado pelo novo método GetBooks. Você pode ver que o EF converte o LINQ Select em uma instrução 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]

Por fim, modifique o método PostBook para retornar um 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);
}

Note

Neste tutorial, estamos convertendo para DTOs manualmente no código. Outra opção é usar uma biblioteca como o AutoMapper que manipula a conversão automaticamente.