Pass multiple optional parameters to Controller Action - asp.net-mvc-4

I have a controller Action that returns a list of activities to a view. (eventID,Venue,Room,EventDescription,EventType,StartDateTime,EndDateTime). The users wanted to be able to filter by Venue so I added Venue as id to the action method
ActionResult ListEvents(id string)
{
... Get the relevant details and return the view with the model
}
Now they want to also be able to filter by any/all of Event Type, Start, End, whether Post-event data has been completed.
Am I better to add these as GET query parameters or to define a custom route that will accept all 5 arguments or is there another solution
I will also need to add sorting and pagination at some point in case this changes the suggestion.

Typically, these would be handled via a query string, but it doesn't matter how you do it really. Regardless, of how the parameters are sent, your action simply needs to accept them all. The only thing you have to be aware of is the standard C# method rule (since actions are just methods) that optional parameters must be the last ones on the method. If they're all optional, then even that isn't really a concern.
Basically, you just have something like:
public ActionResult ListEvents(string id = null, int? eventID = null, ...)
{
Then inside, you'd just do something like:
var events = db.Events;
if (eventID.HasValue)
{
events = events.Where(m => m.EventID == eventId);
}
// rinse and repeat for each filter

Related

Api Platform pagination custom page_parameter_name

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);
}

Want to use one tag with different values according to the scenario in cucumber

