I am trying to take this API and generalize Pagination, so it can be used for Any of my APIs and Models. How would I conduct this? I placed existing code base below, and want to have applicable to all apis and their getmethods. Researching to modify this function. If Microsoft has built into functionality to support this, I am open to Any other strategies.
From Resource (Existing Code only used can be used for 1 Class API):
https://dejanstojanovic.net/aspnet/2019/january/filtering-and-paging-in-aspnet-core-web-api/
Background:
We prepared many Get APIs, and now want pagination support for all 200+ apis. (I came into the project later) Without manually update/recoding everything, is there a automatic method to apply routing/pagination to all APIs for our end point customers using maybe resource link above or other ideas? Seeking way to filter all APIs.
public ActionResult<PagedCollectionResponse<Person>> Get([FromQuery] SampleFilterModel filter)
/api/Persons?Page=2&Limit=3
/api/Inventory?Page=2&Limit=3
/api/Product?Page=2&Limit=3
/api/Product/GetAll/Page=2&Limit=4
/api/Product/GetByProductCategory/7/Page=2&Limit=4
/api/Product/GetByVendorId/2/Page=2&Limit=4
/api/Product/GetByProductLocation/US/CA/Page=2&Limit=4
Code:
namespace Sample.Web.Api.Models
{
public abstract class FilterModelBase:ICloneable
{
public int Page { get; set; }
public int Limit { get; set; }
public FilterModelBase()
{
this.Page = 1;
this.Limit = 100;
}
public abstract object Clone();
}
}
public class SampleFilterModel:FilterModelBase
{
public string Term { get; set; }
public SampleFilterModel():base()
{
this.Limit = 3;
}
public override object Clone()
{
var jsonString = JsonConvert.SerializeObject(this);
return JsonConvert.DeserializeObject(jsonString,this.GetType());
}
}
namespace Sample.Web.Api.Models
{
public class PagedCollectionResponse<T> where T:class
{
public IEnumerable<T> Items { get; set; }
public Uri NextPage { get; set; }
public Uri PreviousPage { get; set; }
}
}
namespace Sample.Web.Api.Models
{
public class Person
{
public String Name { get; set; }
public DateTime DOB { get; set; }
public String Email { get; set; }
}
}
namespace Sample.Web.Api.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PersonsController : ControllerBase
{
IEnumerable<Person> persons = new List<Person>() {
new Person() { Name = "Nancy Davolio", DOB = DateTime.Parse("1948-12-08"), Email = "nancy.davolio@test.com" },
new Person() { Name = "Andrew Fuller", DOB = DateTime.Parse("1952-02-19"), Email = "andrew.fuller@test.com" },
new Person() { Name = "Janet Leverling", DOB = DateTime.Parse("1963-08-30"), Email = "janet.leverling@test.com" },
new Person() { Name = "Margaret Peacock", DOB = DateTime.Parse("1937-09-19"), Email = "margaret.peacock@test.com" },
new Person() { Name = "Steven Buchanan", DOB = DateTime.Parse("1955-03-04"), Email = "steven.buchanan@test.com" },
new Person() { Name = "Michael Suyama", DOB = DateTime.Parse("1963-07-02"), Email = "michael.suyama@test.com" },
new Person() { Name = "Robert King", DOB = DateTime.Parse("1960-05-29"), Email = "robert.king@test.com" },
new Person() { Name = "Laura Callahan", DOB = DateTime.Parse("1958-01-09"), Email = "laura.callahan@test.com" },
new Person() { Name = "Anne Dodsworth", DOB = DateTime.Parse("1966-01-27"), Email = "anne.dodsworth@test.com" }
};
// GET api/values
[HttpGet]
public ActionResult<PagedCollectionResponse<Person>> Get([FromQuery] SampleFilterModel filter)
{
//Filtering logic
Func<SampleFilterModel, IEnumerable<Person>> filterData = (filterModel) =>
{
return persons.Where(p => p.Name.StartsWith(filterModel.Term ?? String.Empty, StringComparison.InvariantCultureIgnoreCase))
.Skip((filterModel.Page-1) * filter.Limit)
.Take(filterModel.Limit);
};
//Get the data for the current page
var result = new PagedCollectionResponse<Person>();
result.Items = filterData(filter);
//Get next page URL string
SampleFilterModel nextFilter = filter.Clone() as SampleFilterModel;
nextFilter.Page += 1;
String nextUrl = filterData(nextFilter).Count() <= 0 ? null : this.Url.Action("Get", null, nextFilter, Request.Scheme);
//Get previous page URL string
SampleFilterModel previousFilter = filter.Clone() as SampleFilterModel;
previousFilter.Page -= 1;
String previousUrl = previousFilter.Page <= 0 ? null : this.Url.Action("Get", null, previousFilter, Request.Scheme);
result.NextPage = !String.IsNullOrWhiteSpace(nextUrl) ? new Uri(nextUrl) : null;
result.PreviousPage = !String.IsNullOrWhiteSpace(previousUrl) ? new Uri(previousUrl) : null;
return result;
}
}
}
Note:
Need to Extend it to the API level as per resource. We know it can be done in business repository method, but want to generalize for all Controller APIs like author from above .
In response to:
I would look into implementing OData v4 if that's an option. You can then standardize pagination across all your APIs using
$top
and$skip
query parameters.So roughly you would want to do something like this:
Install the Microsoft.AspNetCore.OData Nuget package on the project. More documentation about the features can be found in the OData WebApi changelog.
Then modify the code as shown below to get started with a basic implementation of OData.
Startup.cs
Models/Person.cs
Controllers/PersonsController.cs
Then in the browser you execute queries in accordance with the OData spec. For example: