2

I have a WebApi with several controllers which return different results. For example, one controller returned an IEnumerable<Foo>, another a Bar, another an IEnumerable of IEnumerable etc., and all I had to do was:

return Ok(thething)

and everything worked fine, even complicated nested objects were serialized with no problem.

Now, The client asked that all results be returned in a Wrapper:

public class Wrapper
{
    public bool Success { get; set; }
    public int ErrorCode { get; set; }
    public String ErrorMessage { get; set; }
    public String Referrer { get; set; }
    public Object Payload { get; set; }
}

Thought it would be trivial, but when I try to return it from the controller:

return Ok(new Wrapper { Success = true, Referrer = "me", Payload = thething)

I get a serialization error:

The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.

The inner exception message is:

'System.Linq.Enumerable+WhereSelectListIterator2[[EPiServer.Find.Api.SearchHit1[[DGTNext.Api.Data.Entities.ProductSummary, DGTNext.Api.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]], EPiServer.Find, Version=9.6.0.3185, Culture=neutral, PublicKeyToken=8fe83dea738b45b7],[DGTNext.Api.Data.Entities.ProductSummary, DGTNext.Api.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' with data contract name 'ArrayOfProductSummary:http://schemas.datacontract.org/2004/07/DGTNext.Api.Data.Entities' is not expected. Consider using a DataContractResolver if you are using DataContractSerializer or add any types not known statically to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to the serializer.

What am I doing wrong? Why did the Ok() function seem to be handle any object before, but has problems now?

Thanks.

Edit: as requested, a simple example of something causing the error:

class Foo 
{
    public int AnInt { get; set; }
}

public IHttpActionResult Get() 
{
    return Ok(new Wrapper { Success = true, Referrer = "me", Payload = new Foo { AnInt = 7 } });
}

Edit #2: well, I came up with kind of a solution, but it still raises some questions.

I made my Wrapper generic in the type of the payload.

public class Wrapper<T>
{
    public bool Success { get; set; }
    public int ErrorCode { get; set; }
    public String ErrorMessage { get; set; }
    public String Referrer { get; set; }
    public T Payload { get; set; }
}

So now, this works:

public IHttpActionResult Get() 
{
    List<Foo> foos = new List<Foo>();
    foos.Add(new Foo { AnInt = 7 });
    foos.Add(new Foo { AnInt = 8 });

    return Ok(new Wrapper<IEnumerable<Foo>> { Success = true, Referrer = "me", Payload = foos });
}

It returns:

{
    "Success": true,
    "ErrorCode": 0,
    "ErrorMessage": null,
    "Referrer": "me",
    "Payload": [ { "AnInt": 7 }, { "AnInt": 8 } ] 
}

And my "real" call:

public IHttpActionResult Get() 
{
    IEnumerable<ProductSummary> prods =  db.getProductSummaries(shopId, culture, queryParams, paging);
    return Ok(new Wrapper<IEnumerable<ProductSummary>> { Success = true, Referrer = "me", Payload = prods });
}

returns:

<WrapperOfArrayOfProductSummaryzc2y5_Pnl>
    <ErrorCode>0</ErrorCode>
    <ErrorMessage i:nil="true"/>
    <Payload>
        <d2p1:ProductSummary>
            <d2p1:Culture i:nil="true"/>
            <d2p1:Guid i:nil="true"/>
            <d2p1:Id>2</d2p1:Id>
            <d2p1:Name>Letto Asia</d2p1:Name>
            <d2p1:ambient>
                <d2p1:Id>1073741838</d2p1:Id>
                <d2p1:Name>notte</d2p1:Name>
            </d2p1:ambient>  
    etc.

So not bad, but this raises two questions:

  1. To test the webapi, I'm calling it by putting an URL in the Firefox address bar and looking an the results in the browser. Why in the world does the first call return Json, and the second XML? As far as I know I'm just using default everything.

  2. Why is the XML now adding that namespace to all the element names? Can I prevent this? When I was just returning the same thing without the wrapper, this didn't happen.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Pete
  • 51
  • 1
  • 7
  • Can you amend your question with an example of the object that you're passing in Payload? – Hades Jun 16 '16 at 15:37
  • Does this realy compile: return Ok( new Wrapper { Success=true, Referer="me", Payload=thething), You missed } at the end. should be like this:. return Ok( new Wrapper { Success=true, Referer="me", Payload=thething}); – Marcus Höglund Jun 16 '16 at 17:32
  • Yes, it compiles, copy-paste error. – Pete Jun 18 '16 at 07:15
  • Do you want JSON or XML? – Tomas Aschan Jun 18 '16 at 12:54
  • As I understand it, one of the advantages to using the WebApi framework is that it will return whatever the caller asks for. @TomasLycken – Pete Jun 26 '16 at 17:37
  • Yes, that's true; however, just entering the url in a browser and inspecting the results can tend to be confusing. The browser usually sends an `Accept` header that *should* lead to ASP.NET Web API returning XML, but since it's become much more common to want JSON, there's some special handling going on (can't remember if it's in the browser or on the server side, and can't find where I read about it...) to make the browser show JSON anyway in this case. Thus, it's interesting to know a) what you want to see, and b) how you're trying to get there :) – Tomas Aschan Jun 27 '16 at 06:29
  • All I care about seeing is the content in any form, since I'm just debugging; but I would like to understand why the browser sometimes shows me XML and sometimes Json, seemingly at random. @TomasLycken – Pete Jun 28 '16 at 14:51
  • Can you inspect two requests that give different results, and see what Accept headers they sent? (Open the dev tools, e.g. F12 in IE or Ctrl+Shift+J in Chrome, switch to the network tab to start capturing traffic, and make a request.) – Tomas Aschan Jun 30 '16 at 06:17

1 Answers1

5

Add this code to global.asax below on Application_Start:

Update from .Ignore to .Serialize. It must work.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
        GlobalConfiguration.Configuration.Formatters.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

or you may look at this answer

Community
  • 1
  • 1
Himanshu Jain
  • 518
  • 4
  • 20