Criar DTOs (objetos de transferência de dados)
No momento, nossa API Web expõe as entidades de banco de dados para o cliente. O cliente recebe dados que são mapeados diretamente para suas tabelas de banco de dados. No entanto, nem sempre é uma boa ideia. Às vezes, você deseja alterar a forma dos dados enviados ao cliente. Por exemplo, você pode querer:
- Remover referências circulares (consulte a seção anterior).
- Ocultar propriedades específicas que os clientes não devem exibir.
- Omitir algumas propriedades para reduzir o tamanho da carga.
- Nivele os grafos de objeto que contêm objetos aninhados para torná-los mais convenientes para os clientes.
- Evite vulnerabilidades de "excesso de postagem". (Consulte Validação de modelo para obter uma discussão sobre sobre postagem.)
- Desacoplar a 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 Book. 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 BookDetailDto
classe inclui todas as propriedades do modelo de Livro, exceto que AuthorName
é uma cadeia de caracteres que conterá o nome do autor. A BookDto
classe contém um subconjunto de propriedades de BookDetailDto
.
Em seguida, substitua os dois métodos GET na BooksController
classe , por versões que retornam DTOs. Usaremos a instrução LINQ Select para converter de entidades book 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 GetBooks
método. 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 PostBook
método 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);
}
Observação
Neste tutorial, estamos convertendo em DTOs manualmente no código. Outra opção é usar uma biblioteca como AutoMapper que manipula a conversão automaticamente.