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.