使用 DTO 在服務層和 UI 層之間傳輸數據
幾天來我一直試圖弄清楚這一點,但似乎很少有關於 ASP.NET MVC 的特定主題的資訊。我已經在Google上搜尋了幾天,並沒有真正弄清楚這個特定問題。
我有一個 3 層項目。業務、DAL 和 UI/Web 層。在 DAL 中是 dbcontext、儲存庫和工作單元。業務層是一個領域層,包含所有介面和 EF 模型。在業務層中,還有一個帶有用於 EF 模型的 DTO 的服務層和一個訪問儲存庫的通用儲存庫服務。這張圖片應該有助於解釋它。
我的問題是我似乎無法弄清楚如何使用 DTO 從業務層傳輸數據。
我為 DTO 創建了服務類。我有一個 ImageDTO 和模型,對於圖像錨點也是如此。我為每個 DTO 創建了一個服務類。所以我有一個圖像服務和錨點服務。這些服務繼承了儲存庫服務,目前實現了它們自己的服務。但這就是我所得到的。由於這些服務具有通過 IoC 接收 IUnitOfWork 介面的建構子,因此我幾乎陷入了困境。
如果我直接從 UI 引用服務,一切都會正常工作,但我只是想不通如何使用 DTO 將數據從服務層傳輸到 UI 層,反之亦然。
我的服務層:
業務/服務/DTO
public class AnchorDto { public int Id { get; set; } public int x1 { get; set; } public int y1 { get; set; } public int x2 { get; set; } public int y2 { get; set; } public string description { get; set; } public int imageId { get; set; } public int targetImageId { get; set; } public AnchorDto(int Id, int x1, int y1, int x2, int y2, string description, int imageId, int targetImageId) { // Just mapping input to the DTO } } public class ImageDto { public int Id { get; set; } public string name { get; set; } public string title { get; set; } public string description { get; set; } public virtual IList<AnchorDto> anchors { get; set; } public ImageDto(int Id, string name, string title, string description, IList<AnchorDto> anchors ) { // Just mapping input to DTO } }業務/服務/服務
public class RepoService<TEntity> : IRepoService<TEntity> where TEntity : class { private IRepository<TEntity> repo; public RepoService(IUnitOfWork repo) { this.repo = repo.GetRepository<TEntity>(); } public IEnumerable<TEntity> Get( Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "") { return repo.Get(filter, orderBy, includeProperties); } public TEntity GetByID(object id) { return repo.GetByID(id); } public void Insert(TEntity entity) { repo.Insert(entity); } public void Delete(object id) { repo.Delete(id); } public void Delete(TEntity entityToDelete) { repo.Delete(entityToDelete); } public void Update(TEntity entityToUpdate) { repo.Update(entityToUpdate); } }Image Service,IImageService 介面目前是空的,直到我弄清楚我需要實現什麼。
public class ImageService : RepoService<ImageModel>, IImageService { public ImageService(IUnitOfWork repo) : base(repo) { } }目前我的控制器並沒有真正工作並且沒有使用服務層,所以我決定不包括任何這些。一旦我解決了這個問題,我計劃使用自動映射器將 DTO 映射到 ViewModel。
所以現在,請任何知識淵博的人給我我所缺少的想法,以便我能解決這個問題?
您的服務應該接收 DTO,將它們映射到業務實體並將它們發送到儲存庫。它還應該從儲存庫中檢索業務實體,將它們映射到 DTO 並將 DTO 作為響應返回。因此,您的業務實體永遠不會脫離業務層,只有 DTO 會這樣做。
那麼您的 UI\Weblayer 應該不知道業務實體。Web 層應該只知道 DTO。執行此規則非常重要,您的 UI 層不使用服務實現類(應該是私有的),只使用介面。並且服務介面不應該依賴於業務實體,而應該依賴於 DTO。
因此,您需要基於 DTO 的服務介面,並且您的基礎服務類需要另一個用於 DTO 的通用參數。我喜歡有一個實體和 DTO 的基類,因此它們可以聲明為:
//Your UI\presentation layer will work with the interfaces (The inheriting ones) //so it is very important that there is no dependency //on the business entities in the interface, just on the DTOs! protected interface IRepoService<TDto> where TDto: DTOBase { //I'm just adding a couple of methods but you get the idea TDto GetByID(object id); void Update(TDto entityToUpdateDto) } //This is the interface that will be used by your UI layer public IImageService: IRepoService<ImageDTO> { } //This class and the ones inheriting should never be used by your //presentation\UI layer because they depend on the business entities! //(And it is a best practice to depend on interfaces, anyway) protected abstract class RepoService<TEntity, TDto> : IRepoService<TDto> where TEntity : EntityBase where TDto: DTOBase { ... } //This class should never be used by your service layer. //Your UI layer should always use IImageService //You could have a different namespace like Service.Implementation and make sure //it is not included by your UI layer public class ImageService : RepoService<ImageModel, ImageDto>, IImageService { ... }然後,您需要一種在不實際實現映射的情況下將實體和 DTO 之間的映射添加到該基礎服務的方法(因為它取決於每個具體實體和 DTO 類)。您可以聲明執行映射的抽象方法,並且需要在每個特定服務(如
ImageService)上實現。基本 RepoService 的實現如下所示:public TDto GetByID(object id) { //I'm writing it this way so its clear what the method is doing var entity = repo.GetByID(id); var dto = this.EntityToDto(entity); return dto; } public void Update(TDto entityToUpdateDto) { var entity = this.DtoToEntity(entityToUpdateDto) repo.Update(entity); } //These methods will need to be implemented by every service like ImageService protected abstract TEntity DtoToEntity(TDto dto); protected abstract TDto EntityToDto(TEntity entity);或者,您可以聲明映射服務,添加一個具有應由您的 IOC 提供的適當映射服務的依賴項(如果您需要在不同服務上使用相同的映射,這更有意義)。RepoService 的實現如下所示:
private IRepository<TEntity> _repo; private IDtoMappingService<TEntity, TDto> _mappingService; public RepoService(IUnitOfWork repo, IDtoMappingService<TEntity, TDto> mapping) { _repo = repo.GetRepository<TEntity>(); _mappingService = mapping; } public TDto GetByID(object id) { //I'm writing it this way so its clear what the method is doing var entity = repo.GetByID(id); var dto = _mappingService.EntityToDto(entity); return dto; } public void Update(TDto entityToUpdateDto) { var entity = _mappingService.DtoToEntity(entityToUpdateDto) repo.Update(entity); } //You will need to create implementations of this interface for each //TEntity-TDto combination //Then include them in your dependency injection configuration public interface IDtoMappingService<TEntity, TDto> where TEntity : EntityBase where TDto: DTOBase { public TEntity DtoToEntity(TDto dto); public TDto EntityToDto(TEntity entity); }在這兩種情況下(抽象方法或映射服務),您都可以手動實現實體和 DTO 之間的映射,也可以使用Automapper之類的工具。但是在使用 AutoMapper 和實體框架時應該非常小心,儘管那是另一個話題!(Google一點點並收集有關該主題的一些資訊。作為第一個建議,請注意在載入數據時對數據庫執行的查詢,這樣您就不會載入超過需要的內容或發送很多查詢。保存數據時請注意到您的收藏和關係)
可能是長文章,但我希望它有幫助!