I was trying to copy the configuration of the backend pagination, used on another project.
I created a new .net 6 web api, imported the JHipsterNet.Core and JHipsterNet.Web 1.0.9, created an endpoint with a IPageable as parameter and a custom object with the annotation [FromQuery], but it seems to not work.
System.ArgumentNullException: Page Number must not be less than zero! (Parameter 'pageNumber')
at JHipsterNet.Core.Pagination.Pageable..ctor(Int32 pageNumber, Int32 pageSize, Sort sort) in ...path...JHipsterNet.Core\Pagination\Pageable.cs:line 8
at JHipsterNet.Core.Pagination.Pageable.get_Previous() in ...path...\JHipsterNet.Core\Pagination\Pageable.cs:line 20
at Microsoft.Extensions.Internal.PropertyHelper.CallNullSafePropertyGetter[TDeclaringType,TValue](Func`2 getter, Object target)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.DefaultComplexObjectValidationStrategy.Enumerator.<>c__DisplayClass13_1.<MoveNext>b__1()
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry.get_Model()
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitChildren(IValidationStrategy strategy)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitComplexType(IValidationStrategy defaultStrategy)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.VisitImplementation(ModelMetadata& metadata, String& key, Object model)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Visit(ModelMetadata metadata, String key, Object model)
at Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor.Validate(ModelMetadata metadata, String key, Object model, Boolean alwaysValidateAtTopLevel, Object container)
at Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator.Validate(ActionContext actionContext, ValidationStateDictionary validationState, String prefix, Object model, ModelMetadata metadata, Object container)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.EnforceBindRequiredAndValidate(ObjectModelValidator baseObjectValidator, ActionContext actionContext, ParameterDescriptor parameter, ModelMetadata metadata, ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult, Object container)
at Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder.BindModelAsync(ActionContext actionContext, IModelBinder modelBinder, IValueProvider valueProvider, ParameterDescriptor parameter, ModelMetadata metadata, Object value, Object container)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: */*
Connection: keep-alive
Host: localhost:50001
User-Agent: PostmanRuntime/7.20.1
Accept-Encoding: gzip, deflate
Cache-Control: no-cache
Postman-Token: f9748320-c0cb-46a3-8966-05f0e44c6ce7
I found out the problem was on the constructor of the Pageable, precisely on the "Previous" property.
private Pageable(int pageNumber, int pageSize, Sort sort = null)
{
if (pageNumber < 0) {
throw new ArgumentNullException(nameof(pageNumber), "Page Number must not be less than zero!");
}
if (pageSize < 1) {
throw new ArgumentNullException(nameof(pageSize), "Page Size must not be less than one!");
}
PageNumber = pageNumber;
PageSize = pageSize;
Sort = sort ?? Sort.Unsorted;
}
public IPageable Previous => new Pageable(PageNumber - 1, PageSize, Sort);
public bool IsPaged => true;
public int PageNumber { get; }
public int PageSize { get; }
public int Offset => PageNumber * PageSize;
public Sort Sort { get; }
public IPageable Next => new Pageable(PageNumber + 1, PageSize, Sort);
public IPageable PreviousOrFirst => HasPrevious ? Previous : First;
The only reference was on the "PreviousOrFirst".
The error was due to the Pageable with "page=0", trying to create the Pageable of the previous page, so "-1".
But i wanted to understand better the reasons of the exception.
So I debugged the JHipsterNet.Core and JHipsterNet.Web libraries and found out that the error was due to the "public" declaration of the variabile It seems that if it is public, at runtime the application is trying to resolve the "Previous" property even if it is not necessary (because the page is equal to 0).
In this way it is possibile to make the IPageable parameter optional and the custom object mandatory, without removing the null check project option (which is enable by default in the newest .net projects)
Change the declaration of "Previous" from public to private.