DTO(데이터 전송 개체) 만들기
현재 웹 API는 데이터베이스 엔터티를 클라이언트에 노출합니다. 클라이언트는 데이터베이스 테이블에 직접 매핑되는 데이터를 받습니다. 그러나 항상 좋은 생각은 아닙니다. 클라이언트에 보내는 데이터의 모양을 변경하려는 경우가 있습니다. 예를 들어, 다음을 수행합니다.
- 순환 참조를 제거합니다(이전 섹션 참조).
- 클라이언트에서 볼 수 없는 특정 속성을 숨깁니다.
- 페이로드 크기를 줄이기 위해 일부 속성을 생략합니다.
- 중첩된 개체가 포함된 개체 그래프를 평면화하여 클라이언트에 더 편리하게 만듭니다.
- "초과 게시" 취약성을 방지합니다. (초과 게시에 대한 논의는 모델 유효성 검사를 참조하세요.)
- 서비스 계층을 데이터베이스 계층과 분리합니다.
이를 위해 DTO( 데이터 전송 개체 )를 정의할 수 있습니다. DTO는 네트워크를 통해 데이터를 보내는 방법을 정의하는 개체입니다. Book 엔터티에서 작동하는 방식을 살펴보겠습니다. Models 폴더에 두 개의 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
포함합니다.
다음으로 클래스의 두 GET 메서드를 BooksController
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);
}
참고
이 자습서에서는 코드에서 DTO로 수동으로 변환합니다. 또 다른 옵션은 자동 변환을 자동으로 처리하는 AutoMapper 와 같은 라이브러리를 사용하는 것입니다.