データ転送オブジェクト (DTO) の作成

完成したプロジェクトをダウンロードする

現在、Microsoft の Web API は、データベース エンティティをクライアントに公開しています。 クライアントは、データベース テーブルに直接マップされるデータを受け取ります。 ただし、これは常に良い考えであるとは限りません。 クライアントに送信するデータの形式を変更したい場合があります。 たとえば、次の操作を行います。

  • 循環参照を削除する (前のセクションを参照)。
  • クライアントが表示しないことになっている特定のプロパティを非表示にする。
  • ペイロード サイズを減らすために、いくつかのプロパティを省略する。
  • 入れ子になったオブジェクトを含むオブジェクト グラフをフラット化して、クライアントにとってより便利なものにする。
  • "オーバーポスティング" の脆弱性を回避する。 (オーバーポスティングの詳細については、モデル検証に関するページを参照してください)。
  • サービス レイヤーをデータベース レイヤーから切り離す。

これを実現するには、"データ転送オブジェクト" (DTO) を定義します。 DTO は、ネットワーク経由でのデータの送信方法を定義するオブジェクトです。 それが Book エンティティでどのように機能するかを見てみましょう。 Models フォルダーに、次の 2 つの 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; }
    }
}

BookDetailDto クラスには、AuthorName が作成者名を保持する文字列であることを除き、Book モデルのすべてのプロパティが含まれています。 BookDto クラスには、BookDetailDto のプロパティのサブセットが含まれています。

次に、BooksController クラスの 2 つの GET メソッドを、DTO を返すバージョンに置き換えます。 LINQ Select ステートメントを使用して、Book エンティティから 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);
}

新しい GetBooks メソッドによって生成される SQL を次に示します。 EF によって LINQ Select が 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]

最後に、DTO を返すように PostBook メソッドを変更します。

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

Note

このチュートリアルでは、コードで DTO に手動で変換します。 もう 1 つのオプションは、変換を自動的に処理する AutoMapper のようなライブラリを使用することです。