Skapa dataöverföringsobjekt (DTU: er)

Ladda ned slutfört projekt

Just nu exponerar vårt webb-API databasentiteterna för klienten. Klienten tar emot data som mappar direkt till dina databastabeller. Men det är inte alltid en bra idé. Ibland vill du ändra formen på de data som du skickar till klienten. Du kanske till exempel vill:

  • Ta bort cirkelreferenser (se föregående avsnitt).
  • Dölj vissa egenskaper som klienter inte ska visa.
  • Utelämna vissa egenskaper för att minska nyttolaststorleken.
  • Platta ut objektdiagram som innehåller kapslade objekt för att göra dem mer praktiska för klienter.
  • Undvik sårbarheter på grund av "överpublicering". (Se Modellverifiering för en diskussion om överpublicering.)
  • Frikoppla tjänstskiktet från databaslagret.

För att åstadkomma detta kan du definiera ett dataöverföringsobjekt (DTO). En DTO är ett objekt som definierar hur data ska skickas via nätverket. Nu ska vi se hur det fungerar med bokentiteten. Lägg till två DTO-klasser i mappen Modeller:

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; }
    }
}

Klassen BookDetailDto innehåller alla egenskaper från bokmodellen, förutom att det AuthorName är en sträng som innehåller författarens namn. Klassen BookDto innehåller en delmängd av egenskaper från BookDetailDto.

Ersätt sedan de två GET-metoderna i BooksController klassen med versioner som returnerar DTU:er. Vi använder LINQ Select-instruktionen för att konvertera från bokentiteter till DTO:er.

// 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);
}

Här är DEN SQL som genereras av den nya GetBooks metoden. Du kan se att EF översätter LINQ Select till en SQL SELECT-instruktion.

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]

Slutligen ändrar du PostBook metoden för att returnera en 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);
}

Anmärkning

I den här självstudien konverterar vi till DTO:er manuellt i kod. Ett annat alternativ är att använda ett bibliotek som AutoMapper som hanterar konverteringen automatiskt.