2

I'm having difficulties getting all members mapped in the following scenario:

I have a class that inherits from List<T>:

public class PagedList<T> : List<T>
{
    public int CurrentPage { get; private set; }
    public int TotalPages { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private 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);
    }

    public PagedList()
    {
       //default constructor added because Mapster complained about missing default constructor
    }

    public static async Task<PagedList<T>> ToPagedListAsync(IQueryable<T> source, int pageNumber, int pageSize, CancellationToken cancellationToken = default)
    {
        var count = source.Count();
        var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync(cancellationToken);
        return new PagedList<T>(items, count, pageNumber, pageSize);
    }
}

Implementation:

public async Task<PagedList<UserDTO>> GetAllAsync(UserParameters userParameters, CancellationToken cancellationToken = default)
    {
        PagedList<User> users = await _repositoryManager.UserRepository.GetAllAsync(userParameters, cancellationToken);
        //users.Count = 5
        //users.TotalPages = 10

        TypeAdapterConfig<PagedList<User>, PagedList<UserDTO>>.NewConfig()
            .IncludeMember((member, side) => member.AccessModifier == AccessModifier.Internal || 
            member.AccessModifier == AccessModifier.ProtectedInternal);

        PagedList<UserDTO> usersDTO = users.Adapt<PagedList<UserDTO>>();
        //usersDTO.Count = 5
        //BUT usersDTO.TotalPages = 0 (should be 10)

        return usersDTO;
    }

In the above scenario, the items of the inherited List<User> list in PagedList<User> are converted properly, whereas the other members, i.e. CurrentPage, TotalPages, PageSize, TotalCount, HasPrevious and HasNext are not.

I tried to configure Mapster to include also hidden members, but to no avail:

TypeAdapterConfig<PagedList<User>, PagedList<UserDTO>>.NewConfig()
            .IncludeMember((member, side) => member.AccessModifier == AccessModifier.Internal || 
            member.AccessModifier == AccessModifier.ProtectedInternal);

How do I go about to make this conversion work?

Peter Rundqvist
  • 275
  • 3
  • 18
  • 1
    I gave up and ended up creating an extension method just for my pageslist object... The mapping of the item I still do with mapster. The manual. – Sugarel Dec 06 '22 at 06:39
  • I haven't tried your code, but I see you are using IncludeMember with AccessModifier.Internal or Protected, but your props you have set it as Private. Also, i see there is another attribute you may give it a try. `member.SetterModifier == AccessModifier.Private` . – Ak777 Dec 10 '22 at 00:14
  • ...better idea: [**just don't** subclass `List`](https://stackoverflow.com/questions/21692193/why-not-inherit-from-listt) – Dai Dec 11 '22 at 03:56

2 Answers2

0

I was facing similar issue and didn't find any clear way, so I made a workaround by adding a constructor with count, pageNumber and pageSize properties.

Then I configured to use this constructor by calling ConstructUsing.

TypeAdapterConfig<PagedList<User>, PagedList<UserDTO>>.NewConfig()
    .ConstructUsing(src => 
                    new PagedList<UserDTO>(src.TotalCount, src.PageSize, src.CurrentPage));

public class PagedList<T> : List<T>
{
    public int CurrentPage { get; private set; }
    public int TotalPages { get; private set; }
    public int PageSize { get; private set; }
    public int TotalCount { get; private set; }
    public bool HasPrevious => CurrentPage > 1;
    public bool HasNext => CurrentPage < TotalPages;
    
    public PagedList(List<T> items, int count, int pageNumber, int pageSize) : this(count, pageNumber, pageSize)
    {
        AddRange(items);
    }

    public PagedList(int count, int pageNumber, int pageSize)
    {
        TotalCount = count;
        PageSize = pageSize;
        CurrentPage = pageNumber;
        TotalPages = (int)Math.Ceiling(count / (double)pageSize);
    }


    public static async Task<PagedList<T>> ToPagedListAsync(IQueryable<T> source, int pageNumber, int pageSize, CancellationToken cancellationToken = default)
    {
        var count = source.Count();
        var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync(cancellationToken);
        return new PagedList<T>(items, count, pageNumber, pageSize);
    }
}
user2250152
  • 14,658
  • 4
  • 33
  • 57
  • Thanks for your suggestion. I still can't get this to work. I've replaced my version of the PagedLisst class w/ yours, and then changed my function to this: public async Task> GetAllAsync(...) { var users = await _repositoryManager.UserRepository.GetAllAsync(...); TypeAdapterConfig, PagedList>.NewConfig() .ConstructUsing(src => new PagedList(src.TotalCount, src.PageSize, src.CurrentPage)); var usersDTO = TypeAdapter.Adapt>(users); } => Mapster compile error – Peter Rundqvist Dec 07 '22 at 00:18
  • @PeterRundqvist What does the error message say? – user2250152 Dec 07 '22 at 06:29
0

I have finally found a “solution” to this problem by changing my approach.

I changed my service implementation to:

public async Task<(object, List<UserDTO>)> GetAllAsync(UserParameters userParameters, CancellationToken cancellationToken = default)
    {
        PagedList<User> users = await _repositoryManager.UserRepository.GetAllAsync(userParameters, cancellationToken);

        var metadata = new
        {
            users.TotalCount,
            users.PageSize,
            users.CurrentPage,
            users.TotalPages,
            users.HasNext,
            users.HasPrevious
        };

        var usersDTO = users.Adapt<List<UserDTO>>();

        var retVal = (metadata, usersDTO);

        return retVal;
    }

Then in the receiving controller method, I serialise metadata and adds it to the header and returning the list.

Like this:

public async Task<IActionResult> GetUsers([FromQuery] UserParameters userParameters, CancellationToken cancellationToken)
    {
        var tuple = await _serviceManager.UserService.GetAllAsync(userParameters, cancellationToken);

        var metadata = tuple.Item1;
        var usersDTO = tuple.Item2;

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

        var links = _userLinks.TryGenerateLinks(usersDTO, userParameters.Fields, HttpContext);

        return links.HasLinks ? Ok(links.LinkedEntities) : Ok(links.ShapedEntities);
    }

Thanks to Marinko Spasojevic at Code-Maze for pointing me in the right direction.

Peter Rundqvist
  • 275
  • 3
  • 18