call two different rest methods with same URI - rest

I have two Rest URIs :
// URI n1 : GET /users/{userName}
public ResponseEntity<userDto> findUserByName(
#PathVariable( value = "userName", required = true)
String userName
);
// URI n2 : GET /users/{userID}
public ResponseEntity<userDto> findUserByID(
#PathVariable( value = "userID", required = true)
Long userID
);
When I call GET /users/SuperUser123 I want the first function to respond and when I call GET /users/1854 I want the second one respond. What really happens is that the first function is always called for both cases (as the param is always of type String).
So how can I achieve what I want while respecting REST API URI recommendations ?

It will give ambiguous mapping runtime exception as the url pattern is same for both the methods.
If your url has some pattern like starting for superuser or something then you can use regex patterns to make it work.
In below example first method method will get called if the path variable is a digit otherwise second method for alphabets.you can change regex pattern accordingly.
#RequestMapping("{id:[0-9]+}")
public String handleRequest(#PathVariable("id") String userId, Model model){
model.addAttribute("msg", "profile id: "+userId);
return "my-page";
}
#RequestMapping("{name:[a-zA-Z]+}")
public String handleRequest2 (#PathVariable("name") String deptName, Model model) {
model.addAttribute("msg", "dept name : " + deptName);
return "my-page";
}

Related

Grails rest-api app to handle multiple params

Using Grails 3.1.3, I created a rest-api so that I am able to capture GET requests that not only query for one parameter, but multiple if needed. I don't know how to code this correctly inside the UrlMappings file. Here are the details.
Domain class:
class ProdDetail {
Integer pid
String title
String category
Integer year
}
And some of these inside the BootStrap:
new ProdDetail(pid:'101', title:'No Highway', author:'Nevil Shute', category:'fiction', year:1948).save(failOnError:true)
new ProdDetail(pid:'214', title:'In the Country of Men', author:'Hisham Matar', category:'misery', year:2007).save(failOnError:true)
Controller:
protected List<ProdDetail> listAllResources(Map params) {
println params
try {
ProdDetail.where {
if (params.category && params.maxYear) {
category == params.category && year <= params.int('maxYear')
} else if (params.category) {
category == params.category
} else if (params.maxYear) {
year <= params.int('maxYear')
} else {
pid > 0
}
}.list()
} catch (Exception e) {
[]
}
}
UrlMappings:
static mappings = {
"/prodDetails"(resources:'prodDetail')
"/prodDetails/category/$category?"(controller:'prodDetail', action:'index')
"/prodDetails/yearUnder/$maxYear?"(controller:'prodDetail', action:'index')
// the line below is not right I think, what's the correct format?
"/prodDetails/combo/$category?&$maxYear?"(controller:'prodDetail', action:'index')
}
Now, where as these two curls would work:
curl localhost:8080/prodDetails/category/misery
curl localhost:8080/prodDetails/yearUnder/2007
This one fails to go into the desired clause in the controller to detect both params:
curl localhost:8080/prodDetails/combo/?category=misery&maxYear=2007
It just detects 'category' but not the 'maxYear' which it considers as 'null'.
How can I cater for such a curl please?
It kind of depends on what you want your URLs to look like, but assuming you want your requests to look like this:
http://localhost:8080/prodDetails/combo/misery?maxYear=2007&title=common
The UrlMappings should look like
static mappings = {
"/prodDetails/combo/$category"(controller:'prodDetail', action:'index')
}
Then the params object in the controller should have both whatever's in the place of $category, in this example misery, and the other parameters after the ? as well.
If you want the parameters to be in the path you can do this:
static mappings = {
"/prodDetails/combo/$category/$title/$maxYear"(controller:'prodDetail', action:'index')
}
And the request would then be:
http://localhost:8080/prodDetails/combo/misery/common/2007
One other option would be to use a command object. So if you had:
static mappings = {
"/prodDetails/combosearch"(controller:'prodDetail', action:'comboSearch')
}
And then created an object beside the controller called ComboSearchCommand.groovy that looked like:
import grails.validation.Validateable
class ComboSearchCommand implements Validetable {
String category
String title
int maxYear
static constraints = {
category blank: false, nullable: true
title blank: false, nullable: true
maxYear blank: false, nullable: true
}
}
(Which you can do validation on just like a domain object)
And then in your controller you have the method take the command object instead of params
protected List<ProdDetail> comboSearch(ComboSearchCommand command) {
println command.category
}
Then your URL would be
http://localhost:8080/prodDetails/combosearch?category=misery&maxYear=2007&title=common
And the parameters will bind to the command object.
I've used that quite a bit, you can share validations or have your command object inherit validations from domain objects, lots of flexibility.
https://grails.github.io/grails-doc/latest/guide/single.html#commandObjects
You don't need to specify the parameters in UrlMappings if those params are not part of the URL:
No need of this:
"/prodDetails/combo/$category&?$maxYear?"(controller:'prodDetail', action:'index')
Yes you need this to match the URL to a controller/action (but remove the ?)
"/prodDetails/yearUnder/$maxYear?"(controller:'prodDetail', action:'index')
Also, you don't need Map params in listAllResources(Map params)
"params" is an injected property of controllers, the println params will work OK with: listAllResources()
What I would do is to define:
listAllResources(String category, int maxYear, ...) where ... are all the params that action can receive, most would be optional, so you will receive a null value if not included in your request.
Remember: UrlMappings are to map URLs to controller/actions, and you have the same controller/action, so I would remove all the mappings and process the optional parameters in the action just checking which are null or not.
Edit (considering comments)
Q: the method is not overloaded to handle params like that
A: methods are dynamic, this is Grails / Groovy, not Java. It will call the action method even if all the params are null. I would recommend you to go through the Grails controller documentation in detail.
Q: found that the listAllResources method was never called
A: remove the protected keyword from the action, only subclasses would be able to invoke that method. Also, you can add an UrlMapping to avoid users to invoke that URL (match the URL and return 404 Not Available or something like that)
Q: I want to handle a GET request like this localhost:8080/prodDetails/combo?category=misery&year=2016&title=commonTitle, how exactly should the i) entry in UrlMappings, and ii) the listAllResources method look like?
A:
static mappings = {
// if "compo" comes in the action portion, map that to the listAllResources method
// as I said, if all parameters comes in the query string, no further actions are needed, if you need parameters to be part of the URL path, then you need to play with the $xxxx in the URL
"/prodDetails/combo"(controller:'prodDetail', action:'listAllResources')
}
def listAllResources()
{
println params
// logic here
render "OK"
}
Check:
https://grails.github.io/grails-doc/latest/ref/Controllers/params.html
https://grails.github.io/grails-doc/latest/ref/Controllers/render.html
How does grails pass arguments to controller methods?

