Olá pessoal. Tudo certo?!
Quando desenvolvemos serviços Web precisamos, muitas vezes, ter condições de entregar um mesmo recurso em diferentes formatos.
ASP.net Web API suporta, nativamente, XML e JSON. Nesse post, mostro como adicionar suporte para outros formatos.
Se você não conhece Web API, talvez deva considerar ver outros posts sobre este assunto.
Considere o seguinte controller:
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using WebApi.MediaTypeFormatter.Models;
using WebApi.MediaTypeFormatter.Repositories;
namespace WebApi.MediaTypeFormatter.Controllers
{
public class ContactsController : ApiController
{
private static readonly ContactsRepository repository =
new ContactsRepository();
public Contact Get(int id)
{
Contact result = repository.Get(id);
if (result == null)
throw new HttpResponseException(
new HttpResponseMessage(HttpStatusCode.NotFound)
);
return result;
}
public IEnumerable<Contact> GetAll()
{
return repository.GetAll();
}
}
}
Como pode ver, é um serviço muito simples. Ele pode prover dados relacionados a um contato (quando um id é fornecido), ou a todos os contatos do repositório.
Web API permite que desenvolvamos nosso controller pensando apenas no recurso. O formato é resolvido fora desse contexto.
Perceba que o recurso já está disponível, por default, como JSON e XML.
Para entregar um recurso em um formato diferente de XML e JSON, precisamos escrever um formatter. Veja o conceito aplicado a entrega em vCard:
using System;
using System.IO;
using System.Net;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Collections.Generic;
using WebApi.MediaTypeFormatter.Models;
namespace WebApi.MediaTypeFormatter.Formatters
{
public class ContactAsVCardMediaTypeFormatter :
System.Net.Http.Formatting.MediaTypeFormatter
{
public ContactAsVCardMediaTypeFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/vcard"));
}
#region Overrides of MediaTypeFormatter
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
var result = type == typeof (Contact) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
return result;
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream,
HttpContentHeaders contentHeaders,
TransportContext transportContext)
{
return Task.Factory.StartNew(() => {
var contacts = (value is Contact)
? new[] {value as Contact}
: value as IEnumerable<Contact>;
using (var sw = new StreamWriter(stream))
{
foreach (var contact in contacts)
{
sw.WriteLine("BEGIN:VCARD");
sw.WriteLine("VERSION:2.1");
sw.WriteLine("N:{0};{1}", contact.LastName,
contact.FirstName);
sw.WriteLine("FN:{0} {1}", contact.FirstName,
contact.LastName);
sw.WriteLine("EMAIL:{0}", contact.Email);
sw.WriteLine("END:VCARD");
}
}
});
}
#endregion
}
}
Como pode ver, um formatter é uma classe comum que herda de MediaTypeFormatter. Perceba que:
Pegou a idéia?!
Já escrevemos um Formatter para vCard, vamos escrever um para CSV. Veja:
using System;
using System.IO;
using System.Net;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Collections.Generic;
using WebApi.MediaTypeFormatter.Models;
namespace WebApi.MediaTypeFormatter.Formatters
{
public class ContactAsCsvMediaTypeFormatter :
System.Net.Http.Formatting.MediaTypeFormatter
{
public ContactAsCsvMediaTypeFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
}
#region Overrides of MediaTypeFormatter
public override bool CanReadType(Type type)
{
return false;
}
public override bool CanWriteType(Type type)
{
var result = type == typeof(Contact) ||
typeof(IEnumerable<Contact>).IsAssignableFrom(type);
return result;
}
public override Task WriteToStreamAsync(Type type, object value, Stream stream,
HttpContentHeaders contentHeaders,
TransportContext transportContext)
{
return Task.Factory.StartNew(() => value.WriteTo(stream));
}
#endregion
}
public static class ContactCsvExtensions
{
public static void WriteTo(this object that, Stream stream)
{
using (var sw = new StreamWriter(stream))
{
if (that is Contact)
(that as Contact).WriteTo(sw);
else
(that as IEnumerable<Contact>).WriteTo(sw);
}
}
public static void WriteTo(this Contact that, StreamWriter sw)
{
sw.WriteLine("{0},{1},{2},{3}",
that.Id,
that.FirstName,
that.LastName,
that.Email
);
}
public static void WriteTo(this IEnumerable<Contact> that, StreamWriter sw)
{
foreach (var contact in that)
contact.WriteTo(sw);
}
}
}
Apliquei a mesma idéia.
Pessoalmente, optei por colocar os formatters de meu projeto em uma pasta só para eles. Veja:
A última versão do ASP.net MVC retirou todas as configurações do arquivo Global.asax. Agora, toda a configuração está em classes especialistas presentes na pasta App_Start.
Seguindo esse padrão, criei um “configurador” para meus formatters. Veja:
Aqui está seu conteúdo:
using System.Web.Http;
using WebApi.MediaTypeFormatter.Formatters;
namespace WebApi.MediaTypeFormatter
{
public class FormatterConfig
{
public static void RegisterFormatters()
{
GlobalConfiguration.Configuration.Formatters.Add(
new ContactAsVCardMediaTypeFormatter());
GlobalConfiguration.Configuration.Formatters.Add(
new ContactAsCsvMediaTypeFormatter());
}
}
}
E seu “uso” em Global.asax:
using System.Web;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
namespace WebApi.MediaTypeFormatter
{
public class WebApiApplication : HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
FormatterConfig.RegisterFormatters();
}
}
}
BTW, bem bacana essa “separação” proposta pelo time do ASP.net MVC.
Para ver o serviço funcionando, faço testes usando o Fiddler.
Primerio, vCard:
E, como você pode ver, tenho o resultado esperado:
Agora, CSV:
Resultado:
Done!
Era isso.
Pingback: PAREANDO: Como você escreveria esse código? « Elemar DEV
Excelente post, parabéns!
Não sei se é de costume, mas seria bem legal se pudesse disponibilizar esse projeto no seu Blog ou em algum repositório de código
Muito bem escrito, parabéns!
Muito obrigado pelo feedback.
muitas vezes, compartilho o código no meu github. Infelizmente, não foi o caso neste post.
Pingback: Suportando JSONP (contornando a “Política da mesma origem”) no ASP.net WEB API « Elemar DEV