ToPaginatedListAsync
public static Task&lt;PaginatedList&lt;T&gt;&gt; ToPaginatedListAsync&lt;T&gt;(
  this IQueryable&lt;T&gt; source, int pageSize, int pageIndex = 1)



Your method says Async, returns a Task&lt;PaginatedList&lt;T&gt;&gt;, but... it doesn't say async.

public static async Task&lt;PaginatedList&lt;T&gt;&gt; ToPaginatedListAsync&lt;T&gt;(
  this IQueryable&lt;T&gt; source, int pageSize, int pageIndex = 1)


With the async keyword specified, you can now await your result - not sure what AsAsyncEnumerable does, but it doesn't seem necessary then:

return await Task.Run(() =&gt;
{
    var count = source.Count();
    return
        source.Skip((pageIndex - 1) * pageSize).Take(pageSize)
        .Aggregate(new PaginatedList&lt;T&gt;(
            totalCount: count,
            pageSize: pageSize,
            pageIndex: pageIndex), (list, x) =&gt; { list.Add(x); return list; });
});




On the other hand, you're not really aggregating with Aggregate; LINQ stands for Language-INtegrated Query, and a query that has side effects isn't really a query.


source.Skip((pageIndex - 1) * pageSize).Take(pageSize)



That part is fine. Or is it? Why do you need to -1? Is pageIndex 1-based? That's pretty confusing, given the list itself is 0-based. What's the meaning of "page 0" then? Why make it throw when assigned to 0, in the setter, when the only caller for that setter is in the constructor? Better fail early and put a guard clause in the constructor and throw that ArgumentOutOfRangeException with a shorter stack trace.

I don't know why, the more I look at the Aggregate call, the more I want the method to be returning an IEnumerable&lt;T&gt;.

The caller already knows the totalCount (well it can have it), the pageIndex and the pageSize, and you're only ever returning a single page - I don't see why a PaginatedList&lt;T&gt; needs to know this metadata at that point.

If you can return an IEnumerable&lt;T&gt;, then you could yield the results and have this:

public static IEnumerable&lt;T&gt; OfPage&lt;T&gt;(this IQueryable&lt;T&gt; source, int pageIndex, int pageSize = 10)
{
    foreach (var item in source.Skip((pageIndex - 1) * pageSize).Take(pageSize))
    {
        yield return item;
    }
}


Notice I made the pageIndex mandatory, and pageSize optional - anyone using this API would expect to be able to pass a page index as a first argument to a method called OfPage.

Now, yield is pretty much the opposite of async, and it's not really buying anything here, since Take should already materialize the query, so you could have this async one-liner then:

public static async Task&lt;IEnumerable&lt;T&gt;&gt; OfPageAsync&lt;T&gt;(this IQueryable&lt;T&gt; source, int pageIndex, int pageSize = 10)
{
    return await Task.Run(() =&gt; source.Skip((pageIndex - 1)*pageSize).Take(pageSize));
}


If PaginatedList&lt;T&gt; doesn't need to know the totalCount, pageIndex and pageSize, then it doesn't need to exist - it's not adding any functionality, merely carrying metadata that the caller already knows, and if it needs to be carried along with the items on that page, then PaginatedList&lt;T&gt; can exist, but then as a composition:

public class PageItems&lt;T&gt; // not a list
{
    private readonly IEnumerable&lt;T&gt; _items;
    private readonly int _page;
    private readonly int _pageSize;

    public PageItems(IEnumerable&lt;T&gt; items, int page, int pageSize)
    {
        _items = items;
        _page = page;
        _pageSize = pageSize;
    }

    public IEnumerable&lt;T&gt; Items { get { return _items; } }
    public int PageIndex { get { return _pageIndex; } }
    public int PageSize { get { return _pageSize; } }
}


Notice that's exposing IEnumerable&lt;T&gt; and not List&lt;T&gt; or even IList&lt;T&gt;: if you don't need to do anything other than iterate these items, you don't need a List, you need an IEnumerable. The code doesn't change much:

public static async Task&lt;PageItems&lt;T&gt;&gt; OfPageAsync&lt;T&gt;(this IQueryable&lt;T&gt; source, int pageIndex, int pageSize = 10)
{
    return await Task.Run(() =&gt; 
    {
        var items = source.Skip((pageIndex - 1)*pageSize).Take(pageSize)
        return new PageItems(items, pageIndex, pageSize);
    });
}