Grails URL mapping that includes id - url

Is it possible to add a URL mapping that includes controller/action/id? Example:
URL:
localhost:8080/myproject/book/scifi/dune
localhost:8080/myproject/book/scifi/walking+dead
new URL:
localhost:8080/myproject/dune
localhost:8080/myproject/walking_dead
I tried:
static mappings = {
"/books" (controller: "book", action: "scifi", id: "dune")
"/walking_dead" (controller: "book", action: "scifi", id: "walking+dead")
}
Doesn't seem to work. Basically our client want specific URLS, I was using ids, then changed it to names as the ids. But now the client wants unique URLs. THanks for any help or insight.

static mappings = {
"/$bookId" (controller: "book", action: "scifi")
}
and the value is available as params.bookId in the controller

Related

Grails: URL parameter in url mappings not interpreted as expected

I'm writing a REST API with grails and need to put a variable on the path of a method. The URL to call this method (which is delete) will look like:
http://localhost:8080/profiles/576/gameLibrary/591
I have the following in my URLMappings class:
"/gameLibrary/${inventoryId}/"(controller: "profile", action: "removeFromLibrary", method: "DELETE")
I tried variations for the mapping like "$inventory" without the curly brackets and leaving off the trailing slash. When I attempt to call this URL, I get a 404. I've created the URL report, and I see the following for the mapping in question:
Controller: profile
| DELETE | /profiles/${profileId}/gameLibrary/null | Action: removeFromLibrary |
So, it's like it's trying to interpolate "${inventoryId} instead of using it as a path variable.
Perhaps I'm misunderstanding how to create path variables. I thought this section of the documentation on embedded variables was describing the approach I took: http://docs.grails.org/latest/guide/theWebLayer.html#embeddedVariables.
Just in case it helps, posting my whole UrlMappings class:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/games"(resources:"game")
"/gameGenres"(resources:"gameGenre")
"/siteUsers"(resources:"siteUser")
"/profiles"(resources:"profile") {
"/gameLibrary" (controller: "profile", action: "getGameLibrary", method: "GET")
"/gameLibrary" (controller: "profile", action: "addGameToLibrary", method: "PUT")
"/gameLibrary/${inventoryId}/"(controller: "profile", action: "removeFromLibrary", method: "DELETE")
"/wishList" (controller: "profile", action: "getWishList", method: "GET")
}
"/inventoryItems"(resources:"inventoryItem")
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
Any help would be appreciated greatly.

Does Grails supports Restful nested URLs?

Does Grails supports Restful nested URLs like '/articles/1/comments/5' by default? If not is there any plugin for that?
See here. Nested RESTful URLs will be supported in Grails 2.3.
If you're bound to an older release of Grails (e.g < 2.3), and the available plugins don't work out, you can use named URL mappings to produce an effective restful mapping.
Here's an example from a project of mine - I've left out some details, but hopefully this gets you started if you decide to give this approach a try.
In your UrlMappings.groovy
/** 1. Mappings can handle multiple actions depending on HTTP
method like Rest. Names are a little clunky, like this would
be more appropriate as "resource" vs "showResource" but we didn't want
potential naming conflict in future release
2. TODO: DRY constraints - make constraints global
3. make sure controllers have proper actions defined
*/
/** RESTFUL mapping for single resource */
name listResources: "/$controller" {
action = [GET: "list", POST: "save"]
}
name createResource: "/$controller/create" {
action = [GET: "create" ]
}
name deleteResource: "/$controller/$id?/delete" {
action = [POST: "delete", DELETE: "delete"]
constraints { id(matches: /[0-9]+/) }
}
name editResource: "/$controller/$id?/edit" {
action = [GET: "edit", PUT: "update", POST: "update"]
constraints { id(matches: /[0-9]+/) }
}
name showResource: "/$controller/$id?" {
action = [GET: "show", PUT: "update", POST: "update", DELETE: "delete"]
constraints { id(matches: /[0-9]+/) }
}
/** RESTFUL mapping for CHILD with PARENT */
name listChildResources: "/$parentResource/$pid/$controller" {
action = [GET: "list", POST: "save"]
constraints { pid(matches: /[0-9]+/) }
}
name createChildResource: "/$parentResource/$pid/$controller/create" {
action = [GET: "create" ]
constraints { pid(matches: /[0-9]+/) }
}
name showChildResource: "/$parentResource/$pid/$controller/$id?" {
action = [GET: "show", PUT: "update", POST: "update", DELETE: "delete"]
constraints {
id(matches: /[0-9]+/)
pid(matches: /[0-9]+/)
}
}
name editChildResource: "/$parentResource/$pid/$controller/$id?/edit" {
action = [GET: "edit"]
constraints {
id(matches: /[0-9]+/)
pid(matches: /[0-9]+/)
}
}
Make sure you controllers have actions and supported HTTP methods define, eg
static allowedMethods = [
save: "POST",
update: ["POST", "PUT"],
delete: ["POST", "DELETE"]
]
Then use the mappings like so (for example lets say we have Gardens and Plants as resources).
//show a garden
<g:link mapping="showResource" controller="garden"
id="${gardenInstance.id}">${gardenInstance.name}</g:link>
//create a plant for garden
<g:link mapping="createChildResource" controller="plant"
params="[parentResource: 'garden', pid: gardenInstance.id]">Add Plant</g:link>
//show list of plants within a garden
<g:link mapping="listChildResources" controller="plant"
params="[parentResource: 'garden', pid: gardenInstance.id]">List plants for Garden</g:link>
Shown here it's pretty verbose, but you could put all of this into TagLib and have something like.
<g:restShow resource="garden"
id="${gardenInstance.id}">${gardenInstance.name}</g:restShow>
<g:restCreate" resource="plant"
parent="${gardenInstance}">Add Plant</g:restCreate>