RESTful webservices: query parameters

I am using JAX-RS with RESTEasy.
I want to know if we can have represent different resources with path differentiated only by order and number of query parameters?
e.g.
/customer/1234
/customer?id=1234
/customer?name=James
Can I create three different methods e.g.
#Path("customer/{id}")
public Response get(#PathParam("id") final Long id) {
..
}
#Path("customer?id={id}")
public Response get(#QueryParam("id") final Long id) {
..
}
#Path("customer?name={name}")
public Response get(#QueryParam("name") final String name) {
..
}
Will this work, can I invoke different methods by differentiating path like this?
Thanks
This is a valid #Path:
#Path("customer/{id}") // (1)
These are not:
#Path("customer?id={id}") // (2)
#Path("customer?name={name}") // (3)
They are the same because the boil down to
#Path("customer")
which you could use.
So you can have (1) and one of (2) and (3). But you can't have (2) and (3) at the same time.
#QueryParam parameters are not part of the #Path. You can access them as you do in the signature of the method but you can't base the routing of JAX-RS on them.
Edit:
You can write one method that accepts both id and name as #QueryParam. These query parameters are optional.
#Path("customer")
public Response get(#QueryParam("id") final String id,
#QueryParam("name") final String name) {
// Look up the Customers based on 'id' and/or 'name'
}

How to handle optional query parameters in Play framework

