Asp.net-Mvc

使用 DTO 在服務層和 UI 層之間傳輸數據

  • April 20, 2017

幾天來我一直試圖弄清楚這一點,但似乎很少有關於 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一點點並收集有關該主題的一些資訊。作為第一個建議,請注意在載入數據時對數據庫執行的查詢,這樣您就不會載入超過需要的內容或發送很多查詢。保存數據時請注意到您的收藏和關係)

可能是長文章,但我希望它有幫助!

引用自:https://stackoverflow.com/questions/16866102