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

时间:2013-05-31 21:14:38

标签: asp.net-mvc repository dto unit-of-work n-tier-architecture

我一直在努力解决这个问题,但是在ASP.NET MVC上这个特定主题的信息似乎很少。我一直在谷歌搜索几天,并没有真正能够解决这个特定的问题。

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

我的问题是,我似乎无法弄清楚如何使用DTO从业务层传输数据。

我为DTO创建了服务类。我有一个ImageDTO和模型和图像锚点相同。我为每个DTO创建了一个服务类。所以我有一个图像服务和主播服务。这些服务继承存储库服务,并且目前实现自己的服务。但就我而言,那就是那个。由于这些服务具有通过IoC接收IUnitOfWork接口的构造函数,因此我几乎陷入困境。

如果我直接从UI引用服务,一切正常,但我无法理解如何使用DTO将数据从服务层传输到UI层,反之亦然。 / p>

我的服务层:

商业/服务/ 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映射到ViewModels。

所以,现在,请那些知识渊博的人给我这个我想念的想法,以便我能解决这个问题吗?

1 个答案:

答案 0 :(得分:35)

您的服务应该接收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);
}

在这两种情况下(抽象方法或地图服务),您可以手动或使用Automapper之类的工具实现实体和DTO之间的映射。但是在使用AutoMapper和实体框架时应该非常小心,尽管这是另一个主题! (谷歌有点关于这一点并收集有关该主题的一些信息。作为第一个建议,请注意在加载数据时对数据库执行的查询,以免加载超过需要或发送许多查询。保存数据时注意你的收藏和关系)

可能很长,但我希望它有所帮助!