Lets say I have an already functioning Play 2.0 framework based application in Scala that serves a URL such as:
http://localhost:9000/birthdays
which responds with a listing of all known birthdays
I now want to enhance this by adding the ability to restrict results with optional "from" (date) and "to" request params such as
http://localhost:9000/birthdays?from=20120131&to=20120229
(dates here interpreted as yyyyMMdd)
My question is how to handle the request param binding and interpretation in Play 2.0 with Scala, especially given that both of these params should be optional.
Should these parameters be somehow expressed in the "routes" specification? Alternatively, should the responding Controller method pick apart the params from the request object somehow? Is there another way to do this?
Encode your optional parameters as Option[String] (or Option[java.util.Date], but you’ll have to implement your own QueryStringBindable[Date]):
def birthdays(from: Option[String], to: Option[String]) = Action {
// …
}
And declare the following route:
GET /birthday controllers.Application.birthday(from: Option[String], to: Option[String])
A maybe less clean way of doing this for java users is setting defaults:
GET /users controllers.Application.users(max:java.lang.Integer ?= 50, page:java.lang.Integer ?= 0)
And in the controller
public static Result users(Integer max, Integer page) {...}
One more problem, you'll have to repeat the defaults whenever you link to your page in the template
#routes.Application.users(max = 50, page = 0)
In Addition to Julien's answer. If you don't want to include it in the routes file.
You can get this attribute in the controller method using RequestHeader
String from = request().getQueryString("from");
String to = request().getQueryString("to");
This will give you the desired request parameters, plus keep your routes file clean.
Here's Julien's example rewritten in java, using F.Option: (works as of play 2.1)
import play.libs.F.Option;
public static Result birthdays(Option<String> from, Option<String> to) {
// …
}
Route:
GET /birthday controllers.Application.birthday(from: play.libs.F.Option[String], to: play.libs.F.Option[String])
You can also just pick arbitrary query parameters out as strings (you have to do the type conversion yourself):
public static Result birthdays(Option<String> from, Option<String> to) {
String blarg = request().getQueryString("blarg"); // null if not in URL
// …
}
For optional Query parameters, you can do it this way
In routes file, declare API
GET /birthdays controllers.Application.method(from: Long, to: Long)
You can also give some default value, in case API doesn't contain these query params it will automatically assign the default values to these params
GET /birthdays controllers.Application.method(from: Long ?= 0, to: Long ?= 10)
In method written inside controller Application these params will have value null if no default values assigned else default values.
My way of doing this involves using a custom QueryStringBindable. This way I express parameters in routes as:
GET /birthdays/ controllers.Birthdays.getBirthdays(period: util.Period)
The code for Period looks like this.
public class Period implements QueryStringBindable<Period> {
public static final String PATTERN = "dd.MM.yyyy";
public Date start;
public Date end;
#Override
public F.Option<Period> bind(String key, Map<String, String[]> data) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
try {
start = data.containsKey("startDate")?sdf.parse(data.get("startDate") [0]):null;
end = data.containsKey("endDate")?sdf.parse(data.get("endDate")[0]):null;
} catch (ParseException ignored) {
return F.Option.None();
}
return F.Option.Some(this);
}
#Override
public String unbind(String key) {
SimpleDateFormat sdf = new SimpleDateFormat(PATTERN);
return "startDate=" + sdf.format(start) + "&" + "endDate=" + sdf.format(end);
}
#Override
public String javascriptUnbind() {
return null;
}
public void applyDateFilter(ExpressionList el) {
if (this.start != null)
el.ge("eventDate", this.start);
if (this.end != null)
el.le("eventDate", new DateTime(this.end.getTime()).plusDays(1).toDate());
}
}
applyDateFilter is just a convienence method i use in my controllers if I want to apply date filtering to the query. Obviously you could use other date defaults here, or use some other default than null for start and end date in the bind method.

Can my MVC2 app specify route constraints on Query String parameters?

