Skip to content

Make @RequestMapping inject the negotiated MediaType [SPR-9980] #14614

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Nov 9, 2012 · 9 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Nov 9, 2012

Michael Osipov opened SPR-9980 and commented

I have a use caes where I do return a suitable object according for the negotiated content type.
In my case I accept JSON and XML. When JSON is returned to the client I produce a Map, when XML is returned I produce a custom object.

There is no way to determine easily which content type will be written out to the client. With examining the Accept header and iterating over registered message converters.

I have a work around which looks like this:

@RequestMapping(value = "/{project:[A-Z0-9_+\\.\\(\\)=\\-]+}", method = RequestMethod.GET, 
  produces = { MediaType.APPLICATION_XML_VALUE })
  public ResponseEntity<Object> lookupProjectAsXml(@PathVariable String project,
      @RequestParam(value = "fields", required = false) String fields) {

    String[] fieldsArray = StringUtils.split(fields, ',');

    return lookup(project, fieldsArray, false, MediaType.APPLICATION_XML);
  }

and


@RequestMapping(value = "/{project:[A-Z0-9_+\\.\\(\\)=\\-]+}", method = RequestMethod.GET, 
  produces = { MediaType.APPLICATION_JSON_VALUE })
  public ResponseEntity<Object> lookupProjectAsJson(@PathVariable String project,
      @RequestParam(value = "fields", required = false) String fields,
      @RequestParam(value = "asList", required = false, defaultValue = "false") boolean asList) {

    String[] fieldsArray = StringUtils.split(fields, ',');

    return lookup(project, fieldsArray, asList, MediaType.APPLICATION_JSON);
  }

I would rather have:

@RequestMapping(value = "/{project:[A-Z0-9_+\\.\\(\\)=\\-]+}", method = RequestMethod.GET, 
  produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
  public ResponseEntity<Object> lookupProject(@PathVariable String project,
      @RequestParam(value = "fields", required = false) String fields,
      @RequestParam(value = "asList", required = false, defaultValue = "false") boolean asList,
      MediaType mediaType) {

    // Process request...
    
    Object body;
    if (mediaType.equals(MediaType.APPLICATION_JSON)) {
      body = projectValues;
    } else if (mediaType.equals(MediaType.APPLICATION_XML)) {
      body = new Project(projectValues);
    } else {
      throw new NotImplementedException("Project lookup is not implemented for media type '" + mediaType + "'");
    }
  
    return new ResponseEntity<Object>(body, HttpStatus.OK);
  }

Affects: 3.1.3

Reference URL: http://stackoverflow.com/q/13272443/696632

Issue Links:

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Nov 23, 2012

Rossen Stoyanchev commented

The "negotiated MediaType" is not yet available at the time of controller method invocation. The actual content negotiation is in HttpEntityMethodProcessor, which handles the returned ResponseEntity. What we do know is the requested media types and in 3.2 that is done through a ContentNegotiationManager so it would be easy to create a HandlerMethodArgumentResolver that supports List<MediaType>.

That said it seems like it would be nicer, in your example, if we provided an option to post-process the return value before it is written to the response. This would help @ResponseBody and ResponseEntity methods since they can't use a HandlerInterceptor to do that. That way you maybe wouldn't have to have an if-else in every method that needs this. Instead you would return the same value and wrap it for XML. It would also help with #13864.

What do you think?

@spring-projects-issues
Copy link
Collaborator Author

Michael Osipov commented

Rossen,

to the first paragraph: Yes, that would be nice to have a list of MediaType which are supported by the client. Would not solve my issue but may be handy for other purposes. Anyway why is MediaType not an Enum?

to the second paragraph: This sounds good, one would move the if-else-clause to a single spot. But how would I know that, for instance the map from that exact method needs to be wrapped. Would this be per-controller just like a @ExceptionHandler?

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Anyway why is MediaType not an Enum?

It's not quite an enum since you have wildcard types and subtypes, vendor extensions, XML or JSON compatible times (i.e. "*+json"), not to mention parameters like quality or charset and others.

But how would I know that, for instance the map from that exact method needs to be wrapped. Would this be per-controller just like a @ExceptionHandler?

This is something that would be registered globally. You would know the return type is ResponseEntity and you would be able to check the requested media type or the "producible" media type in order to decide.

@spring-projects-issues
Copy link
Collaborator Author

Michael Osipov commented

bq. But how would I know that, for instance the map from that exact method needs to be wrapped. Would this be per-controller just like a @ExceptionHandler?
This is something that would be registered globally. You would know the return type is ResponseEntity and you would be able to check the requested media type or the "producible" media type in order to decide.

If you have to register this globally how is one supposed to know in this handler that for instance a simple (and very common) object like Map must be wrapped in X where X can be something completely different based on the controller which is returning the Map?

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

In addition to the return type you would also have access to a MethodParameter instance from which you can get the declaring class (i.e. the controller) as well as the method and any annotations it has. See the HandlerMethodReturnValueHandler. It shows what would be available roughly.

@spring-projects-issues
Copy link
Collaborator Author

Michael Osipov commented

I am missing a link here. Is there something already where I could plugin such a thing than wrap my map in a custom object for that specific controller?

@spring-projects-issues
Copy link
Collaborator Author

Arnaud Cogoluègnes commented

pull request for the list of requested media types as a method argument: #232

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Moving to general backlog now. See discussion under pull request for further details.

@spring-projects-issues
Copy link
Collaborator Author

Rossen Stoyanchev commented

Resolving as "Won't fix". See pull request for further comments.

@spring-projects-issues spring-projects-issues added status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement in: web Issues in web modules (web, webmvc, webflux, websocket) labels Jan 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: declined A suggestion or change that we don't feel we should currently apply type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

2 participants