Returning an anonymous type from MVC 4 Web Api fails with a serialization error

I’m just getting started with MVC 4 Web API and I seem to be misunderstanding how it works.

Before Web API I had a simple MVC action method like this:

public JsonResult User()
{
    return Json(new
    {
        firstName = "Joe",
        lastName = "Jacobs",
        email = "joe.jaco<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e38190a3848e828a8fcd808c8e">[email protected]</a>"
    });
}

That would work fine. In the new web API controller I am trying to do something similar.

public object User()
{
    return new
    {
        firstName = "Joe",
        lastName = "Jacobs",
        email = "<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7f15101a51151e1c101d0c3f18121e1613511c1012">[email protected]</a>"
    }
}

This fails with a serialization error:

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

Inner exception:

Type ‘<>f__AnonymousType1`3[System.String,System.String,System.String]’ cannot be serialized. Consider marking it with the DataContractAttribute attribute, and marking all of its members you want serialized with the DataMemberAttribute attribute. If the type is a collection, consider marking it with the CollectionDataContractAttribute. See the Microsoft .NET Framework documentation for other supported types.

What am I not understanding about returning anonymous type from the API controller?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

If you look at the Fiddler (sample in here I use Firefox)

enter image description here

By default, request from browser will accepts application/xml, not application/json

But, you can create fake request from Fiddler by adding one header:

Accept: application/json

It will work

From the link:

The XML serializer does not support anonymous types or JObject instances. If you use these features for your JSON data, you should remove the XML formatter from the pipeline, as described later in this article.

How to remove XmlFormatter:

  var configuration = GlobalConfiguration.Configuration;
  configuration.Formatters.Remove(configuration.Formatters.XmlFormatter);

Method 2

You could also use the JsonMediaTypeFormatter so you do not need the JSONObject and related classes. Then you can return a dynamic type in your controller class.

public static void Register(HttpConfiguration config)
{
    config.Formatters.Clear();            
    config.Formatters.Add(new JsonMediaTypeFormatter());
    config.MapHttpAttributeRoutes();
}

public class YourController : ApiController
{        
    [HttpGet, Route("getstuff/{stuffId}")]
    public dynamic Get(string stuffId)
    {
        var stuff = Model.Stuff.Get(stuffId);

        return new {
            success= stuff != null,
            stuffId = stuff.Id,
            name = stuff.Name
        };
    }
}

If you also want to support Jsonp you can inherit the JsonMediaTypeFormatter and create you own JsonpMediaTypeFormatter (which also can be found on stackoverflow: https://stackoverflow.com/a/12492552/1138266).

Method 3

I found that the API didn’t like returning a raw list. Instead, I had to create an object, and set the value of the object as my list; see the first return statement.

Example:

public IHttpActionResult GetMessages(int messageFeedId, int lastMessageId) {
    List<Message> messageDomainObjects = MessageService.GetMessages(messageFeedId, lastMessageId);

    if (messageDomainObjects.Any())
    {
        var messages = messageDomainObjects.Select(m => new MessageModel(
            m.Id,
            m.Message,
            m.CreatedDate,
            m.IsActive,
            new UserModel(
                m.User.Id,
                m.User.FirstName,
                m.User.LastName
            )
        ));

        return Ok(new { messages = messages });
    }
    else
    {
        return Ok(new { });
    }
}

Method 4

I was having a similar problem and the solution was to add a snippet to the
Global.asax.cs file.

The snippet to add is as follows:

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

I also added the above lines of code to the very top of the Application_Start method which left the method looking like this:

protected void Application_Start()
    {
        GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
            .ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
        GlobalConfiguration.Configuration.Formatters
            .Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);

        AreaRegistration.RegisterAllAreas();
        GlobalConfiguration.Configure(WebApiConfig.Register);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    }


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x