Crear objetos de transferencia de datos (DTO)

Descargar el proyecto completado

Actualmente, nuestra API web expone las entidades de base de datos al cliente. El cliente recibe datos que se asignan directamente a las tablas de base de datos. Sin embargo, esto no siempre es lo adecuado. A veces desea cambiar la forma de los datos que envía al cliente. Por ejemplo, puedes:

  • Quitar las referencias circulares (consulte la sección anterior).
  • Ocultar las propiedades particulares que los clientes no deben ver.
  • Omitir algunas propiedades para reducir el tamaño de la carga.
  • Aplanar gráficos de objetos que contienen objetos anidados, para que sean más cómodos para los clientes.
  • Evitar vulnerabilidades de "exceso de envíos". (Consulte Validación del modelo para obtener una explicación sobre el exceso de envíos).
  • Desacoplar el nivel de servicio de la capa de base de datos.

Para ello, puede definir un objeto de transferencia de datos (DTO). Un DTO es un objeto que define cómo se enviarán los datos a través de la red. Veamos cómo funciona con la entidad Book. En la carpeta Modelos, agregue dos clases 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; }
    }
}

La clase BookDetailDto incluye todas las propiedades del modelo Book, excepto que AuthorName es una cadena que contendrá el nombre del autor. La clase BookDto contiene un subconjunto de propiedades de BookDetailDto.

A continuación, reemplace los dos métodos GET de la clase BooksController, por versiones que devuelven DTO. Usaremos la instrucción LINQ Select para convertir entidades Book a 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);
}

Este es el código SQL generado por el nuevo método GetBooks. Puede ver que EF traduce Select de LINQ a una instrucción SELECT de SQL.

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 último, modifique el método PostBook para devolver un 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);
}

Nota:

En este tutorial, vamos a convertir a DTO manualmente en el código. Otra opción es usar una biblioteca como AutoMapper que controla la conversión automáticamente.