WebAPI not returning XML even with correct Accepts header - asp.net

I'm using ASP.NET WebAPI RC, and hosting an API controller with nothing fancy about it. Everything works fine with JSON, but I'm testing requesting different formats using the Accepts header, and that's where I'm having trouble.
I'm using jQuery to issue an AJAX request, and setting the 'dataType' parameter of the request. This correctly sets the appropriate Accept header as you will see below.
$.ajax({
type: method,
url: url,
dataType: "xml",
data: data || null,
success: function (data) {
// omitted
}
});
Here is a save of the fiddler request/response. As you can see the Accept header says application/xml, but WebAPI returned JSON. I have also tried manually setting the Accept header to just "application/xml" (so it doesn't have the text/html stuff as well), but to no avail.
What the heck am I missing? (note: I've snipped some confidential info in the data but didn't tweak it otherwise)
GET http://localhost/insp**snip**6716 HTTP/1.1
Host: localhost
Connection: keep-alive
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.60 Safari/537.1
Accept: application/xml, text/xml, */*; q=0.01
Referer: http://localhost/inspector/api/test?
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: m=34e2:|2c69:t|47ba:t|4e99:t; .INSPECTOR3COOKIE=08BA683091E2A457B1832E9B*snip*F911D9ED97076
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Fri, 03 Aug 2012 22:27:42 GMT
Content-Length: 1816
{"Id":2416716,"ProjectId":36,"Url":"http://ins *snip, but obviously this is JSON not XML *
I'd like to point out I'm not tweaking any formatters in AppStart or anything, so as far as I understand, the JSON and XML formatters should be enabled by default.
UPDATE: I figured it out -- check my own answer below

I figured it out!
I had this in my AppStart, because I wanted the Xml serializer not the DataContract serializer:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
HOWEVER... apparently there is something about my model that makes the Xml Serializer think it can't serialize it. I am guessing that is causing WebAPI to decide to use the JSON formatter instead.
It's completely non-intuitive that this harmless looking setting could actually affect which formatter is used. Hope the WebAPI people see this :)
Some kind of tool that let you understand the inputs and outputs of content negotiation process so you can debug issues like this would be nice.

I had the same issue but fixed it by adding default constructors to all the models that I was returning.
The XML serializer creates blank model objects and then populates it via the setters on the properties. If the setters are protected or private then that property will not get serialized either

The current answers in this thread already call out a lot of the reasons but just to summarize, the XmlSerializer only supports a limited number of types.
When looking for the "best" formatter, the DefaultContentNegotiator, as correctly described by AASoft, asks each of the formatters whether they can support a particular type. It then matches those formatters against the accept headers in the request.
If it doesn't find any match based on the accept headers then it picks the first that can serialize the type, in this case the JSON formatter. However, you can configure the DefaultContentNegotiator to instead of returning a default format then return a 406 None Accepted status code. This indicates to the client that no matching representation could be found and instead of sending data that the client may not be able to use it generates an error response.
Setting this option is described in the blog "ASP.NET Web API Updates – May 14" [1] under the section "Content Negotiation Improvements".
Hope this helps,
Henrik
[1] http://blogs.msdn.com/b/henrikn/archive/2012/05/14/asp-net-web-api-updates-may-14.aspx

The answer has already been provided but I thought I'd put my findings so that it might be helpful to anyone coming later.
The culprit was IEnumerable. For example, returning object of Class Album containing IEnumerable and never getting XML return - only JSON.
I used
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
in the Global.asax as well. Which is actually necessary for xml returns. Still I wasn't getting XML back.
Then I Changed the IEnumerable to List and it worked fine. Looks like the XML Formatter in Web API cannot process the IEnumerable in return objects.
Hope this helps.

Just as a followup to this. We had this problem when we had a List of objects in our return model but the object in the list didn't have a parameterless constructor. Our code looked like this:
public class ReturnClass
{
public int Value { get; set; }
public List<OtherClass> ListOfThings { get; set; }
}
public class OtherClass
{
public int OtherValue { get; set; }
public OtherClass(OtherObject o)
{
this.OtherValue = o.OtherValue;
}
}
We simply had to add a parameterless constructor for the SubClass object.
public class ReturnClass
{
public int Value { get; set; }
public List<OtherClass> ListOfThings { get; set; }
}
public class OtherClass
{
public int OtherValue { get; set; }
public OtherClass(OtherObject o)
{
this.OtherValue = o.OtherValue;
}
public OtherClass()
{
}
}

Be careful using nullable int for any of the properties you may be serializing. A nullable int with config.Formatters.XmlFormatter.UseXmlSerializer = true set will cause Web API to return JSON no matter what your accept header says

I just want to add one more reason this might happen, properties with internal get or set. The kind that are generated by VS when you are adding properties to a class by pressing Ctrl-.
like public string Foo {get; internal set;}

Related

Spring 5 - Read JSON or MultipartFile

I ran into what looks like a really simple problem. I have an http endpoint which should accept either JSON body or uploaded file.
Here is definition of controller method:
#PostMapping(value = "/api/endpoint")
public CompletableFuture<ResponseEntity<Void>> createResource(
#RequestParam(name = "file", required = false) MultipartFile file,
#RequestBody(required = false) Command command){
}
Command is a POJO class with Jackson annotations for deserialisation.
When I pass JSON body with Content-Type: application/json, it works fine. But when I pass file with Content-Type: multipart/form-data, I get 415 Unsupported Media Type.
Here is raw http request when passing just JSON body.
POST /api/devices?= HTTP/1.1
Host: localhost:8080
Content-Type: application/json
cache-control: no-cache
{"foo": "bar"}------WebKitFormBoundary7MA4YWxkTrZu0gW--
Any ideas what is causing this, thanks.
Try the below code.
#PostMapping(value = "/api/endpoint",consumes = {"multipart/form-data"})
public CompletableFuture<ResponseEntity<Void>> createResource(
#RequestPart("file") MultipartFile file,
#RequestPart Command command){
}
You might need two separate methods with different parameters and annotations. Which, of course, can each just call a shared internal method for their common behaviors.

Spring-boot return json and xml from controllers

I have a spring-boot 1.1.7 application that uses Thymeleaf for much of the UI, so the response from my controllers hasn't really been a concern. However, now I need to provide a XML response when a user submits a request via URL.
Here is a typical Request:
http://localhost:9001/remote/search?sdnName=Victoria&address=123 Maple Ave
Here is most of my gradle configuration:
project.ext {
springBootVersion = '1.1.7.RELEASE'
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
compile("org.springframework.boot:spring-boot-starter-thymeleaf")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-data-jpa:$springBootVersion")
compile("org.springframework.security:spring-security-web:4.0.0.M1")
compile("org.springframework.security:spring-security-config:4.0.0.M1")
compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity3:2.1.1.RELEASE')
compile("org.springframework.boot:spring-boot-starter-actuator")
compile('com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.5.0')
}
And here is my controller:
#Controller
public class RemoteSearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = MediaType.APPLICATION_XML_VALUE)
public List<Sdn> search(#ModelAttribute SdnSearch sdnSearch) {
List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
return foundSdns;
}
Here is my Object to be returned:
#Entity
public class Sdn {
#Id
private long entNum;
private String sdnName;
...
//getters & setters here
}
I am able to receive the request via REST client (such as CocoaREST) and handle it. But When I return the list of SDN i get the following exception, even though I do have Jackson & jackson-dataformat-xml on my classpath:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:229)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:301)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:248)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:57)
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:299)
My REST Client is including a Accept Header of "text/xml" (but in all honesty I would rather them not have to set this. Ideally any call to this Controller would always get XML, regardless of header being present).
Is there a way to handle this? I thought the Media Converters were included and just returned whatever the controller told them to?
SOLUTION:
See below for the answer I posted.
I had the exact same problem and I found the solution on Spring documentation website : here
In synthesis, I added the following dependency to the pom.xml of my project :
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
Then I added the following code block to the class that the service had to return :
import javax.xml.bind.annotation.XmlRootElement;
#XmlRootElement
public class Greeting {...}
And it worked.
SOLUTION: I used a combination of both answers below (thank you very much!). I am posting here in case anyone else needs help.
My modified controller:
#Controller
public class RemoteSearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping(value = "/remote/search", method = RequestMethod.GET, produces = { "application/xml", "text/xml" }, consumes = MediaType.ALL_VALUE )
#ResponseBody
public SdnSearchResults search(#ModelAttribute SdnSearch sdnSearch) {
List<Sdn> foundSdns = sdnSearchService.find( sdnSearch );
SdnSearchResults results = new SdnSearchResults();
results.setSdns( foundSdns );
return results;
}
}
And on my client, I set the request headers:
Content-type: application/text
Accept: text/xml
I think ultimately the problem was that my client headers were not being set correctly, so I may not have had to make some of these changes. But I liked the idea of a SearchResults class containing a list of results:
#XmlRootElement
public class SdnSearchResults {
private List<Sdn> sdns;
...
}
It may be better to create a new class:
public class SdnSearchResult {
private List<Sdn> sdns;
...
}
Then, a slight change will be required to the existing classes as follows:
public interface SdnSearchService {
SdnSearchResult find(SdnSearch sdnSearch);
}
#Controller
public class UISearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping("/search")
public ModelAndView search(#ModelAttribute SdnSearch sdnSearch) {
return new ModelAndView("pages/search/results", "sdns", sdnSearchService.find(sdnSearch).getSdns());
}
}
Once this is done, the other controller must be coded as:
#Controller
public class RemoteSearchController {
#Autowired
private SdnSearchService sdnSearchService;
#RequestMapping("/remote/search")
#ResponseBody
public SdnSearchResult search(#RequestBody SdnSearch sdnSearch) {
return sdnSearchService.find(sdnSearch);
}
}
A quick explanation of the changes from your code:
#RequestBody will automatically deserialize the entire HTTP request body to an SdnSearch instance. External applications will typically submit the request data as HTTP body, so #RequestBody will ensure that the deserialization to Java object happens automatically.
#ResponseBody will automatically serialize the return value according to the external client's capabilities and the libraries available on the classpath. If Jackson is available on the classpath and the client has indicated that they can accept JSON, the return value will be automatically sent as JSON. If the JRE is 1.7 or higher (which means that JAXB is included with the JRE) and the client has indicated that they can accept XML, the return value will be automatically sent as XML.
List<Sdn> needs to be changed to SdnSearchResult to ensure that the application can exchange JSON, XML, RSS and ATOM formats with a single controller method, since XML (and XML based formats) require a root-tag on the output, which a List<Sdn> cannot be translated to.
Once these changes are done, fire up a REST client such as the Postman extension for Chrome and submit a request to /remote/search with the following information:
Request header Accepts set to application/json.
Request header Content-Type set to application/json.
Request body set to the JSON string { "sdnName" : "Victoria", "address" : "123 Maple Ave" }.
This will give you a JSON response.
You've marked the controller method as producing application/xml responses (produces = MediaType.APPLICATION_XML_VALUE). The request's accept header (Accept: text/xml) doesn't match so Spring determines that your search method cannot handle the request.
There are a few different ways to fix this on the server, depending on your exact requirements:
You could remove the produces attribute entirely
You could specify multiple media types: produces = { "application/xml", "text/xml" }
I am not sure about your version of Spring Boot (1.1.7.RELEASE) but I am on version 1.5.2.RELEASE and this xml conversion / serialization happens automatically without usage of any jackson dependencies as mentioned in few of the answers.
I guess that is happening because org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter is automatically configured since Spring Boot version 1.5.1.RELEASE & that converter uses default JAXB implementation of JRE ( so no explicit xml conversion dependency needed ) .
Second, Accept header set by clients in request decides which format the output is expected so a request mapping like below ( i.e. a single end point ) ,
#RequestMapping(method = RequestMethod.GET, value = "/remote/search", produces = {
MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE })
can be used to produce an xml as well as a JSON response ( if Accept header is set as text/xml or application/xml & application/json respectively.
Note 1 : javax.xml.bind.annotation.XmlRootElement needs to be specified on root class if xml response is expected for a Java class. This is mandatory.
Note 2 : Jackson for json is already included in Spring Boot so that is not to be explicitly included for json outputs
Note 3 : Accept header - Output match off happens automatically by framework & developer doesn't have to code anything specific for that.
So in my opinion, if you only add XmlRootElement to your base class & upgrade your Spring Boot version, your server side is all set. Responsibility to set correct Accept header lies with the clients.
In addition to what Michael told in his answer, I added the following dependencies as well to pom.xml
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version>
</dependency>
For some reason, the jackson-dataformat-xml alone was not helping.
I also made sure that ResponseEntity is returned in the get call and removed the produces=MediaType from the RequestMapping annotation.
With these changes, I was able to get the correct data but I had to give the extension of mime type to the REST URL during get call. ie, specify explicitly like: http://localhost:8080/hello.xml or http://localhost:8080/hello.json in browser
In my case I wanted to return a formatted XML string and it was all combined into one line.
Adding produces = { "application/xml", "text/xml" } to the request mapping was enough to return the string as formatted XML (with indentation).
example:
#RequestMapping(method= RequestMethod.GET, value="/generate/{blabla}", produces = { "application/xml", "text/xml" })
public String getBlaBla(#PathVariable("param") String param) throws IOException {
}
Goodluck.

spring-mvc The request sent by the client was syntactically incorrect. for json post

I am trying to build a springmvc app. On one of the action I want it to take a POST of JSON content. I am sending the following values:
POST /TestResults/create.json HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
[ { "test_name": "value1", "test_time": "111"} ]
However, when I send it I am getting the following error:
The request sent by the client was syntactically incorrect.
My action looks like this:
#RequestMapping(value="/create", method=RequestMethod.POST, consumes = "application/json")
#ResponseBody
public ModelAndView createTest(#RequestBody TestResult test_result) {
When I change the action to bepublic ModelAndView createTest(#RequestBody String test_result) { the action does succeed but I am using "String" at that time.
Is it possible to know what I might be doing incorrectly?
The JSON you are receiving is a JSON array. A JSON array unless you have a custom deserializer cannot be mapped to a type like TestResult, unless it's some subtype of Collection. You'll want to use something like
public ModelAndView createTest(#RequestBody List<TestResult> testResults) {
For #RequestBody String it works because all Spring has to do is read from the request body and convert the bytes into a String.

Caching of Web Service not working when request has query string

I'm trying to implement the client-side caching of web service calls, and based on information from the web, I was able to do it according to the SetCachingPolicy() function as shown in code 1 below.
I was able to successfully get it working with a web method, RetrieveX, but not with method RetrieveY. I noticed that RetrieveX has no parameters and RetrieveY has one string parameter, and on inspection under Fiddler, the difference seems to be that the HTTP GET request of the web service call from RetrieveY has a query string for the parameter.
All HTTP GET web service calls so far without a query string is doing the caching properly, but not this call that has a query string in it.
Examination under Fiddler indicates that RetrieveX has the following caching information in output 1, and RetrieveY has the information in output 2.
Is this a limitation of this caching method or can I do something to get the client side caching of RetrieveY working?
Code 1: SetCachingPolicy
private void SetCachingPolicy()
{
HttpCachePolicy cache = HttpContext.Current.Response.Cache;
cache.SetCacheability(HttpCacheability.Private);
cache.SetExpires(DateTime.Now.AddSeconds((double)30));
FieldInfo maxAgeField = cache.GetType().GetField(
"_maxAge", BindingFlags.Instance | BindingFlags.NonPublic);
maxAgeField.SetValue(cache, new TimeSpan(0, 0, 30));
}
Code 2: RetrieveX
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
public string[] RetrieveX()
{
SetCachingPolicy();
// Implementation details here.
return array;
}
Code 3: RetrieveY
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod(UseHttpGet = true)]
public string[] RetrieveY(string arg1)
{
SetCachingPolicy();
// Implementation details here.
return array;
}
Output 1: RetrieveX caching info
HTTP/200 responses are cacheable by default, unless Expires, Pragma, or Cache-Control headers are present and forbid caching.
HTTP/1.0 Expires Header is present: Wed, 12 Sep 2012 03:16:50 GMT
HTTP/1.1 Cache-Control Header is present: private, max-age=30
private: This response MUST NOT be cached by a shared cache.
max-age: This resource will expire in .5 minutes. [30 sec]
Output 2: RetrieveY caching info
HTTP/200 responses are cacheable by default, unless Expires, Pragma, or Cache-Control headers are present and forbid caching.
HTTP/1.0 Expires Header is present: -1
Legacy Pragma Header is present: no-cache
HTTP/1.1 Cache-Control Header is present: no-cache
no-cache: This response MUST NOT be reused without successful revalidation with the origin server.
I ran into this issue as well, I thought I'd share what worked for me. The underlying issue is that VaryByParams is not being set on the response. If you add this to your SetCachingPolicy() method RetrieveY should begin working as expected:
cache.VaryByParams["*"] = true

ASP.NET MVC ignoring Content-Length?

I've been having some problems with missing post data in ASP.NET MVC which has lead me to investigate how ASP.NET MVC deals with invalid content lengths. I had presumed that a post with a invalid content length should be ignored by MVC.NET but this doesn't seem to be the case.
As an example, try creating a new ASP.NET MVC 2 web application and add this action to the HomeController:
public ActionResult Test(int userID, string text)
{
return Content("UserID = " + userID + " Text = " + text);
}
Try creating a simple form that posts to the above action, run fiddler and (using "Request Builder") modify the raw data so that some of the form data is missing (e.g. remove the text parameter). Before executing the request, remember to un-tick the "Fix Content-Length header" checkbox under the Request Builder options then set a break point on the code above and execute the custom http request.
I find that the request takes a lot longer than normal (30 seconds or so) but to my amazement is still processed by the controllers action. Does anyone know if this is expected behavior and, if so, what would you recommend to safeguard against invalid content-lengths?
ASP.NET does not ignore the Content-Length request header. Consider the following controller action as an example which simply echoes back the foo parameter:
[HttpPost]
public ActionResult Index(string foo)
{
return Content(foo, "text/plain");
}
Now let's make a valid POST request to it:
using (var client = new TcpClient("127.0.0.1", 2555))
using (var stream = client.GetStream())
using (var writer = new StreamWriter(stream))
using (var reader = new StreamReader(stream))
{
writer.Write(
#"POST /home/index HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:2555
Content-Length: 10
Connection: close
foo=foobar");
writer.Flush();
Console.WriteLine(reader.ReadToEnd());
}
As expected this prints the response HTTP headers (which are not important) and in the body we have foobar. Now try reducing the Content-Length header of the request:
POST /home/index HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:2555
Content-Length: 5
Connection: close
foo=foobar
Which returns a single f in the response body. So as you can see an invalid HTTP request could lead to incorrect parsing of the parameters.

Resources