1

First, the project is using the clean architecture, the root of my problems is the clean architecture, the "messy" way I don't have half the problems I've been facing, but I need to learn it... Everything I'm implementing here works without the clean architecture, the hard part is adapting the codes.

Outbursts aside...

After implementing the pagination with headers that apparently needs a special type of list containing the pagination data, the automapper just presents me with errors.

In the infrastructure layer I have a generic repository (I disagree a little with the use of repositories but whoever told me to study clean architecture told me to do it that way)

public IQueryable<T> Get()
{
    return _context.Set<T>().AsNoTracking();
}

public async Task<PagedList<T>> GetAsync(PagingParameters parameters, Expression<Func<T, object>> orderByExpression)
{
    var query = Get();

    if (orderByExpression != null)
    {
        query = query.OrderBy(orderByExpression);
    }

    var count = await query.CountAsync();
    var items = await query
                .Skip((parameters.PageNumber - 1) * parameters.PageSize)
                .Take(parameters.PageSize).ToListAsync();

    return new PagedList<T>(items, count, parameters.PageNumber, parameters.PageSize);
}

CategoryRepository exists and inherits this generic repository but has no specific methods.

In the application layer I have services (and their interfaces) and DTOs, in this case for categories.

In CategoryServices I have this code that is connected to the previous code from the repository:

        public async Task<PagedList<CategoryDTO>> GetCategories(PagingParameters parameters)
        {
            Expression<Func<Category, Object>> orderByExpression;
            switch (parameters.OrderedBy.ToLower())
            {
                case "name":
                    orderByExpression = x => x.Nome;
                    break;
                default:
                    orderByExpression = x => x.Id;
                    break;
            }

            var categoriesEntity = await _categoryRepository.GetAsync(parameters, orderByExpression);
            
            // Debugging, the error happens here:
            return _mapper.Map<PagedList<CategoryDTO>>(categoriesEntity);
        }

PagingParameters has paging values, in it I can also inform a string that defines the order of the list as ID or name (that's why the switch case for linq expressions).

Finally in CategoriesController I have a Get action which has a metadata to add the pagination data to the header for the consumer software to use.

        [HttpGet]
        public async Task<ActionResult<PagedList<CategoriaDTO>>> Get([FromQuery] PagingParameters parameters)
        {
            var categorias = await _categoriaService.GetCategorias(parameters);

            // Header for pagination
            var metadata = new
            {
                categorias.TotalCount,
                categorias.PageSize,
                categorias.CurrentPage,
                categorias.TotalPages,
                categorias.HasNext,
                categorias.HasPrevious
            };

            Response.Headers.Add("X-Pagination", JsonSerializer.Serialize(metadata));

            return Ok(categorias);

        }

This is the PagedList:

namespace Store.Domain.Pagination
{
    public class PagedList<T> : List<T>
    {
        public int CurrentPage { get; set; }
        public int TotalPages { get; set; }
        public int PageSize { get; set; }
        public int TotalCount { get; set; }

        public bool HasPrevious => CurrentPage > 1;
        public bool HasNext => CurrentPage < TotalPages;

        public PagedList(List<T> items, int count, int pageNumber, int pageSize)
        {
            TotalCount = count;
            PageSize = pageSize;
            CurrentPage = pageNumber;
            TotalPages = (int)Math.Ceiling(count / (double)pageSize);

            AddRange(items);
        }
    

Before adding the pagedlist I used the return with IEnumerable, which worked perfectly, after adding the pagedlist I have been getting error like:
Error mapping types.

The mapping for category looks like this:
CreateMap<Category, CategoryDTO>().ReverseMap();

I tried creating a mapping for the pagedlist:

Like this:
CreateMap(typeof(PagedList<Category>), typeof(PagedList<CategoryDTO>));

And like this (but this just dont work lmao, but i tryed):
CreateMap<typeof(PagedList<Category>), typeof(PagedList<CategoryDTO>)>();

But when I try to create a mapping involving the PagedList I get this error:
"Incorrect number of arguments supplied for call to method 'Store.Application.DTOs.CategoryDTO get_Item(Int32)' (Parameter 'property')"

Tried to create a DTO for PagedList and mapped:
CreateMap(typeof(PagedList<Category>), typeof(PagedListDTO<CategoryDTO>));
But when I try this I get an error asking for an empty constructor in the PagedList, but even doing that the error persists.

Fildor
  • 14,510
  • 4
  • 35
  • 67
Nayala
  • 31
  • 6
  • 1
    Why do you create the Pages List before you map? I'd use Automapper's `ProjectTo` to map directly into DTOs and put those in the PagedList instead of mapping the PagedList. – Fildor Jun 20 '23 at 19:21
  • Because I need to return the pagination data that is taken from the repository because that's where the transaction with the db happens. At least that's the logic that came to my mind, I didn't have any other ideas to solve this. – Nayala Jun 20 '23 at 19:26
  • Oh wait, you _derive_ from `List` ? Don't do that. Composition over inheritance. – Fildor Jun 20 '23 at 19:32
  • Related to that: https://stackoverflow.com/q/21692193/982149 – Fildor Jun 20 '23 at 19:41
  • Well, I found a solution that was: return a tuple in the method that is in the repository, and then in the method that is in CategoryServices I get the items from the tuple and assemble the PagedList after converting the list into DTO by the automapper. I can't use ProjectTo because of the rules of the layer the repository is in. I was adapting the pagination from this example here, as I only added a few extra fields to the list, I didn't think there would be a problem: https://code-maze.com/paging-aspnet-core-webapi/ – Nayala Jun 22 '23 at 14:07
  • Maybe you can post it as an answer? – Fildor Jun 22 '23 at 15:30

1 Answers1

0

Based on the points mentioned by Fildor, my solution was:

  • Return a tuple in the method that is in the repository with a list and an object with a new object called pagingInfo (it just contains the paging data without composing or inheriting a list), instead of a single PagedList.
  • In the method that is in CategoryServices I get the tuple items.
  • Convert list into DTO by automapper.
  • And return to the controller the ListDTO and the pagingInfo.
  • In the controller, instead of creating a metadata, I just attach the pagingInfo to the response header.
  • Finally, I return an Ok Result with the List of itemsDto.

I believe that the pagination being in the header of the response is more than enough for the frontend to work with it, and so I don't need to use a composition that requires extra work on receipt and I don't even create an inheritance of List that apparently is not recommended.

Nayala
  • 31
  • 6