Make additional asynchronous call to UserManager<T> after pagination - c#

I have a LINQ query that gets the results from a database with filtering and pagination and that maps the result to the PaginatedList<T>.
var bookReservationList = await _dbContext.BookReservations
.Include(x => x.Books).Select(
book => new BookRequestViewModel
{
Author = book.Books.Author
}).PaginatedListAsync(pageNumber, pageSize);
Now with the result obtained from the bookReservationList in the above LINQ query, I wanted to call the GetUserEmailAsync to get the
Requester = await _identityService.GetUserEmailAsync(book.RequesterId), which is the call to UserManager<T>
I have been trying in this way but I am failing: to map the result to Task<PaginatedList>
var res = bookReservationList.Items.Select(async y => new BookRequestViewModel
{
Requester = await _identityService.GetUserEmailAsync(y.RequesterId);
});
PaginatedListAsync
public static Task<PaginatedList<TDestination>> PaginatedListAsync<TDestination>(this IQueryable<TDestination> queryable, int pageNumber, int pageSize)
{
return PaginatedList<TDestination>.CreateAsync(queryable, pageNumber, pageSize);
}
PaginatedList
public class PaginatedList<T>
{
public List<T> Items { get; }
public int PageIndex { get; }
public int TotalPages { get; }
public int TotalCount { get; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
TotalCount = count;
Items = items;
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
The implementation of GetUserEmailAsync is done as:
public interface IIdentityService
{
Task<string> GetUserEmailAsync(string userId);
}
ViewModel
public class BookRequestViewModel
{
public int Id { get; set; }
public string Requester { get; set; }
public string RequesterId { get; set; }
public DateTime RequestDate { get; set; }
}
Basically, I want to return the result as
Task<PaginatedList<BookRequestViewModel>> GetRequestedBookList(int pageNumber, int pageSize); but failing to do so.

You can not do that with LINQ. You have to use foreach
foreach (y in bookReservationList.Items)
{
y.Requester = await _identityService.GetUserEmailAsync(y.RequesterId);
}

Related

Pagination Not Returning Any Results in DotNetCore 3.0

I've added paging to my headers in DotNetCore 3.0 and I've followed a video tutorial on Udemy to do this, but it's not returning any results in the response. Before I added paging this worked fine and did return the users, but since adding the headers the status response is 200 OK as expected, but no results.
This modifies the headers to return the page size, current page etc (this is where I suspect the problem is as the camelCaseFormatter is not applied either):
public static void AddPagination(this HttpResponse response, int currentPage, int itemsPerPage, int totalItems, int totalPages) {
var paginationHeader = new PaginationHeader(currentPage, itemsPerPage, totalItems, totalPages);
var camelCaseFormatter = new JsonSerializerSettings();
camelCaseFormatter.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
response.Headers.Add("Pagination", JsonConvert.SerializeObject((paginationHeader, camelCaseFormatter)));
response.Headers.Add("Access-Control-Expose-Headers", "Pagination");
}
This is the Users Controller which does the work of actually getting the users from the DB:
[HttpGet]
public async Task<IActionResult> GetUsers([FromQuery]UserParams userParams) {
var users = await _repo.GetUsers(userParams);
var usersToReturn = _mapper.Map<IEnumerable<UserForListDto>>(users);
Response.AddPagination(users.CurrentPage, users.PageSize, users.TotalCount, users.TotalPages);
return Ok(usersToReturn);
}
This is the Repo (so the GetUsers call that is above):
public async Task<PagedList<User>> GetUsers(UserParams userParams)
{
var users = _context.Users.Include(p => p.Photos);
return await PagedList<User>.CreateAsync(users, userParams.PageSize, userParams.PageSize);
}
The CreateAsync Method and PagedList Class:
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 PagedList(List<T> items, int count, int pageNumber, int pageSize)
{
TotalCount = count;
PageSize = pageNumber;
CurrentPage = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double) pageSize);
this.AddRange(items);
}
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageNumber, int pageSize) {
var count = await source.CountAsync();
var items = await source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageNumber, pageSize);
}
}
And Finally the UserParams class used to calculate the pageSize etc.
public class UserParams
{
private const int MaxPageSize = 50;
public int PageNumber { get; set; } = 1;
private int pageSize = 10;
public int PageSize
{
get { return pageSize; }
set { pageSize = (value > MaxPageSize) ? MaxPageSize : value; }
}
}
The result I get back from Postman is a Status 200 OK with an empty JSON object ([]).

How to use AutoMapper with PagedLists?

