Mapster not mapping members of generic class inherited from generic list

1.1k Views Asked by At

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?

2

There are 2 best solutions below

0
Peter Rundqvist On BEST ANSWER

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.

2
user2250152 On

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