in ASP.NET

ASP.NET Web API: Configurando a serialização com JSON e XML

O ASP.NET Web API é um framework muito flexível e testável, falando em API’s existem diversas maneiras de empacotar e entregar as informações para os clientes que estão utilizando, recentemente tive que realizar algumas configuração de data e formatação de como os objetos seriam serializando. Pensando nisto, decidi escrever este post para mostrar algumas das possibilidades de configurações em como é feita a serialização e entrega pelas nossas WebAPI’s.

Negociação (Content Negotiation)

A WebAPI possui um mecanismo que analisa a requisição, os MediaTypeFormatters disponíveis e utiliza a melhor maneira para responder a requisição, seja em JSON, XML ou em outros formatos.

Na especificação do HTTP (RFC 2616) existe a definição para o content negotiation como “o processo de selecionar a melhor representação para uma resposta, quanto múltiplas representações estão disponíveis”. Alguns headers da request são utilizados nesta negociação:

  • Accept (exemplo: application/json, appliation/xml)
  • Accept-Charset (exemplo: UTF-8, ISO 8859-1)
  • Accept-Encoding (exemplo: Gzip)
  • Accept-Language (exemplo: en-us, pt-br)

JSON e XML MediaTypeFormatter

No WebAPI é possível adicionar e remover os MediaTypeFormatter dos tipos de serialização. Isto é útil em cenário que precisamos garantir e restringir que uma api só retorne JSON ou só XML, além disto, podemos adicionar outros tipos de formaters, por exemplo para JSONP ou BSON.

Removendo um MediaTypeFormatter

No exemplo abaixo eu removo o MediaTypeFormatter responsável por serializar e entregar o conteúdo da minha api em XML (na linha comentada eu também mostro como eu faria para remover o JsonFormater).

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Formatters.Remove(config.Formatters.XmlFormatter);
        //ou
        //config.Formatters.Remove(config.Formatters.JsonFormatter);
        

        ////outras configuracoes
    }
}

Adicionando um novo MediaTypeFormatter

Outra possibilidade é adicionarmos um MediaTypeFormatter, podemos até desenvolver um custom e adicionar, para este exemplo, vou adicionar um MediaTypeFormatter para BSON (notem que dei um using no System.Net.HttpFormatting).

using System.Net.Http.Formatting;
using System.Web.Http;

namespace APIExample
{
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Formatters.Add(new BsonMediaTypeFormatter());

        ////outras configuracoes
    }
}

Com isto nossa WebAPI já pode entender requests que utilizem o header de Accept “application/bson”, abaixo está o exemplo da request utilizando uma ferramenta muito útil que se chama Fiddler.

image

Neste repositório do github é possível ver um exemplo de um MediaTypeFormatter para JSONP, também é possível adicionar via NuGet https://www.nuget.org/packages/WebApiContrib.Formatting.Jsonp/.

JSON