How to exclude specific keywords from UrlMapping in Grails?

I use the following url mapping in Grails:
"/$id"{
controller = "user"
action = "show"
}
to map urls like mydomain.com/someusername
How do I attach constrains to the url mapping to exclude keywords like "login", "logout",...
I.e.,
mydomain.com/someusername should route to mydomain.com/user/show/someusername,
mydomain.com/login should not route to mydomain.com/user/show/login.
You can use contrainsts for this mapping:
"/$id"{
controller = "user"
action = "show"
constraints {
//add a validator for $id from url mapping
id(validator: {
return !(it in ['login', 'logout'])
})
}
}
Use a filter, and redirect from it.
class UrlMappingFilters {
def filters = {
filterUrlKeywords(controller: '*', action: '*') {
def keywords = ['login', 'logout']
before = {
if (params.id in keywords) {
// redirect to start, or render error...
redirect(uri: '/')
return false
}
}
}
}
}
If you want to make it specific to a controller or action, use its name instead of the '*'.
Read more:
Official docs on Grails filters
Reference
I have done this type of thing for REST based endpoints numerous times, and Grails is smart enough to figure out what you want. Note that ordering in the UrlMappings file may be important.
For example, you can define this:
class UrlMappings {
static mappings = {
"/login" { controller: 'auth', action: 'login' }
"/logout" { controller: 'auth', action: 'logout' }
"/$id" { controller: 'user', action: 'view' }
}
}
Then when you hit "/login" you will go in the auth:login method, but when you hit "/userid" you will be sent to user:view:userid as expected.

Web API - Multiple POST methods

