Skip to content

Conversation

@1nf0rmagician
Copy link
Member

@1nf0rmagician 1nf0rmagician commented Nov 17, 2025

Summary

Adding pagination to the actions in the ResourceModificationController that return sets of data. This PR may be used as an example to change all APIs in the MORYX Framework to make use of pagination in the endpoints.
The following changes are part of this PR:

  • Adding a common model for pagination requests and respones within the Framework
  • Adding a second controller action for the resource query with identical method and path
  • Adding ActionSelectorMethodAttribute implementations to differentiate between API calls using the new pagination responses and legacy calls
  • Adding an example to configure Swagger to cope with the duplicated action method
  • Updating the generated API client in the Web project

Open TODOs:

  • Add ObsoleteAttributes
  • Add propper usage of pagination in the web project
  • Add pagination to other methods in the controller that return sets of items
  • Move pagination request and result objects to a common namespace for all controllers

Linked Issues

Partially resolves #703

Checklist for Submitter

  • I have tested these changes locally
  • I have updated documentation as needed
  • I have added or updated tests as appropriate
  • I have used clear and descriptive commit messages

Review

Typical tasks

  • Merge request is well described
  • Critical sections are documented in code
  • Tests are extended
  • Documentation is created / updated
  • Running in test environment
  • Ports to other maintained versions are created

Clean code

  • All unused references are removed
  • Clean code rules are respected with passion (naming, ...)
  • Avoid copy and pasted code snippets

Regenerating the API based on the open api spec lead to dropping the namespaces from the model names. This appears to be a new standard behavior and has nothing to do with the actual changes in this PR.
The proper integration of the new pagination is still missing.
/// <summary>
/// The maximum page size provided for the request
/// </summary>
public int PageSize { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be Count or ItemsCount (looking at TotalCount). I'd propose something like

  • Page(Number)
  • Page(s)Count
  • (Items)Count *of this set
  • TotalCount

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like PageSize as it is very telling in the context of pagination, while I think that

  • Page(Number) -> to close to PageNumber which already exists
  • Page(s)Count -> without the apostrophe in page's count think it is misleading
  • (Items)Count *of this set -> is to long
  • TotalCount -> already exists for the total number of items

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry, I messed this up... This is what my idea was::

PagedResult {
    long Page(Number); // Could be Page but I don't have a strong opinion on changing this
    long Page(s)Count; // i.e. Pages/PageCount/PagesCount
    long (Items)Count; // of this set (instead of PageSize)
    long TotalCount; // Keeping it
    [] Items; 
}

That way, everything countable could be *Count. The 'pagination' could read sth like Page x of n Pages

/// with corresponding metadata from the request.
/// </summary>
/// <typeparam name="ItemType">Type of the items to be returned</typeparam>
public sealed class PagedResult<ItemType>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be possible to extend a paged result with other meta data? For example errors instead of items?

Copy link
Member Author

@1nf0rmagician 1nf0rmagician Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For errors I would prefer to commit to using the RFC7807 standard conform ProblemDetails from now on, I found a nice blog post explaining it and I would definitely prefer it to any MORYX custom error API responses 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that! Regarding my comment: Erros might have been a bad example. I just want to make sure that there is absolutely no reason to derive from the PagedResult.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsealing the class is a lot easier than the other way around, thats why i sealed it for now 😅

/// <summary>
/// The number of items provided on the page; defaults to 20 with a minimum of 0 and a maximum of 100
/// </summary>
public int PageSize { get; set => field = Math.Min(Math.Max(value, 0), 100); } = 20;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about numbers. I think common values are 25, 50, 100, 200 (or 250?). While the max value depends on the use case, I'd tend to go for 200 and would also use max as a default.
Min should be 1.

Copy link
Member Author

@1nf0rmagician 1nf0rmagician Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, should be 1 and 200 (or something reasonable that also works in the tree structures we have) 👍

await this.resourceModification
.getResources({
PageNumber: 1,
PageSize: 10000,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this 10k, while the endpoints max is only 100?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot, wanted to circumvent implementing a propper pagination. Also that might be hard in this tree structure... Maybe we need a bigger max than 200?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having a bigger max would mitigate the feature itself. It should prevent users to request tons of data (not only to have a responsive UI, but also reduce server load).
You could ask for multiple pages (which is not necessarily better, but should be load 'balanceble').

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should prevent users to request tons of data (not only to have a responsive UI, but also reduce server load).

I think this is only an argument if big requests would cause more load on the backend side, but since for all our modules we hold the working set of items in the memory anyway this is not really the case for us, is it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed we agreed on 200 as the maximum value.

}

[HttpPost("query")]
[RequiresQueryParameter(nameof(PaginationParameters.PageNumber))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to require a page number? Isn't 1 is a legit default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, could go for page size. We just need a parameter here for the minor conform change. I would drop the duplicate action (and the corresponding attributes) in the next major then 🙂

}

[HttpPost("query")]
[RequiresQueryParameter(nameof(PaginationParameters.PageNumber))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some Ideas:

  • Could we introduce attributes, that define things like for example maximum page size to bring it to the api spec/doc?
  • Should we already think about sorting?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good ideas:

  • Yes at least swagger respects the usual DataAnnotation attributes like Range, so we should make use of them
  • Somewhat, the parameter class makes it extendable to also include sorting parameters, so I would make it two separate PRs

return values;
}

[HttpPost("query")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we put the query parameters to the query instead of the request body and make these GET requests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not completely sure, but as far as I understand no, because the body is a deep object 🤔

public ActionResult<PagedResult<ResourceModel>> GetResources([FromBody] ResourceQuery query, [FromQuery] PaginationParameters parameters)
{
var resourceModels = QueryResourceModels(query);
return new PagedResult<ResourceModel>().With(parameters).Of(resourceModels);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parameters should be updated depending on the actual data, instead of just passing them back.

Ok, I see this MR is right now rather defining the API and doesn't have any further logic yet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly, up till now I just wanted to prove that we can make it Minor conform. Nevertheless, I agree the metadata should be updated according to the actual content 🙂

[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status417ExpectationFailed)]
[Authorize(Policy = ResourcePermissions.CanViewDetails)]
public ActionResult<ResourceModel[]> GetDetailsBatch([FromQuery] long[] ids)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After browsing the controller, I feel like there should be a root level GET (in a future version), that combines

  • filtering
  • sorting
  • pagination

@1nf0rmagician 1nf0rmagician added good first issue Good for newcomers help wanted Extra attention is needed labels Nov 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants