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 i dati mappati direttamente alle tabelle di 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.
  • Rendere i grafici degli oggetti flat che contengono oggetti annidati, per renderli più pratici per i client.
  • Evitare vulnerabilità di over-posting. Per informazioni 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 versioni che restituiscono DTOs. Si userà l'istruzione LINQ Select per convertire le 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]

Modificare infine il PostBook metodo per restituire un oggetto 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

In questa esercitazione viene eseguita la conversione manuale in DTOs nel codice. Un'altra opzione consiste nell'usare una libreria come AutoMapper che gestisce automaticamente la conversione.