we have a cleanup hook in our automation framework which tagged from the cucumber feature file
E.g
#cc_task_clean_up_hook_enrol_A
Scenario: Person can enrol_A
When I select the context menu
Then I am able to enroll the patient into 'enrol_A'
the implementation of the hook (#cc_task_clean_up_hook) is
#After(value = "#toc_task_clean_up_hook_enrol_A", order = HookOrder.CLEAN_UP_APP_AFTER)
public void cleanUpTOC() {
this.patientContextPage.selectedContextMenuItem("Pathway");
this.pathWayPage.selectReferences("Enroll in Pathway");
this.pathWayPage.deactivateEnrollment("enrol_A", "Withdrawn");
}
So exactly the same way we need an another scenario like
Scenario: Person can enroll_B
When I select the context menu
Then I am able to enroll the patient into 'enrollB'
So we can implement another hook as follows, the difference is the parameter type "enrollB"
#After(value = "#toc_task_clean_up_hook_enrollB", order = HookOrder.CLEAN_UP_APP_AFTER)
public void cleanUpTOC() {
this.patientContextPage.selectedContextMenuItem("Pathway");
this.pathWayPage.selectReferences("Enroll in Pathway");
this.pathWayPage.deactivateEnrollment("enrol_B", "Withdrawn");
}
So is it possible to consolidate these two methods and write only one generic clean up hook, based on the passed parameter? Your help is much appreciated.
You can add the scenario object to the arguments passed to the after hook. The framework will inject the currently executing scenario to the method.
public void afterMethod(Scenario scenario){}
You can use the getSourceTagNames() method of the Scenario object which will return you the collection of tags for the current executing scenario. From this collection you can determine if you have the tag ending with 'enroll_A' or 'enroll_B'.
Or you can use the getName() method which returns the description of the current scenario. So you will get either 'Person can enroll_A' or 'P..... enroll_B'. Just need to parse again.
You can modify the Then step to pass the enroll type to step definition. Store this in a variable. Use this variable in your after hook. But this will require the after hook to be in the same class.
Also you will need to change the value parameter of After hook to - {"#toc_task_clean_up_hook_enrollA,#toc_task_clean_up_hook_enrollB"}.
One observation that these two seem to have the same steps, if so then have you considered ScenarioOutline instead.

Laravel query parameters or pretty parameters for api

i am willing to know which is the best practice to use parameters for developing an api
//url
http://localhost:8000/api/my_orders/1/10/1
//route
Route::get('/my_orders/{user_id}/{limit}/{page}', 'ApiControllers\OrderController#getOrdersByUser');
//method
public function getOrdersByUser($user_id, $limit, $page)
{
//logic using $user_id, $limit, $page
}
or
//url
http://localhost:8000/api/my_orders?user_id=1&&limit=5&&page=1
//route
Route::get('/my_orders', 'ApiControllers\OrderController#getOrdersByUser');
//method
public function getOrdersByUser(Request $request)
{
//logic using $request->user_id, $request->limit, $request->page
}
this api is for mobile applications and for front end Vue application
thank you
Laravel has pagination built in, it will check for page and perpage query string arguments using paginate() or something that uses Paginator related methods for fetching a subset of your data.
So when you have ?page=1&perpage=20 and you use paginators, they will pick these up automatically.
For REST API endpoints, try and make the urls as descriptive as possible and based on your models.
// get a list of orders
GET /api/orders
// get a list of orders belonging to user 1
GET /api/orders?user_id=1
// get a paginated list of 20 orders belonging to user 1
GET /api/orders?user_id=1&page=1&perpage=20
You are calling your endpoint my_orders, which basically says it will return orders owned by the authenticated user. So, in my opinion, it wouldn't make sense here to include a user_id argument.
// get a list of orders owned by the authenticated user
GET /api/my_orders
You can use my_orders, but more descriptive will be to use a url like:
// get a list of orders owned by user
GET /api/users/{user_id}/orders
Edit:
In your case, you probably want to create a UserOrderController
public function index($user_id)
{
// first fetch user, if fetch fails show error
$user = User::findOrFail($user_id);
// maybe add some code here to check if the authenticated user is allowed to view this user's orders
// return paginated orders
return $user->orders()->paginate();
}
And you would need to define the relations in the models:
App/User.php
public function orders()
{
// assuming orders table has a column user_id
return $this->hasMany(Order::class);
}
App/Order.php
// inverse relation
public function user()
{
return $this->belongsTo(User::class);
}
I use 1st way for only those parameters which are associated with DB, like user_id here, because it reduces 1 step for me to get the specific data from DB of that particular ID. for more dynamic url laravel will go to this route even when you supply the wrong values and you have to apply the regix to avoid that.
Secondly, URLs like 2/20/4 wouldnt make any sense and hard to understand. So the best way in my opinion is the second way.

Different action method depending on parameter value?

I have two urls like this,
/products/home?subcat=abc
/products/home?subcat=xyz
I would like to have two action methods in products controller - homeabc, and homexyz.
How can I construct the route so it goes to the correct action method, depending on the value of the 'subcat' parameter?
PS: I don't have the freedom to change the url format, it has to stay the same.
Thanks.
Since you cannot change the URL formats and you only have one or two, the easiest way would be to do some type of switch in the Home/Index method
public ActionResult Index(string subcat){
switch(subcat.ToLower())[
case "abc":
return RedirectToAction("abc");
case "xyz":
return RedirectToAction("xyz");
default:
return RedirectToAction("UnknownCategory", "Errors");
}
}
Unfortunately, the routing engine does not allow you to route based on query string parameters (?). The only other possibility would be a url rewrite.

Overload of actions in the controller

Is it possible to do an overload of the actions in the controller? I haven't found any info about it and when I tried, I got this error:
The current request for action 'Create' on controller type 'InterviewController' is >ambiguous between the following action methods:
System.Web.Mvc.ViewResult Create() on type >MvcApplication4.MvcApplication4.InterviewController
System.Web.Mvc.ViewResult Create(Int32) on type >MvcApplication4.MvcApplication4.InterviewController
I've tried to do this on another way and I also get a new error that I can't fix. In fact, I created a new action (called create_client instead of create)
I need 2 ways of creating an "opportunite".
I just call the action, and I receive an empty formular in which I just have to insert data.
From a client's page, I must create an "opportunite" with the client that's already completed when the form is displayed to the user. (there is a need of productivity, the user must perform actions as fast as possible).
In the table "opportunite", I've got a column called "FK_opp_client", which is equal to the column "idClient" from the client's table.
I don't get how I can do the second way.
I've created a new action in the controller.
'
' GET: /Opportunite/Create_client
Function Create_client(idclient) As ViewResult
'Dim FK_Client = (From e In db.client
'Where(e.idClient = idclient)
' Select e.nomCompteClient).ToString()
'ViewBag.FK_client = New SelectList(db.client, "idClient", "nomCompteClient", idclient)
Dim opportunite As opportunite = db.opportunite.Single(Function(o) o.idOpportunite = 5)
opportunite.FK_Client = idclient
ViewBag.FK_Client = New SelectList(db.client, "idClient", "nomCompteClient", opportunite.FK_Client)
Return View(opportunite)
End Function
I've tried a few things to get what I wanted, the last one was to copy what was done in the "Edit" action, but for an empty rank. (so I created an empty rank in my DB). I don't think it was a good idea (imagine someone wants to update the DB where idOpportunite = 5...)
Any better ideas?
If you want to keep those two methods under the same name, you will have to implement an ActionSelectionAttribute to decorate them, or use them with different verbs (for example POST and PUT). Please read more details on action method selection process here (old but still true).
Different approach might be making your parameter optional and make action to check if it has been passed or not (through nullable type).