Search code examples
asp.net-mvcasp.net-web-apiasp.net-web-api2dtoasp.net-web-api-routing

Web Api Routing Multiple DTOs Same POCO


I am in doubt about how to implement the Web API Routing when you have multiple DTOs for the same POCO.

Let us imagine the following scenario:

  • you have a POCO Class with. Let's say 100 properties
  • you need to display lists of that class in different platforms/UIs

i.e.

List 1 Prop A1 | Prop A2 | Prop A3 | Prop A4

List 2 Prop A21 | Prop A22 | Prop A23 | Prop A24 | Prop A25 | Prop A26 | Prop A27 | Prop A28 |

I will call it Company, since there are many examples using this class name

Is it correct to implement a Web API + DTOs strategy like this?

Classes are:

  • Company (the POCO class)
  • CompanyDetailDTO
  • CompanyLightListDTO
  • CompanyMediumListDTO

Example:

public class CompaniesController : ApiController
{
    private MultiTierWebApiContext db = new MultiTierWebApiContext();

    private static readonly Expression<Func<Company, CompanyDetailDTO>> AsCompanyDetailDTO =
         x => new CompanyDetailDTO
         {
             Name = x.Name,
             Email = x.Email,
             isCustomer = x.isCustomer,
             isSupplier = x.isSupplier,
             Id = x.Id
         };

    private static readonly Expression<Func<Company, CompanyMediumListDTO>> AsCompanyMediumListDTO =
        x => new CompanyMediumListDTO
        {
            Name = x.Name,
            Email = x.Email,
            Id = x.Id
        };

    private static readonly Expression<Func<Company, CompanyLightListDTO>> AsCompanyLightListDTO =
        x => new CompanyLightListDTO
        {
            Name = x.Name,
            Id = x.Id
        };

    // GET: api/Companies/LightList
    [Route("api/Companies/LightList")] **It works, but is this a correct way to do it?**
    [ResponseType(typeof(CompanyLightListDTO))]
    public IQueryable<CompanyLightListDTO>GetCompaniesLightList()
    {
        return db.Companies.Select(AsCompanyLightListDTO);   // default
    }

    // GET: api/Companies/MediumList
    [Route("api/Companies/MediumList")]
    [ResponseType(typeof(CompanyMediumListDTO))]
    public IQueryable<CompanyMediumListDTO> GetCompaniesMediumList()
    {
        return db.Companies.Select(AsCompanyMediumListDTO);   // default
    }

// remaining code removed for simplicity
}

Thanks in advance for further help.


Solution

  • I would say you are on the right track with only providing the relevant information related to the particular DTO. Don't provide more data than necessary.

    If you look at the following walk-through you will see how they follow a similar pattern to what you have in your example. Create a REST API with Attribute Routing in ASP.NET Web API 2

    Quoting for reference:

    Instead, I want this request to return a subset of the fields. Also, I want it to return the author's name, rather than the author ID. To accomplish this, we'll modify the controller methods to return a data transfer object (DTO) instead of the EF model. A DTO is an object that is designed only to carry data.

    // Typed lambda expression for Select() method.
    private static readonly Expression<Func<Book, BookDto>> AsBookDto =
        x => new BookDto
        {
            Title = x.Title,
            Author = x.Author.Name,
            Genre = x.Genre
        };
    
    // GET api/Books
    public IQueryable<BookDto> GetBooks()
    {
        return db.Books.Include(b => b.Author).Select(AsBookDto);
    }
    

    My advice would be to separate the transformation/projection functionality out of the controller (SoC) into their own service(s) (SRP) and inject them (DI) into the ApiController. It will keep your Controllers light.