使用 DTO 在服务层和 UI 层之间传输数据

Posted

技术标签:

【中文标题】使用 DTO 在服务层和 UI 层之间传输数据【英文标题】:Using DTO to transfer data between service layer and UI layer 【发布时间】:2013-05-27 19:05:28 【问题描述】:

几天来我一直在尝试解决这个问题,但似乎很少有关于 ASP.NET MVC 的特定主题的信息。我已经在谷歌上搜索了好几天,并没有真正弄清楚这个特定问题。

我有一个 3 层项目。业务、DAL 和 UI/Web 层。在 DAL 中是 dbcontext、存储库和工作单元。业务层是一个领域层,包含所有接口和 EF 模型。在业务层中,还有一个带有 EF 模型的 DTO 的服务层和一个访问存储库的通用存储库服务。 This图片应该有助于解释它。

我的问题是我似乎无法弄清楚如何使用 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);
        
    

图像服务,IImageService 接口目前是空的,直到我弄清楚我需要实现什么。

public class ImageService : RepoService<ImageModel>, IImageService

    public ImageService(IUnitOfWork repo)
        : base(repo)
    

    

目前我的控制器并没有真正工作并且没有使用服务层,所以我决定不包括任何这些。解决此问题后,我计划使用自动映射器将 DTO 映射到 ViewModel。

所以现在,请任何有足够知识的人告诉我我缺少的想法,以便我可以解决这个问题?

【问题讨论】:

【参考方案1】:

您的服务应该接收 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 和实体框架时应该非常小心,尽管那是另一个话题! (谷歌一点,并收集有关该主题的一些信息。作为第一个建议,请注意在加载数据时对数据库执行的查询,这样您就不会加载超出需要或发送很多查询。保存数据时请注意到您的收藏和关系)

可能会长篇大论,但希望对你有帮助!

【讨论】:

您的示例中的 DTOBase/Tentity 类包含什么?它只是一个具有 id 属性的抽象类吗?另外,它们将位于哪一层? 如果它们不包含任何逻辑,那么它们就是接口。但通常您将拥有像 Id 这样的公共属性,因此它们是具有该公共属性的抽象类。 DTOBase 将位于服务层,EntityBase 位于业务层。 我明白了,非常感谢您的帮助,这真的帮助了我:) 很好的答案。给了我很多灵感! 从干净架构和洋葱架构的角度来看,这意味着 DTO 也需要在核心项目中。这显然是不行的。为什么不在 UI 和服务之间使用控制器?

以上是关于使用 DTO 在服务层和 UI 层之间传输数据的主要内容,如果未能解决你的问题,请参考以下文章

DTO中的继承和自定义逻辑[关闭]

将 UI 相关信息持久化到分层应用程序中的数据库

DAO DTO

DAO与DTO

如何在 UI、BLL、DAL 之间使用 DTO

网络层和传输层