My MVC2 app uses a component that makes subsequent AJAX calls back to the same action, which causes all kinds of unnecessary data access and processing on the server. The component vendor suggests I re-route those subsequent requests to a different action. The subsequent requests differ in that they have a particular query string, and I want to know whether I can put constraints on the query string in my route table.
For example, the initial request comes in with a URL like http://localhost/document/display/1. This can be handled by the default route. I want to write a custom route to handle URLs like http://localhost/document/display/1?vendorParam1=blah1&script=blah.js and http://localhost/document/display/1?vendorParam2=blah2&script=blah.js by detecting "vendor" in the URL.
I tried the following, but it throws a System.ArgumentException: The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.:
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "OtherController", action = "OtherAction" },
new RouteValueDictionary { { "args", "vendor" } });
Can I write a route that takes the query string into account? If not, do you have any other ideas?
Update: Put simply, can I write routing constraints such that http://localhost/document/display/1 is routed to the DocumentController.Display action but http://localhost/document/display/1?vendorParam1=blah1&script=blah.js is routed to the VendorController.Display action? Eventually, I would like any URL whose query string contains "vendor" to be routed to the VendorController.Display action.
I understand the first URL can be handled by the default route, but what about the second? Is it possible to do this at all? After lots of trial and error on my part, it looks like the answer is "No".
QueryString parameters can be used in constraints, although it's not supported by default. Here you can find an article describing how to implement this in ASP.NET MVC 2.
As it is in Dutch, here's the implementation. Add an 'IRouteConstraint' class:
public class QueryStringConstraint : IRouteConstraint
{
private readonly Regex _regex;
public QueryStringConstraint(string regex)
{
_regex = new Regex(regex, RegexOptions.IgnoreCase);
}
public bool Match (HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// check whether the paramname is in the QS collection
if(httpContext.Request.QueryString.AllKeys.Contains(parameterName))
{
// validate on the given regex
return _regex.Match(httpContext.Request.QueryString[parameterName]).Success;
}
// or return false
return false;
}
}
Now you can use this in your routes:
routes.MapRoute("object-contact",
"{aanbod}",
/* ... */,
new { pagina = new QueryStringConstraint("some|constraint") });
You don't need a route for this. It is already handled by the default model binder. Query string parameters will be automatically bound to action arguments:
public ActionResult Foo(string id, string script, string vendorname)
{
// the id parameter will be bound from the default route token
// script and vendorname parameters will be bound from the request string
...
}
UPDATE:
If you don't know the name of the query string parameters that will be passed you could loop through them:
foreach (string key in Request.QueryString.Keys)
{
string value = Request.QueryString[key];
}
This post is old, but couldn't you write a route before your default route
this would only catch routes with "vendor" in the args
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "VendorController", action = "OtherAction" },
new {args=#".*(vendor).*"}//believe this is correct regex to catch "vendor" anywhere in the args
);
And This would catch the rest
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "DisplayController", action = "OtherAction" }
);
Haven't tried this and I am a novice to MVC but I believe this should work?? From what I understand if the constraint doesn't match the route isn't used. So it would test the next route. Since your next route doesn't use any constraint on the args, it should, match the route.
I tried this out and it worked for me.

ASP.NET Mvc - nullable parameters and comma as separator

How should I define route in my global.asax to be able use nullable parameters and coma as separator?
I'm trying to implement routing rule for my search users page like
"{Controller}/{Action},{name},{page},{status}"
Full entry from the Global.asax:
routes.MapRoute(
"Search",
"{controller}/{action},{name},{page},{status}",
new { controller = "User", action = "Find",
name = UrlParameter.Optional,
page = UrlParameter.Optional,
status = UrlParameter.Optional }
);
Routine defined like above works fine when I'm entering all parameters, but when some parameters are equal to null routing fails (for example "user/find,,,")
According to Clicktricity comment bellow - the singature of action method that handes the request:
public ActionResult Find(string userName, int? page, int? status)
{
// [...] some actions to handle the request
}
On the beginning I was testing the route by VS debugger, now I'm using route debugger described on Phil's Haack blog. The tool confirm - that routing with null values is not properly handled (or I'm doing something wrong ;) )
As far as I know .Net routing doesn't let you do multiple nullable parameters like that. Multiple parameters will only work if they are missing working backwards from the end and with the separator also missing so you'd get matches on
user/find,bob,2,live
user/find,bob,2
user/find,bob
user/find
It'd be a lot easier to use querystrings for what you're trying to do.
Edit based on comment:
If this is necessary then you could try doing it this way (though it's not a nice approach)
Change your path to match
{Controller}/{Action},{*parameters}
Make sure to put a constraint on the action and controller so this is limited to as few as possible.
Rename each action that would take your full list to something else, adding a standard prefix to each one would be the cleanest way, and add the [NonAction] attribute. Add a new method with the original name that takes a string, this string is a comma separated string of your variables. In this method split the string and return the original action passing in the values from the split.
So you go from:
public ActionResult Find(string name, int page, string status){
//Do stuff here
return View(result);
}
To
public ActionResult Find(string parameters){
string name;
int? page;
string status;
//split parameters and parse into variables
return FindAction(name, page, status);
}
[NonAction]
public ActionResult FindAction(string parameters){
//Do what you did in your previous Find action
return View(results);
}