I am writing a simple web api application. I came to a phase when I need to have two POST methods in my web api controller. One of these methods works and the other does not. My route table looks like this:
config.Routes.MapHttpRoute(
name: "ApiRouteWithAction",
routeTemplate: "api/{controller}/{action}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
Then I have my methods defined like this:
[HttpPost]
public bool PostTaskAgain(My3TasksWebAPI.Data.Task task)
{
var oldTask = _db.Task.Where(t => t.Id == task.Id).SingleOrDefault();
oldTask.DoAgain = true;
oldTask.DateUpdated = task.DateUpdated;
if (_db.SetOfTasks.Where(t => CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(t.DateCreated, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday) == CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(DateTime.Now, CalendarWeekRule.FirstFullWeek, DayOfWeek.Monday)).Any())
{
int currentSetOfTasksId = _db.SetOfTasks.OrderBy(s => s.DateCreated).FirstOrDefault().Id;
My3TasksWebAPI.Data.Task newTask = new Data.Task() { CreatedBy = oldTask.CreatedBy, DateCreated = oldTask.DateCreated, DateUpdated = null, DoAgain = false, Notes = string.Empty, SetOfTasksId = currentSetOfTasksId, Status = false, Title = oldTask.Title, UserId = oldTask.UserId };
_db.Task.Add(newTask);
}
_db.SaveChanges();
return true;
}
// Post api/values/PostSetOfTasks/{setOfTasks}
[HttpPost]
public bool PostSetOfTasks(My3TasksWebAPI.Data.SetOfTasks setOfTasks)
{
_db.SetOfTasks.Add(setOfTasks);
_db.SaveChanges();
return true;
}
When I try to call PostTaskAgain I get an internal server error. I think that it might be the routing table but I am not sure how to handle two post methods.
I call the web api from my asp.net mvc application like this:
HttpResponseMessage response = client.PostAsJsonAsync("api/values/PostSetOfTasks", model.SetOfTasks).Result;
and
HttpResponseMessage response = client.PostAsJsonAsync("api/values/PostTaskAgain", taskToPost).Result;
That means that I include the actions.
Working with POST in webapi can be tricky though conincidently, your issue turned out to be trivial. However, for those who may stumble upon this page:
I will focus specifically on POST as dealing with GET is trivial. I don't think many would be searching around for resolving an issue with GET with webapis. Anyways..
If your question is - In MVC Web Api, how to-
- Use custom action method names other than the generic HTTP verbs?
- Perform multiple posts?
- Post multiple simple types?
- Post complex types via jQuery?
Then the following solutions may help:
First, to use Custom Action Methods in Web API, add a web api route as:
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "ActionApi",
routeTemplate: "api/{controller}/{action}");
}
And they you may create action methods like:
[HttpPost]
public string TestMethod([FromBody]string value)
{
return "Hello from http post web api controller: " + value;
}
Now, fire the following jQuery from your browser console
$.ajax({
type: 'POST',
url: 'http://localhost:33649/api/TestApi/TestMethod',
data: {'':'hello'},
contentType: 'application/x-www-form-urlencoded',
dataType: 'json',
success: function(data){ console.log(data) }
});
Second, to perform multiple posts,
It is simple, create multiple action methods and decorate with the [HttpPost] attrib.
Use the [ActionName("MyAction")] to assign custom names, etc.
Will come to jQuery in the fourth point below
Third,
First of all, posting multiple SIMPLE types in a single action is not possible and there is a special format to post a single simple type (except for passing the parameter in the query string or REST style).
This was the point that had me banging my head with Rest Clients and hunting around the web for almost 5 hours and eventually, the following URL helped me. Will still quote the contents for the link may turn dead!
Content-Type: application/x-www-form-urlencoded
in the request header and add a = before the JSON statement:
={"Name":"Turbo Tina","Email":"na#Turbo.Tina"}
http://forums.asp.net/t/1883467.aspx?The+received+value+is+null+when+I+try+to+Post+to+my+Web+Api
Anyway, let us get over that story. Moving on:
Fourth, posting complex types via jQuery, ofcourse, $.ajax() is going to promptly come in the role:
Let us say the action method accepts a Person object which had an id and a name. So, from javascript:
var person = { PersonId:1, Name:"James" }
$.ajax({
type: 'POST',
url: 'http://mydomain/api/TestApi/TestMethod',
data: JSON.stringify(person),
contentType: 'application/json; charset=utf-8',
dataType: 'json',
success: function(data){ console.log(data) }
});
And the action will look like:
[HttpPost]
public string TestMethod(Person person)
{
return "Hello from http post web api controller: " + person.Name;
}
All of the above, worked for me!!
Cheers!
There was a problem with my LINQ query.
The response from the server was: {"$id":"1","Message":"An error has occurred.","ExceptionMessage":"LINQ to Entities does not recognize the method 'Int32 GetWeekOfYear(System.DateTime, System.Globalization.CalendarWeekRule, System.DayOfWeek)' method, and this method cannot be translated into a store expression.","ExceptionType":"System.NotSupportedException","StackTrace":" at System.Data.Objects.ELinq.ExpressionConverter.MethodCallTranslator.DefaultTransl‌​ator.Translate(ExpressionConverter parent, MethodCall....
After correcting the linq query everything is working fine. Visual studio was fine about me doing the linq query wrong.

path_prefix for asp.net mvc routes

I read this article about how you can prefix routes in ruby on rails. I want to be able to do the same thing with asp.net mvc
So I want to be able to define a route like :
/photographers/1/photos/2 //photo 2 of photographer with id 1
/photographers/1/photos //all of photographer with id 1
Any tips ?
EDIT:
"photographers/{id}/photos/{photoID}" - seems to do the job quite ok, BUT how can I support
RedirectToAction<PhotosController>(x => x.Add());
I would like to redirect to : /photographers/1/photos/add
Define your route like this:
routes.MapRoute(
"Photographers",
"photographers/{id}/photos/{photoID}",
new { controller = "Photographers", action = "Photo", photoID = null });
Then define your controller action like this:
public ActionResult Photo(int id, int? photoID)
{
// If photoID is not null, show just that photo.
// Otherwise, show all photographs.
}
You could use regex routing or use wildcards in your routing table so that the {id*} matches the /1/photos/2 for the photographers default controller, parse the string, and redirect to an appropriate action.
Also take a look at this post about nested resources.
RouteTable.Routes.Add(
new Route { Url = "events/[eventId]/tickets/[action]/[id]",
Defaults = new { controller = "Tickets",
action = "List", id = (string)null },
RouteHandler = typeof(MvcRouteHandler) });

Resources