Créer des objets de transfert de données (DTO)
À l’heure actuelle, notre API web expose les entités de base de données au client. Le client reçoit des données qui correspondent directement à vos tables de base de données. Cependant, ce n’est pas toujours une bonne idée. Parfois, vous souhaitez modifier la forme des données que vous envoyez au client. Vous pouvez, par exemple, souhaiter effectuer les opérations suivantes :
- Supprimez les références circulaires (voir la section précédente).
- Masquer les propriétés particulières que les clients ne sont pas censés afficher.
- Omettre certaines propriétés afin de réduire la taille de la charge utile.
- Aplatir les graphiques d’objets qui contiennent des objets imbriqués, afin de les rendre plus pratiques pour les clients.
- Évitez les vulnérabilités de « sur-publication ». (Voir Validation du modèle pour une discussion sur la sur-publication.)
- Dissociez votre couche de service de votre couche de base de données.
Pour ce faire, vous pouvez définir un objet de transfert de données (DTO). Un DTO est un objet qui définit la façon dont les données seront envoyées sur le réseau. Voyons comment cela fonctionne avec l’entité Book. Dans le dossier Models, ajoutez deux 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; }
}
}
La BookDetailDto
classe inclut toutes les propriétés du modèle Book, sauf qu’il AuthorName
s’agit d’une chaîne qui contiendra le nom de l’auteur. La BookDto
classe contient un sous-ensemble de propriétés de BookDetailDto
.
Ensuite, remplacez les deux méthodes GET dans la BooksController
classe , par des versions qui retournent des DTO. Nous allons utiliser l’instruction LINQ Select pour convertir des entités Book en 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);
}
Voici le code SQL généré par la nouvelle GetBooks
méthode. Vous pouvez voir qu’EF traduit linQ Select en instruction 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]
Enfin, modifiez la PostBook
méthode pour retourner 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);
}
Notes
Dans ce tutoriel, nous convertissons manuellement en DTO dans le code. Une autre option consiste à utiliser une bibliothèque comme AutoMapper qui gère automatiquement la conversion.