第 5 章 使用 Entity Framework Core
5.4 重構(gòu) Controller 和 Action
重構(gòu) AuthorController
構(gòu)造函數(shù)重構(gòu)
public IMapper Mapper { get; set; }
public IRepositoryWrapper RepositoryWrapper { get; set; }
public AuthorController(IRepositoryWrapper repositoryWrapper, IMapper mapper)
{
RepositoryWrapper = repositoryWrapper;
Mapper = mapper;
}
IRepositoryWrapper 用于操作倉儲(chǔ)類,IMapper 用于處理對象之間的映射關(guān)系
獲取作者列表重構(gòu)
[HttpGet]
public async Task<ActionResult<List<AuthorDto>>> GetAuthorsAsync()
{
var authors = (await RepositoryWrapper.Author.GetAllAsync()).OrderBy(author => author.Name);
var authorDtoList = Mapper.Map<IEnumerable<AuthorDto>>(authors);
return authorDtoList.ToList();
}
在 RepositoryBase 類中使用的延遲執(zhí)行會(huì)在程序運(yùn)行到 Mapper.Map 時(shí)才實(shí)際去執(zhí)行查詢,獲取單個(gè)資源的方法的重構(gòu)思路類似
創(chuàng)建資源方法重構(gòu)
[HttpPost]
public async Task<IActionResult> CreateAuthorAsync(AuthorForCreationDto authorForCreationDto)
{
var author = Mapper.Map<Author>(authorForCreationDto);
RepositoryWrapper.Author.Create(author);
var result = await RepositoryWrapper.Author.SaveAsync();
if (!result)
{
throw new Exception("創(chuàng)建資源 author 失敗");
}
var authorCreated = Mapper.Map<AuthorDto>(author);
// 返回201 Created 狀態(tài)碼,并在響應(yīng)消息頭中包含 Location 項(xiàng),它的值是新創(chuàng)建資源的 URL
// 第一個(gè)參數(shù)是要調(diào)用 Action 的路由名稱
// 第二個(gè)參數(shù)是包含要調(diào)用 Action 所需要參數(shù)的匿名對象
// 最后一個(gè)參數(shù)是代表添加成功后的資源本身
return CreatedAtRoute(nameof(GetAuthorsAsync), new { authorId = authorCreated.Id }, authorCreated);
}
當(dāng)數(shù)據(jù)發(fā)生變化時(shí),EF Core 會(huì)將實(shí)體對象的屬性及其狀態(tài)修改,只有在調(diào)用 DbContext 類的 Save 或 SaveAsync 方法后,所有的修改才會(huì)存儲(chǔ)到數(shù)據(jù)庫中
刪除資源方法重構(gòu)
[HttpDelete("{authorId}")]
public async Task<ActionResult> DeleteAuthorAsync(Guid authorId)
{
var author = await RepositoryWrapper.Author.GetByIdAsync(authorId);
if (author == null)
{
return NotFound();
}
RepositoryWrapper.Author.Delete(author);
var result = await RepositoryWrapper.Author.SaveAsync();
if (!result)
{
throw new Exception("刪除資源 author 失敗");
}
return NoContent();
}
重構(gòu) BookController
由于所有 Action 操作都基于一個(gè)存在的 Author 資源,因此每個(gè) Action 中都會(huì)包含 IsExistAsync 邏輯,因此可以放在自定義過濾器中
namespace Library.API.Filters
{
public class CheckAuthorExistFilterAttribute : ActionFilterAttribute
{
public IRepositoryWrapper RepositoryWrapper { get; set; }
public CheckAuthorExistFilterAttribute(IRepositoryWrapper repositoryWrapper)
{
RepositoryWrapper = repositoryWrapper;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var authorIdParameter = context.ActionArguments.Single(m => m.Key == "authorId");
Guid authorId = (Guid) authorIdParameter.Value;
var isExist = await RepositoryWrapper.Author.IsExistAsync(authorId);
if (!isExist)
{
context.Result = new NotFoundResult();
}
await base.OnActionExecutionAsync(context, next);
}
}
}
如果檢查結(jié)果不存在,則結(jié)束本次請求,并返回 404 Not Found 狀態(tài)碼;反之,則繼續(xù)完成 MVC 請求
接著,在 ConfigureServices 中注入
services.AddScoped<CheckAuthorExistFilterAttribute>();
注入之后可以在 BookController 中通過特性應(yīng)用
[ServiceFilter(typeof(CheckAuthorExistFilterAttribute))]
public class BookController : ControllerBase
獲取指定作者的所有圖書,可以這么寫
var books = await RepositoryWrapper.Book.GetByConditionAsync(book => book.Id == authorId);
但是更推薦在 IBookRepository 中定義專門的接口
Task<IEnumerable<Book>> GetBooksAsync(Guid authorId);
并在 BookRepository 中實(shí)現(xiàn)
public Task<IEnumerable<Book>> GetBooksAsync(Guid authorId)
{
return Task.FromResult(DbContext.Set<Book>().Where(book => book.AuthorId == authorId).AsEnumerable());
}
在 BookController 中重構(gòu) GetBooks
[HttpGet]
public async Task<ActionResult<List<BookDto>>> GetBooksAsync(Guid authorId)
{
var books = await RepositoryWrapper.Book.GetBooksAsync(authorId);
var bookDtoList = Mapper.Map<IEnumerable<BookDto>>(books);
return bookDtoList.ToList();
}
重構(gòu) GetBook 方法與此類似
Task<Book> GetBookAsync(Guid authorId, Guid bookId);
public async Task<Book> GetBookAsync(Guid authorId, Guid bookId)
{
return await DbContext.Set<Book>()
.SingleOrDefaultAsync(book => book.AuthorId == authorId && book.Id == bookId);
}
[HttpGet("{bookId}", Name = nameof(GetBookAsync))]
public async Task<ActionResult<BookDto>> GetBookAsync(Guid authorId, Guid bookId)
{
var book = await RepositoryWrapper.Book.GetBookAsync(authorId, bookId);
if (book == null)
{
return NotFound();
}
var bookDto = Mapper.Map<BookDto>(book);
return bookDto;
}
當(dāng)添加一個(gè)子級資源,將 BookForCreationDto 對象映射為 Book 后,還需要為其 AuthorId 屬性設(shè)置值,否則創(chuàng)建失敗
[HttpPost]
public async Task<IActionResult> AddBookAsync(Guid authorId, BookForCreationDto bookForCreationDto)
{
var book = Mapper.Map<Book>(bookForCreationDto);
book.AuthorId = authorId;
RepositoryWrapper.Book.Create(book);
if (!await RepositoryWrapper.Book.SaveAsync())
{
throw new Exception("創(chuàng)建資源 Book 失敗");
}
var bookDto = Mapper.Map<BookDto>(book);
return CreatedAtRoute(nameof(GetBookAsync), new {bookId = bookDto.Id}, bookDto);
}
對于更新子級資源或部分更新子級資源,處了檢查父級、子級資源是否存在外,還應(yīng)該使用 IMapper 接口中的 Map 方法的另一個(gè)重載
object Map(object source, object destination, Type sourceType, Type destinationType);
它能將源映射到一個(gè)已經(jīng)存在的對象,重載是為了將 BookForUpdateDto 映射到已經(jīng)從數(shù)據(jù)庫中獲取到的 Book 實(shí)體
[HttpPut("{bookId}")]
public async Task<IActionResult> UpdateBookAsync(Guid authorId, Guid bookId, BookForUpdateDto updateBook)
{
var book = await RepositoryWrapper.Book.GetBookAsync(authorId, bookId);
if (book == null)
{
return NotFound();
}
Mapper.Map(updateBook, book, typeof(BookForUpdateDto), typeof(Book));
RepositoryWrapper.Book.Update(book);
if (!await RepositoryWrapper.Book.SaveAsync())
{
throw new Exception("更新資源 Book 失敗");
}
return NoContent();
}
部分更新的實(shí)現(xiàn)邏輯與此類似,不同的是獲取需要部分更新的 Book 實(shí)體后,首先將它映射為 BookForUpdateDto 類型的對象,其次使用 JsonPatchDocument 的 ApplyTo 方法將更新信息應(yīng)用到映射后的 BookForUpdateDto 對象,接著再將它映射到 Book 實(shí)體得到更新后的值
[HttpPatch("{bookId}")]
public async Task<IActionResult> PartiallyUpdateBookAsync(Guid authorId, Guid bookId, JsonPatchDocument<BookForUpdateDto> patchDocument)
{
var book = await RepositoryWrapper.Book.GetBookAsync(authorId, bookId);
if (book == null)
{
return NotFound();
}
var bookUpdateDto = Mapper.Map<BookForUpdateDto>(book);
patchDocument.ApplyTo(bookUpdateDto, ModelState);
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
Mapper.Map(bookUpdateDto, book, typeof(BookForUpdateDto), typeof(Book));
RepositoryWrapper.Book.Update(book);
if (!await RepositoryWrapper.Book.SaveAsync())
{
throw new Exception("更新資源 Book 失敗");
}
return NoContent();
}
本文摘自 :https://blog.51cto.com/u