Używanie DTO do przesyłania danych między warstwą usługi a warstwą interfejsu użytkownika

Próbowałem to rozgryźć od kilku dni, ale wydaje się, że jest bardzo mało informacji na ten konkretny temat z ASP.NET MVC. Googlowałem się od kilku dni i nie byłem w stanie nic wymyślić na ten konkretny temat.

Mam trójwarstwowy projekt. Business, DAL i UI/Web layer. W DAL jest dbcontext, repozytorium i jednostka pracy. W warstwie biznesowej jest warstwa domeny ze wszystkimi interfejsami i modelami EF. W warstwie biznesowej znajduje się również warstwa usługowa z DTOs dla modeli EF i ogólna usługa repozytorium, która uzyskuje dostęp do repozytorium. to zdjęcie powinno pomóc to wyjaśnić.

Mój problem polega na tym, że po prostu nie mogę zrozumieć, jak używać DTOs do przesyłania danych z warstwy biznesowej.

Stworzyłem klasy usług dla DTOs. Mam ImageDTO i model i to samo dla kotwic obrazkowych. Stworzyłem klasę serwisową dla każdego DTO. Więc mam usługę wizerunkową i obsługę kotwiczną. Usługi te odziedziczyć usługę repozytorium i w tej chwili wdrożyć własne usługi. Ale tak daleko zaszedłem. Ponieważ te usługi mają konstruktory, które otrzymują interfejs IUnitOfWork za pośrednictwem IoC, prawie utknąłem.

Jeśli odwołuję się do usługi bezpośrednio z interfejsu, wszystko działa tak, jak powinno, ale po prostu nie mogę zrozumieć, jak używać DTOs do przesyłania danych zarówno z warstwy usługi do warstwy interfejsu, jak i na odwrót.

Moja warstwa usług:

Biznes/Usługi / 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
    }
}

Biznes/Usługi / Usługi

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

Usługa obrazu, interfejs IImageService jest obecnie pusty, dopóki nie dowiem się, co muszę zaimplementować.

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

    }
}

W tej chwili moje Kontrolery nie działają i nie korzystają z warstwy usług, więc zdecydowałem się nie włączać żadnego z nich. Planuję zmapować DTOs do ViewModels za pomocą auto mapper, gdy już rozwiążę ten problem.

Więc teraz, proszę kogokolwiek wystarczająco kompetentnego żeby dać mi ten pomysł, którego mi brakuje, żebym mógł to rozgryźć?

Author: grimurd, 2013-06-01

1 answers

Twój serwis powinien otrzymywać DTOs, mapować je do podmiotów gospodarczych i wysyłać do repozytorium. Powinien również pobierać podmioty gospodarcze z repozytorium, mapować je do DTOs i zwracać DTOs jako odpowiedzi. Tak więc twoje podmioty biznesowe nigdy nie wychodzą z warstwy biznesowej, tylko Dto.

Wtedy twój Ui\Weblayer powinien być nieświadomy podmiotów gospodarczych. Warstwa internetowa powinna wiedzieć tylko o DTOs. Aby wyegzekwować tę regułę jest bardzo ważne, że twoja warstwa interfejsu nie używa klas implementacji usług (które powinny być prywatne), tylko interfejsów. A Interfejsy usług nie powinny zależeć od podmiotów gospodarczych, tylko od Dto.

Więc potrzebujesz interfejsów usług opartych na DTOs, a twoja podstawowa klasa usług potrzebuje innego ogólnego argumentu dla DTO. Lubię mieć klasę bazową dla encji i DTOs, aby mogły być zadeklarowane jako:

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

Następnie potrzebujesz sposobu dodawania mapowania między encjami i DTO do tej usługi bazowej bez faktycznie implementacja mapowania (ponieważ zależy to od każdego konkretnego bytu i klas DTO). Możesz zadeklarować abstrakcyjne metody, które wykonują mapowanie i będą musiały być zaimplementowane w każdej konkretnej usłudze(np. ImageService). Implementacja Reposerwisu bazowego wyglądałaby następująco:

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

Lub możesz zadeklarować usługi mapowania, dodając zależność z odpowiednią usługą mapowania, która powinna być dostarczona przez IOC (ma to sens, jeśli potrzebujesz tego samego mapowania na różnych usługi). Implementacja RepoService wyglądałaby następująco:

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

W obu przypadkach (metody abstrakcyjne lub usługi mapowania), można zaimplementować mapowanie między encjami a DTOs ręcznie lub za pomocą narzędzia typu Automapper. Ale powinieneś być bardzo ostrożny podczas korzystania z AutoMapper i entity framework, chociaż to inny temat! (Google trochę o tym i zebrać kilka informacji na ten temat. Jako pierwsza porada zwróć uwagę na zapytania wykonywane przeciwko baza danych podczas ładowania danych, więc nie załadować więcej niż potrzebne lub wysłać wiele zapytań. Podczas zapisywania danych zwróć uwagę na swoje kolekcje i relacje)

Długi post może, ale mam nadzieję, że pomoże!

 30
Author: Daniel J.G.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-01 12:02:15