O JSON é um formato fácil de ser consumidos por diversas aplicações, além de oferecer um custo (tamanho) menor do que o do XML na transmissão dos dados. No WebAPI ele é formatado e serializado por uma biblioteca chamada Json.NET, claro que é possível substituir e utilizar outras bibliotecas para a serialização (existem diversos comparativos de performance e exemplo para isto https://github.com/kevin-montrose/Jil).

DataContractJsonSerializer

A primeira configuração que é possível fazer é alterar o JsonMediaTypeFormatter, que por padrão utiliza o Json.NET para utilizar o DataContractJsonSerializer, para realizar esta mudança é preciso utilizar o seguinte código (também no Register das configurações do WebAPI)

config.Formatters.JsonFormatter.UseDataContractJsonSerializer = true;

Só de realizar esta mudança já podemos ver difererenças em como o JSON é serializado, abaixo estão o resultado de um objeto com DateTime e um Dictionary:

//Ex (com DataContractJsonSerializer):
{"Endereco":"Itaim Bibi","ExTags":[{"Key":"Exemplo1","Value":"ValordoExemplo"},{"Key":"Exemplo2","Value":"ValordoExemplo"}],"Id":1,"Nome":"Rodolfo","Sobrenome":"Fadino","UltimoLogin":"\/Date(1430881790994-0300)\/"}

//Ex (com Json.NET):
{"Id":1,"Nome":"Rodolfo","Sobrenome":"Fadino","Endereco":"Itaim Bibi","UltimoLogin":"2015-05-06T00:38:41.2588175-03:00","ExTags":{"Exemplo1":"ValordoExemplo","Exemplo2":"ValordoExemplo"}}

O que é serializado?

Por padrão em uma classe, todas as propriedades publicas serão serializadas, caso alguma propriedade não deva ser serializada na resposta é possível utilizar o atributo JsonIgnore para que este campos seja ignorado:

public class Usuario
{
    public int Id { get; set; }
    public string Nome { get; set; }
    [JsonIgnore]
    public DateTime UltimoLogin { get; set; }
}

 

Outra opção é utiliza o atributo DataContract, com ele somente os campos DataMember serão serializados:

[DataContract]
public class Usuario
{
    [DataMember]
    public int Id { get; set; }
    [DataMember]
    public string Nome { get; set; }
    public DateTime UltimoLogin { get; set; }
}

Resultado:

image

Formatação de Data

O Json.NET utiliza o padrão ISO 8601 para as datas, quando ele utiliza data em UTC ela é enviada com um sufixo “Z”. Datas que estão em local time (não UTC) são enviadas com o time-zone junto.

2015-05-07T15:00:00.57412Z         // UTC
2015-05-07T15:00:00.57412-03:00    // Local (GMT -3)

O Json.NET também preserva o time zone, entretanto você pode sobrescrever esta configuração, utilizando a propriedade DateTimeZoneHandling.

var jsonFormater= config.Formatters.JsonFormatter;
jsonFormater.SerializerSettings.DateTimeZoneHandling=Newtonsoft.Json.DateTimeZoneHandling.Utc;

Outra alteração que podemos fazer nas datas é utilizar o Microsoft JSON date format (“\/Date(ticks)\/”), para isto devemos alterar a propriedade DateFormatHandling.

var jsonFormater= config.Formatters.JsonFormatter;
jsonFormater.SerializerSettings.DateFormatHandling = Newtonsoft.Json.DateFormatHandling.MicrosoftDateFormat;

Indentação

A indentação também é uma outra opção para ser configurada, particularmente eu não indentaria um JSON de uma API de produção (ocupa mais espaço). Porém isto pode ser útil para facilitar a visualização dos dados e eu utilizaria em um ambiente de debug ou sandbox.

var jsonFormater= config.Formatters.JsonFormatter;
jsonFormater.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;

Abaixo temos um exemplo sem esta configuração de Indentação:

image

E aqui o exemplo com a indentação:

image

Camel Casing (minúscula)

Para escrever o JSON com Camel Case podemos configurar o CamelCasePropertyNamesContractResolver:

var jsonFormater = config.Formatters.JsonFormatter;
jsonFormater.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

Resultado:

image

Objetos Anônimos ou “não tipados”

Seguindo um pouco a linha de desenvolvimento com tipos mais flexíveis, é possível ter retorno anônimo na nossa api, por exemplo:

public dynamic Get(int id)
{
    return new { Nome = "Rodolfo", Sobrenome = "Fadino" };
}
Ou então, podemos ter um método que receba como parâmetro itens que podem variar:
public void Post(JObject usuario)
{
    string nome = usuario["Nome"].ToString();
    int rg = usuario["Rg"].ToObject<int>();   
}
<strong>Referencia Circular</strong>

Referencia circular é uma coisa que é bem comum entre os objetos, ao serializar objetos que tem a referencia circular a API retornaria uma exception. Para simular montei o seguinte modelo de dados:

public class Usuario
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public Area Area { get; set; }
}

public class Area
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public Usuario Coordenador { get; set; }
}

E a seguinte Action no Controller

public Area Get(int id)
{
    var areaTi=new Area(){Id = 1,Nome = "Tecnologia"};
    var usuario = new Usuario() {Id = 33, Nome = "Rodolfo", Area = areaTi};
    areaTi.Coordenador = usuario;
    return areaTi;
}

No exemplo acima, ao fazer uma requisição para a Action, é retornado um erro 500, explicando que não foi possível serializar os objetos.

image

Podemos contornar este tipo de situação configurando para a nossa WebApi tratar e preservar as referencias durante a serialização, também é possível realizar outras configurações (All, Array, None e Objects).

var jsonFormater = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
jsonFormater.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.All;

Notem o resultado da requisição agora, ele envolve os objetos em outros com Id’s únicos e referencia eles:

image

Em breve farei a segunda parte deste post abordando como trabalhar com XML 🙂

Bom espero que este post tenha sido útil, estou a disposição para dúvidas criticas ou sugestões.

abs

Rodolfo

  • Anonymous

    fala rodolfo, na minha webapi nao consigo retornar o json no formato correto, tipo request header esta como “text/html”