Using DTO to transfer data between service layer and UI layer
Asked Answered
G

1

18

I've been trying to figure this out for days but there seems to be very little info on this particular subject with ASP.NET MVC. I've been Googling around for days and haven't really been able to figure anything out about this particular issue.

I've got a 3 layer project. Business, DAL and UI/Web layer. In the DAL is dbcontext, repository and unit of work. In the business layer is a domain layer with all the interfaces and the EF models. In the business layer there is also a service layer with DTOs for the EF models and a generic repository service that accesses the repository. This picture should help explain it.

My problem is that i just can't seem to figure out how to use the DTOs to transfer data from the business layer.

I've created service classes for the DTOs. I've got a ImageDTO and model and same for image anchors. I've created a service class for each DTO. So i've got a image service and anchor service. These services inherit the repository service and at the moment implement their own services. But thats about as far as i have gotten. Since these services have constructors that receive a IUnitOfWork interface via IoC i've pretty much gotten stranded.

If i reference the service directly from the UI everything works as it should but i just can't get my mind around how to use DTOs to transmit data both from the service layer to the UI layer and the other way around.

My service layer:

Business/Services/DTOs

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
    }
}

Business/Services/Services

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);
        }
    }

The Image Service, the IImageService interface is currently empty until i figure out what i need to implement.

public class ImageService : RepoService<ImageModel>, IImageService
{
    public ImageService(IUnitOfWork repo)
        : base(repo)
    {

    }
}

At the moment my controllers aren't really working and aren't using the service layer so i decided not to include any of those. I plan to map the DTOs to ViewModels using auto mapper once i've sorted this issue out.

So now, please anyone knowledgeable enough to give me that idea I'm missing so that i can figure this out?

Greenwood answered 31/5, 2013 at 21:14 Comment(0)
F
47

Your service should receive DTOs, map them to business entities and send them to the repository. It should also retrieve business entities from the repository, map them to DTOs and return the DTOs as reponses. So your business entities never get out from the business layer, only the DTOs do.

Then your UI\Weblayer should be unaware of the business entities. The web layer should only know about the DTOs. To enforce this rule is very important that your UI layer does not uses the service implementation classes (which should be private), just the interfaces. And the service interfaces shouldn´t depend on the business entities, just the DTOs.

So you need service interfaces based on DTOs, and your base service class needs another generic argument for the DTO. I like to have a base class for entities and DTOs so they can be declared as:

//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
{
    ...
}

You then need a way of adding the mapping between entities and DTO to that base service without actually implementing the mapping (as it depends on each concrete entity and DTO classes). You could declare abstract methods that perform the mapping and will need to be implemented on each specific service (like ImageService). The implemantion of the base RepoService would look like:

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);

Or you could declare mapping services, adding a dependency with an appropiated mapping service that should be provided by your IOC (This makes more sense if you need the same mapping on different services). The implementation of RepoService would look like:

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);
}

In both cases (abstract methods or mapping services), you can implement the mapping between the entities and DTOs manually or using a tool like Automapper. But you should be very careful when using the AutoMapper and entity framework, although that is another topic! (Google a bit about that and collect some information on the topic. As a first advice pay attention to the queries being executed against the database when loading data so you don´t load more than needed or send many queries. When saving data pay attention to your collections and relationships)

Long post maybe, but I hope it helps!

Friedlander answered 1/6, 2013 at 11:19 Comment(5)
What does the DTOBase/Tentity classes contain in your example? Is it just an abstract class with an id property? Also, in which layer would they be located?Greenwood
If they don´t contain any logic then they would be interfaces. But usually you will have common properties like the Id so they then are abstract classes with that common property. The DTOBase will be located in the service layer and the EntityBase in the business layer.Friedlander
From a clean architecture and onion architecture perspective this would mean, that the DTO need also be inside the core project. And thats clearly a no go. Why not using a controller between UI and Services?Labyrinthodont
Your answer was linked here: https://mcmap.net/q/669283/-pass-dto-to-service-layer, and some comments call it completely wrong. I'm so confused...Colonialism
@Colonialism its been 10 years, meanwhile the understanding of concepts like service or DTO changed, and architecture styles have evolved! My answer (and I believe OP) assumed controllers had their own models as part of the MVC pattern. Also, SOA was in vogue and I saw the service as an API exposed to clients, the MVC app being one of them. If you can abstract/ignore/translate concepts like DTO/service to your current architecture style and simply think of decoupling layers, the answer might still be useful as inspiration on how to structure the code.Friedlander

© 2022 - 2024 — McMap. All rights reserved.