Api Platform pagination custom page_parameter_name - react-admin

I have very specific question on which I cannot find any answer and/or solution provided for Api Platform.
By default, the documentation states, that if you want to pass a page parameter for paging action, you must do the following:
pagination:
page_parameter_name: _page
However, due to the nature of our frontend we're not able to pass this variable to the request. It is hardcoded to the frontend request and is something like page[number]=1.
Is it possible to configure page_parameter_name to receive this variable or we need to transform it somehow in the Api itself?
Thank you!

ApiPlatform\Core\EventListener\ReadListener::onKernelRequest gets $context['filters'] from the request through ApiPlatform\Core\Util\RequestParser::parseRequestParams which ultimately uses PHP's parse_str function so the value of 'page[number]' will be in $context$context['filters']['page']['number'].
ApiPlatform\Core\DataProvider\Pagination::getPage retrieves the page number from $context['filters'][$parameterName] so whatever the value of [$parameterName] it will at best retrieve the array ['number'=> 1].
Then ::getPage casts that to int, which happens to be 1. But will (at least with PHP7) be 1 for any value under 'number'.
Conclusion: You need to transform it somehow in the Api itself. For example by decoration of the ApiPlatform\Core\DataProvider\Pagination service (api_platform.pagination).

API_URL?page[number]=2
print_r($request->attributes->get('_api_pagination'));
Array(
[number] => 2
)
The value of the "page_parameter_name" parameter should be "number" .
api_platform.yaml
collection:
pagination:
page_parameter_name: number
This may not work in version 3
vendor/api-platform/core/src/JsonApi/EventListener/TransformPaginationParametersListener.php
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$pageParameter = $request->query->all()['page'] ?? null;
...
/* #TODO remove the `_api_pagination` attribute in 3.0 */
$request->attributes->set('_api_pagination', $pageParameter);
}

Related

Loopback 4: Authorization decoration of CRUDRestController - is it in anyway possible?