I had automapper working but then added in the PagedList and now it does not work. How
do I tell it just to map the RfReport to RfReportModel but keep the PagedList parameters?
AutoMapper.AutoMapperMappingException: 'Error mapping types.'
ArgumentException: PWDRS.Core.Helpers.PagedList`1[PWDRS.Application.Models.RfReportModel] needs to have a constructor with 0 args or only optional args.
public PagedList<RfReportModel> GetRfReportListPaged(RfReportParameters rfReportParameters)
{
PagedList<RfReport> rfReports = _rfReportRepository.GetRfReportListPaged(rfReportParameters);
PagedList<RfReportModel> mapped = _mapper.Map<PagedList<RfReportModel>>(rfReports);
return mapped;
}
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 static PagedList<T> ToPagedList(IEnumerable<T> source, int pageNumber, int pageSize)
{
var count = source.Count();
var items = source.Skip((pageNumber - 1) * pageSize).Take(pageSize).ToList();
return new PagedList<T>(items, count, pageNumber, pageSize);
}
}
EDIT 1: I could not figure out how to do it in automapper but I realized I could recreate the PagedList easily like this to effectively do the same thing.
public PagedList<RfReportModel> GetRfReportListPaged(RfReportParameters rfReportParameters)
{
PagedList<RfReport> rfReports = _rfReportRepository.GetRfReportListPaged(rfReportParameters);
IEnumerable<RfReportModel> mapped = _mapper.Map<IEnumerable<RfReportModel>>(rfReports);
PagedList<RfReportModel> plMapped = PagedList<RfReportModel>.ToPagedList(
mapped,
rfReportParameters.PageNumber,
rfReportParameters.PageSize);
return plMapped;
}

How to serialize enclosing object properties in ASP.NET Core?

I am following the official ASP.NET Core 2.2 paging example over at Microsoft.
Here, I'm adding a PaginatedList.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace BlatantlyCopiedCode
{
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage
{
get
{
return (PageIndex > 1);
}
}
public bool HasNextPage
{
get
{
return (PageIndex < TotalPages);
}
}
public static async Task<PaginatedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
}
and adding this to my EF object Merge
[HttpGet("[action]")]
public async Task<PaginatedList<Merge>> Index(int? pageNumber)
{
var merges = context.Merges;
int pageSize = 20;
return await PaginatedList<Merge>.CreateAsync(merges.AsNoTracking(), pageNumber ?? 1, pageSize);
}
When the result is serialized to JSON in the controller, the properties from PaginatedList<T> aren't serialized, only the List<Merge>. How can I force the properties from PaginatedList<T> to appear in the resulting JSON?
The reason is that it inherits the List<T> which constrains the result to a list.Modify your code to below
public class PaginatedList<T> //: List<T>
{
public int PageIndex { get; private set; }
public int TotalPages { get; private set; }
public List<T> Items { get; set; }
public PaginatedList(List<T> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.Items = new List<T>();
this.Items.AddRange(items);
}
//...
}

Map entity pagination class to dto pagination class

I have the following PaginatedList class.
public class PaginatedList<T> : List<T>
{
public int PageIndex { get; }
public int TotalRecords { get; }
public int TotalPages { get; }
public PaginatedList(IEnumerable<T> items, int totalRecords, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalRecords = totalRecords;
TotalPages = (int)Math.Ceiling(TotalRecords / (double)pageSize);
AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<T>> CreateAsync(
IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PaginatedList<T>(items, count, pageIndex, pageSize);
}
}
I'm using this class to get pagination information for list of entity that is retrieved with EF.
This is how I use this class to return list of users with pagination info.
var users = await PaginatedList<User>.CreateAsync(userQuery, pageIndex, pageSize);
Above call will return PaginatedList<User> object.
If I have a DTO class for that entity, let's call it UserDto. How do I use automapper to convert PaginatedList<User> into PaginatedList<UserDto> so the result will have all userDto objects and also the pagination info?
Otherwise, is there another/better way to achieve something similar?
I ended up creating another factory method within the PaginatedList which will handle the conversion.
public static async Task<PaginatedList<TResult>> CreateAsync<TSource>(
IQueryable<TSource> source, int pageIndex, int pageSize)
{
Ensure.It.IsGreaterThan(0, pageIndex);
Ensure.It.IsGreaterThan(0, pageSize);
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.Select(ufc => Mapper.Map<TResult>(ufc))
.ToListAsync();
return new PaginatedList<TResult>(items, count, pageIndex, pageSize);
}
Then make the existing CreateAsync method to call this new CreateAsync<TSource> method.
public static async Task<PaginatedList<TResult>> CreateAsync(
IQueryable<TResult> source, int pageIndex, int pageSize)
{
return await CreateAsync<TResult>(source, pageIndex, pageSize);
}
With this, if we want to keep returning the same Entity class, we can use it like this
await PaginatedList<User>.CreateAsync(_dbContext.User.AsQueryable(), pageIndex, pageSize)
And if we want to convert the entity class to return a Dto class or other class, it can be used like this
await PaginatedList<UserDto>.CreateAsync(_dbContext.User.AsQueryable(), pageIndex, pageSize)
If we don't convert the Entity into other class, we don't need to specify anything within the automapper configuration. But if you want to map the entity into other class, then it needs to be configured within the automapper.
More or less, here is how the PaginatedList class looks like
public class PaginatedList<TResult> : List<TResult>
{
public int PageIndex { get; }
public int TotalRecords { get; }
public int TotalPages { get; }
public PaginatedList(IEnumerable<TResult> items, int count, int pageIndex, int pageSize)
{
PageIndex = pageIndex;
TotalRecords = count;
TotalPages = (int)Math.Ceiling(TotalRecords / (double)pageSize);
AddRange(items);
}
public bool HasPreviousPage => PageIndex > 1;
public bool HasNextPage => PageIndex < TotalPages;
public static async Task<PaginatedList<TResult>> CreateAsync<TSource>(
IQueryable<TSource> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.Select(ufc => Mapper.Map<TResult>(ufc))
.ToListAsync();
return new PaginatedList<TResult>(items, count, pageIndex, pageSize);
}
public static async Task<PaginatedList<TResult>> CreateAsync(
IQueryable<TResult> source, int pageIndex, int pageSize)
{
return await CreateAsync<TResult>(source, pageIndex, pageSize);
}
}
Automapper is perfect for me, but are going to need to create and default constructor on the PaginatedList class
public PaginatedList()
{
PageIndex = 0;
TotalRecords = 0;
TotalPages = 0;
}
-------------------------------- Example
class User
{
public string Name { get; set; }
}
class UserDto
{
public string NameDto { get; set; }
}
public class UserProfile: Profile
{
public UserProfile()
{
CreateMap<User, UserDto>()
.ForMember(target => target.NameDto, x => x.MapFrom(source => source.Name))
.ReverseMap();
}
}
internal class Program
{
private static void Main(string[] args)
{
Mapper.Initialize(config =>
{
config.AddProfile<UserProfile>();
});
var items = new List<User> { new User { Name = "First name" } };
var users = new PaginatedList<User>(items, 1, 0, 1);
var usersDtos = Mapper.Map<PaginatedList<UserDto>>(users);
}
}

Map generic type that receives constructor parameters with Automapper

My questions is related to a specific scenario that I don't know how to handle it.
I have a mapper registered between Application and ApplicationModel and vice verse.
Now I am calling a method that returns an IPagedList
GetApplications Method:
IPagedList<Application> GetApplications()
{
IQueryable items = context.Applications...
return new PagedList<Application>(items, 1, 10);
}
And this is where I get my error because PagedList doesn't have a default constructor when it tries to make the mapping.
IPagedList<Application> applications = GetApplications();
var toRet = Mapper.Map<IPagedList<ApplicationModel>>(applications); //here I get the error
I tried to figure out how this is done with ConstructUsing but honestly I need help to structure the call correctly if that is the correct path
Bellow are the interface and the implementation of IPagedList
Interface:
public interface IPagedList<T> : IList<T>
{
int CurrentPage { get; }
int TotalPages { get; }
int PageSize { get; }
int TotalCount { get; }
bool HasPrevious { get; }
bool HasNext { get; }
IEnumerable<T> Items { get; }
}
Implementation:
public class PagedList<T> : List<T>, IPagedList<T>
{
public PagedList(IEnumerable<T> items, int count, int pageNumber, int pageSize)
{
Items = items;
TotalCount = count;
PageSize = pageSize;
CurrentPage = pageNumber;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
AddRange(Items);
}
public PagedList(IQueryable<T> source, int pageNumber, int pageSize) : this(source.AsEnumerable(), source.Count(), pageNumber, pageSize)
{
}
public int CurrentPage { get; }
public int TotalPages { get; }
public int PageSize { get; }
public int TotalCount { get; }
public bool HasPrevious => CurrentPage > 1;
public bool HasNext => CurrentPage < TotalPages;
public IEnumerable<T> Items { get; }
}
Use ProjectTo<ApplicationModel>() or map the IQueryable<Applications> to List<ApplicationModels>, then throw it into PagedList's constructor and won't deal with the default constructor issue in AutoMapper.
ProjectTo Example:
var toRet = new PagedList<ApplicationModel>(context.Applications...ProjectTo<ApplicationModel>(), 1, 10);

Categories

Resources