Reputation: 440
I'm trying to implement a simple RestfulController for my application. Given the following domain class:
class Test {
String name
int someInteger
static constraints = {
}
}
and its controller:
class TestController extends RestfulController<Test>{
TestController() {
super(Test)
}
}
Inside conf/UrlMappings.groovy I added the following entries:
"/api/$controller?(.${format})?" {
action = [POST: "save", PUT: "save", GET: "index", DELETE:"error"]
}
"/api/$controller/$id?(.${format})?" {
action = [POST: "update", PUT: "update", GET: "show", DELETE: "delete"]
}
Get requests are working fine, but Post and Put requests to a URL like http://localhost:8080/app/api/test.json
when the Content-Type: application/x-www-form-urlencoded
Header is present fail to respond with a JSON as expected. Instead render the show action view after persisting the entrie sent.
I also tried to use the Header Accept: application/json
with no effect.
How can I fix that?
Edit:
Further investigating RestfulController
's source file and the docs section regarding Content Negotiation I was able fix it by overriding the save and update methods replacing the line:
request.withFormat {
with:
withFormat {
Is it intentional or is there a flaw on RestfulController
's implementation?
Why does it consider the Content-Type header instead of the Accept header to render response?
Upvotes: 1
Views: 1288
Reputation: 440
Sorry for taking so long to respond. I had some trouble putting everything to work. Thanks a lot @Dónal for all the help. Ended using the following class to do the trick:
import org.codehaus.groovy.grails.web.servlet.HttpHeaders;
import org.springframework.http.HttpStatus;
import grails.artefact.Artefact;
import grails.rest.RestfulController;
import grails.transaction.Transactional;
@Artefact("Controller")
@Transactional(readOnly = true)
class MyRestfulController<T> extends RestfulController<T> {
public MyRestfulController(Class<T> resource, boolean readOnly = false) {
super(resource, readOnly);
}
@Override
@Transactional
def save() {
if(handleReadOnly()) {
return
}
T instance = createResource(getParametersToBind())
instance.validate()
if (instance.hasErrors()) {
respond instance.errors, view:'create' // STATUS CODE 422
return
}
instance.save flush:true
def formatHolder = params.format ? this : request
formatHolder.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: "${resourceName}.label".toString(), default: resourceClassName), instance.id])
redirect instance
}
'*' {
response.addHeader(HttpHeaders.LOCATION,
g.createLink(
resource: this.controllerName, action: 'show',id: instance.id, absolute: true,
namespace: hasProperty('namespace') ? this.namespace : null ))
respond instance, [status: HttpStatus.CREATED]
}
}
}
@Override
@Transactional
def update() {
if(handleReadOnly()) {
return
}
T instance = queryForResource(params.id)
if (instance == null) {
notFound()
return
}
instance.properties = getParametersToBind()
if (instance.hasErrors()) {
respond instance.errors, view:'edit' // STATUS CODE 422
return
}
instance.save flush:true
def formatHolder = params.format ? this : request
formatHolder.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: "${resourceClassName}.label".toString(), default: resourceClassName), instance.id])
redirect instance
}
'*'{
response.addHeader(HttpHeaders.LOCATION,
g.createLink(
resource: this.controllerName, action: 'show',id: instance.id, absolute: true,
namespace: hasProperty('namespace') ? this.namespace : null ))
respond instance, [status: HttpStatus.OK]
}
}
}
}
By using def formatHolder = params.format ? this : request
and then call formatHolder.withFormat
I am now able to override the response format independently from the request format.
It doesn't work for the Accept Header yet but at least it works.
Upvotes: 0
Reputation: 187499
If it's acceptable for all your controller's methods to always respond with JSON (when there a response body), you can achieve this with responseFormats like so:
class TestController extends RestfulController<Test>{
static responseFormats = ['json']
TestController() {
super(Test)
}
def customJsonAction() {
respond Something.get(params.id)
}
def someActionThatRendersGsp() {
render view: 'myGsp', model: [foo: 'bar']
}
}
This means the controller will always respond with JSON regardless of which headers, params, etc. are sent by the client.
Upvotes: 2