Wondering if anybody in the community has any experience or guidance on how one could use
Authorization decorators (or any custom decoration?)(https://loopback.io/doc/en/lb4/Decorators_authorize.html) on CrudRestController endpoints? (https://loopback.io/doc/en/lb4/Creating-crud-rest-apis.html).
Looked at the src for crud-rest.controller.ts and it just seems like there is no way to really do it.
It seems like it's not easily possible to use any decoration of endpoints in a CrudRestController without taking a very hacky approach and/or wholesale duplicating the code in crud-rest.controller.ts and that we'll have to basically write every endpoint for every model by hand.
Maybe someone has come up with something or has some guidance on an approach? Is the only way to use auth with CrudRestController with the AuthorizationComponent as of now to use Authorizer functions (https://loopback.io/doc/en/lb4/Authorization-component-authorizer.html)
Seems like one part lies in this :
https://github.com/loopbackio/loopback4-example-shopping/blob/9188104c01516a5cbd4ce13f28abe18bafef821e/packages/shopping/src/services/basic.authorizor.ts
/**
* Allow access only to model owners, using route as source of truth
*
* eg. #post('/users/{userId}/orders', ...) returns `userId` as args[0]
*/
if (currentUser[securityId] === authorizationCtx.invocationContext.args[0]) {
return AuthorizationDecision.ALLOW;
}
So I ended up doing :
async authorize(
context: AuthorizationContext,
metadata: AuthorizationMetadata,
) {
const parent = context.invocationContext?.parent
const request = parent?.getBinding("rest.http.request").getValue(parent)
const givenUserId = request?.body?.userId
// next line finds out the user id in the JWT payload
const jwtUserId = context?.principals[0]?.payload?.sub
if (!jwtUserId || (givenUserId && givenUserId != jwtUserId)) {
return AuthorizationDecision.DENY;
} else {
return AuthorizationDecision.ALLOW;
}
}
as my userId is provided in the http parameters (post form or get parameters)
I also use a custom JTWService to read the payload and make it available in the UserProfile.
This may not be the best way to do it, but so far it works. I am still working on finding out how to deal with read requests and add a filter on all of them by userId too using decorators I will post my finding here, if nothing better show up first here.

How to access values from list of maps in Apache camel message body

Perhaps this is easy, but I am somehow not able to crack it yet. Message body for an exchange is basically a list of maps with both key & value being string. As example,
[{'key'='val1'}, {'key'='val2'},...]
I am using simple expression to set this as a property which I would be using in subsequent routes. This is how I am setting it:
.setProperty("myProperty", simple("${body}"))
But this sets the complete body. I just want to (somehow) set only the values part to avoid setting the entire list of maps. What I have tried and not working so far:
.setProperty("myProperty", simple("${body}['key']"))
.setProperty("myProperty", simple("${body}[*]['key']"))
.setProperty("myProperty", simple("${body}[0]['key']")) // this returns only the first value, I want all
Any idea/suggestion how can I achieve this ?
You can access every level of your body with Simple expressions:
${body} // get whole list of maps
${body[0]} // get first map in the list (index 0)
${body[0][key]} // get value of key "key" from the first map in the list
What you cannot do in a Simple expression is a conversion of your data structure in another one.
However, you can simply plug a Java bean into your route
from("direct:start")
...
.bean(MyConversionBean.class)
...;
And do the conversion with Java
public class MyConversionBean {
public List<String> convertBody() {
// extract all values (or whatever) with Java;
return listOfValues;
}
}

How to bind optional querystring string parameter on POST

I have the following controller in ASP.NET WebApi 2:
[RoutePrefix("Validations")]
public partial class ValidationsController
{
[HttpPost, Route("Bsb")]
public IHttpActionResult ValidateBsb(string value)
{
var validator = new BankStateBranchValidator(DbContext.BankStateBranches);
var data = new ValidationsResult
{
IsValid = validator.IsValid(value ?? string.Empty)
};
data.Error = data.IsValid
? null
: "The BSB you have entered does not appear to be valid. Please check the value and try again.";
return Ok(data);
}
}
For historical reasons, the value parameter needs to be in the querystring, rather than the form body, which should be empty. So the expected API call would be POST /Validate/Bsb?value=012345.
That all works fine, and I get the expected result; however, sometimes we are getting clients calling the API with POST /Validate/Bsb or POST /Validate/Bsb?value=, and that is resulting in a 400 Bad Request response from WebAPI itself, because, as far as I can tell, the model binder is failing to bind the missing value to the parameter. If I put a breakpoint inside the method, it never gets hit.
So, given that I can't change the API contract, how can I handle this scenario? I've tried adding a [ValueProvider(typeof(RouteDataValueProviderFactory))] attribute to the parameter, and my test case for the missing value works, but then the valid value test cases break since the value isn't in the route but in the querystring.
Update
Based on Craig H's suggestion, I've added a default value to the value parameter. So the various scenarios are:
POST /Validate/Bsb?value=012345 - pass (valid value)
POST /Validate/Bsb?value=000000 - pass (invalid value)
POST /Validate/Bsb?value= - fail (empty value)
POST /Validate/Bsb - pass (missing value)
You should be able to make the parameter optional by specifying a default value in the method signature.
e.g.
[HttpPost, Route("Bsb")]
public IHttpActionResult ValidateBsb(string value = null)
Your question says that a query with ?value= was throwing a bad request.
When I tried this locally my breakpoint was hit and value was null.
If I omitted the QS parameter completely, then I received a method not allowed response.
This page makes mention of optional route parameters with attribute routing, although you are not specifying the parameter like that here.
I cannot find the document which describes the other options with regards to routing and optional parameters. I have seen one which indicates the differences between defining it as optional in the route definition, and optional in the method signature. If I find it, I will update this answer!

Howto select a ASP.NET Core action from a JSON property in the body?

I have a REST interface endpoint like
POST /items/12345/actions
I utilize a generic actions sub collection to be apply to apply changes to 12345 which are not easily mapped to the content or direct other sub collections of it.
My question is the following: Since there could be multiple different action types I identify the action by a JSON property of the content of an uploaded document.
How do I select a action by a part of the JSON body of the request. Is there something possible like...
[Route("api/v1/items")
public class ItemsController : Controller
{
[HttpPost("{id}/actions")]
[CheckJsonBody("type", "ActionA")]
public ActionResult DoActionA(int id, ActionA a)
{
// do something
}
[HttpPost("{id}/actions")]
[CheckJsonBody("type", "ActionB")]
public ActionResult DoActionB(int id, ActionB b)
{
// do something
}
}
The request would look like ...
{
"type": "ActionA",
"abc": "xyz"
}
I have digged myself up into the code till Microsoft.AspNetCore.Mvc.ActionConstraints.ActionMethodSelectorAttribute (GitHub).
However starting from there, I am a bit lost to reach a high-performance solution. Do I need to decode the body or is that something which is already done at that time the constraint is evaluated?
ps: And yes, I know I could handle them in one action and do a switch on the "type" property.
An ASP.NET team member was so friendly to direct me to an answer: In the ActionMethodSelectorAttribute you can read the body into a memory stream, read till the property for the selection filter. Then you seek the memory stream to zero and replace it in the request (for later model binding). You can cache the criteria value in HttpContext.Items to speed it up if you use the same property for multiple actions.

Web API 2 Routing based on Parameter's Value

Is it possible to setup Web Api 2 route based on a parameter's value in the query string.
I have the following requirement:
/api/controller?data=param.data1
should go to controller's action 1
/api/controller?data=param.data2
should go to controller's action 2
any other value of data must go to action 3.
I know there's an option to set a constraint with a regex, but the examples I've found are for generic scenarios and not as specific as mine.
This is what I've tried
config.Routes.MapHttpRoute(
name: "test",
routeTemplate: "api/Hub/{data2}",
defaults: new { action = "Test" },
constraints: new { data2 = #"^(param\.data2)$" }
);
Is there a way to do it? Maybe there's a better way?
Important to note, I cannot change the URI of the service. It must have ?data=[value]
This is a fallback for a legacy system :(
You can use Attribute Routing, new in Web API 2.
Let's say you have the following actions, where the data param is, let's say, a string:
public Stuff GetStuffForData1(string data) { ... }
public Stuff GetStuffForData2(string data) { ... }
public Stuff GetStuffForData(string data) { ... }
Since you mentioned regex, you can specify route constraints for each of the above actions using a regex like the one you mentioned in your question1, for example:
[Route("controller/{data:regex(#"^(param\.data1)$")]
public Stuff GetStuffForData1(string data) { ... }
[Route("controller/{data:regex(#"^(param\.data2)$")]
public Stuff GetStuffForData2(string data) { ... }
// No need for a route constraint for other data params.
public Stuff GetStuffForData(string data) { ... }
The general syntax is {parameterName:constraint(params)} (params is optional and is not used for all constraints). In the above example, the first route will only be selected if the data segment of the URI matches the data1 regex. Similarly, the second route will be selected if the data segment of the URI matches the data2 regex. Otherwise, the last route will be chosen.
In general, the total ordering is determined as follows:
Compare the RouteOrder property of the route attribute. Lower values are evaluated first. The default order value is zero.
Look at each URI segment in the route template. For each segment, order as follows:
Literal segments.
Route parameters with constraints.
Route parameters without constraints.
Wildcard parameter segments with constraints.
Wildcard parameter segments without constraints.
In the case of a tie, routes are ordered by a case-insensitive ordinal string comparison (OrdinalIgnoreCase) of the route template.
You can even create your own custom route constraints by implementing the IHttpRouteConstraint interface and registering it in the Register method of your WebApiConfig class, assuming you're hosting on IIS, or in the Configuration method of your Startup class if self-hosting using OWIN.
Note I haven't personally tried any of the above, but it should all work; at the very least it should give you some ideas. For more details, including very nice examples, you should start by taking a look at the following article (which I shamelessly used extensively in my answer):
http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2#constraints
1 I'm really not an expert on writing regexes, so unfortunately I can't advise you on the specific ones you'll need.