Elemar DEV

Tecnologia e desenvolvimento .net

Provendo recursos em diferentes formatos (CSV e vCard, por exemplo) usando Asp.net Web API

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.

Ponto de partida

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.

Entregando vCard

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:

  • a definição do(s) formato(s) suportado(s) ocorre no construtor (especificando o MimeType);
  • há dois métodos, CanReadType e CanWriteType, que determinam se o formatter suporta um determinado tipo (convertendo de/para);
  • há um método assíncrono onde a “escrita para um stream” ocorre efetivamente.

Pegou a idéia?!

Entregando CSV

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.

Onde colocar os formatters?

Pessoalmente, optei por colocar os formatters de meu projeto em uma pasta só para eles. Veja:

image

Configurando..

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:

image

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.

Em uso..

Para ver o serviço funcionando, faço testes usando o Fiddler.

Primerio, vCard:

image

E, como você pode ver, tenho o resultado esperado:

image

Agora, CSV:

image

Resultado:

image

Done!

Era isso.

5 Comments on “Provendo recursos em diferentes formatos (CSV e vCard, por exemplo) usando Asp.net Web API

  1. Pingback: PAREANDO: Como você escreveria esse código? « Elemar DEV

  2. Flávio
    27/06/2012

    Excelente post, parabéns!

  3. Eduardo Pires
    27/06/2012

    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!

    • elemarjr
      27/06/2012

      Muito obrigado pelo feedback.

      muitas vezes, compartilho o código no meu github. Infelizmente, não foi o caso neste post.

  4. Pingback: Suportando JSONP (contornando a “Política da mesma origem”) no ASP.net WEB API « Elemar DEV

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

WordPress.com Logo

Você está comentando usando sua conta WordPress.com. Sair / Mudar )

Imagem do Twitter

Você está comentando usando sua conta Twitter. Sair / Mudar )

Foto do Facebook

Você está comentando usando sua conta Facebook. Sair / Mudar )

Conectando a %s

Informação

Publicado às 26/06/2012 por em Post e marcado , .

Estatísticas

  • 427,073 hits
%d bloggers like this: