Condividi tramite


Creare oggetti di trasferimento dati (DTO)

Scaricare il progetto completato

Al momento, l'API Web espone le entità di database al client. Il client riceve dati che vengono mappati direttamente alle tabelle del database. Tuttavia, questa non è sempre una buona idea. A volte si vuole modificare la forma dei dati inviati al client. Può, ad esempio, essere necessario:

  • Rimuovere riferimenti circolari (vedere la sezione precedente).
  • Nascondere proprietà specifiche che i client non devono visualizzare.
  • Omettere alcune proprietà per ridurre le dimensioni del payload.
  • Appiattire i grafici di oggetti che contengono oggetti annidati, per renderli più pratici per i clienti.
  • Evitare le vulnerabilità dovute a un eccesso di pubblicazione. Per una discussione sull'over-posting, vedere Convalida del modello.
  • Separare il livello di servizio dal livello di database.

A tale scopo, è possibile definire un oggetto DTO ( Data Transfer Object ). Un DTO è un oggetto che definisce la modalità di invio dei dati in rete. Vediamo come funziona con l'entità Book. Nella cartella Models aggiungere due classi 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 BookDetailDto classe include tutte le proprietà del modello Book, ad eccezione AuthorName di una stringa che conterrà il nome dell'autore. La BookDto classe contiene un subset di proprietà da BookDetailDto.

Sostituire quindi i due metodi GET nella BooksController classe con le versioni che restituiscono DTO. Si userà l'istruzione LINQ Select per eseguire la conversione da entità Book in 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);
}

Di seguito è riportato il codice SQL generato dal nuovo GetBooks metodo. È possibile notare che EF converte LINQ Select in un'istruzione 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]

Infine, modificare il metodo PostBook per restituire 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);
}

Annotazioni

In questo tutorial, eseguiamo manualmente la conversione in DTOs nel codice. Un'altra opzione consiste nell'usare una libreria come AutoMapper che gestisce automaticamente la conversione.