Tworzenie obiektów transferu danych (DTO)
Pobieranie ukończonego projektu
W tej chwili nasz internetowy interfejs API uwidacznia jednostki bazy danych klientowi. Klient odbiera dane mapujące bezpośrednio na tabele bazy danych. Jednak nie zawsze jest to dobry pomysł. Czasami chcesz zmienić kształt danych wysyłanych do klienta. Na przykład możesz chcieć:
- Usuń odwołania cykliczne (zobacz poprzednią sekcję).
- Ukryj określone właściwości, których klienci nie powinni wyświetlać.
- Pomiń niektóre właściwości, aby zmniejszyć rozmiar ładunku.
- Spłaszczane wykresy obiektów, które zawierają zagnieżdżone obiekty, aby uczynić je wygodniejszymi dla klientów.
- Unikaj luk w zabezpieczeniach "nadmiernego publikowania". (Zobacz Walidacja modelu , aby zapoznać się z omówieniem nadmiernego delegowania).
- Rozdziel warstwę usługi z warstwy bazy danych.
W tym celu można zdefiniować obiekt transferu danych (DTO). Obiekt DTO to obiekt, który definiuje sposób wysyłania danych za pośrednictwem sieci. Zobaczmy, jak to działa z jednostką Book. W folderze Models dodaj dwie klasy 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; }
}
}
Klasa BookDetailDto
zawiera wszystkie właściwości z modelu Book, z tą różnicą, że AuthorName
jest to ciąg, który będzie zawierać nazwę autora. Klasa BookDto
zawiera podzbiór właściwości z klasy BookDetailDto
.
Następnie zastąp dwie metody GET w BooksController
klasie wersjami, które zwracają obiekty DTO. Użyjemy instrukcji LINQ Select , aby przekonwertować jednostki z książki na obiekty 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);
}
Oto sql wygenerowany przez nową GetBooks
metodę. Widać, że program EF tłumaczy linQ Select na instrukcję 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]
Na koniec zmodyfikuj metodę PostBook
, aby zwrócić cel 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);
}
Uwaga
W tym samouczku ręcznie przeprowadzamy konwersję na obiekty DTO w kodzie. Inną opcją jest użycie biblioteki, takiej jak AutoMapper , która automatycznie obsługuje konwersję.