Service layer pagination, paged results (where to put logic?)

asp.net-core asp.net-core-webapi c# entity-framework-core n-tier-architecture

Question

I want to implement paging in my Web API, but from what I saw most paged results contain URLs or links to self, next, previous, last, first.

I'm not sure where to put paging logic, as the service layer can't generate URLs. And I don't want to couple my service layer with ASP NET Core.

How can I accomplish this? I want simple CRUD with paging.

Should controller generate my "Paging" model with URLs etc.? And service would only return IQueryable?

Github API, for example, returns pages in "Link" header: https://api.github.com/search/code?q=addClass+user:mozilla&per_page=2

1
1
4/18/2018 10:08:28 AM

Accepted Answer

The url generation should be included the closer you can to the Mvc/WebApi stuff (inside controllers, filters or whatever mechanism you want), you should not put your url generation inside the service layer unless you have bussiness rules that defined that url generation.

Trying to generate urls from a service layer will force to include HttpContext and Mvc references wich is preferable to elude.

Services should know about business data and not about ui layer components.

Think about it as trying to reuse the same service for a table or a view, then you dont need that url generation stuff. You should return the data, offset,limit, order by and total count (if required) because its needed to query that data, but not the url info.

I usually use something like this with entity framework and crud operations inside services or application layers, it encapsulates pagination simplifying Count and Skip, Take actions

/// <summary>
/// Paged queryable
/// </summary>
/// <typeparam name="T">T</typeparam>
public sealed class PagedQueryable<T> : IPagedEnumerable<T> ,IEnumerable<T>
{
    IQueryable<T> _source = null;
    int? _totalCount = null;

    /// <summary>
    /// Ctor
    /// </summary>
    /// <param name="source">source</param>
    /// <param name="offset">start element</param>
    /// <param name="limit">max number of items to retrieve</param>
    public PagedQueryable(IQueryable<T> source, int offset, int? limit)
    {
        if (source == null)
            throw new ArgumentNullException(nameof(source));

        _source = source;
        Limit = limit;
        Offset = Math.Max(offset, 0);
    }

    public int TotalCount
    {
        get
        {
            if (!_totalCount.HasValue && _source != null)
                _totalCount = _source.Count();

            return _totalCount.GetValueOrDefault();
        }
    }

    public int? Limit { get; }
    public int Offset { get; }

    public IEnumerator<T> GetEnumerator()
    {
        if (_source is IOrderedQueryable<T>)
        {
            var query = _source.Skip(Offset);
            if (Limit.GetValueOrDefault() > 0)
                query = query.Take(Limit.GetValueOrDefault());

            return query.ToList().GetEnumerator();
        }
        else
            return Enumerable.Empty<T>().GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}
3
4/18/2018 11:37:53 AM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow