Desenvolvimento - C#

WCF - Syndication

Web Syndication é uma forma popularmente conhecida que temos para publicar um conteúdo de um determinado site para outros sites ou pessoas.

por Israel Aéce



function doClick(index, numTabs, id) { document.all("tab" + id, index).className = "tab"; for (var i=1; i Baixe o código.

Web Syndication é uma forma popularmente conhecida que temos para publicar um conteúdo de um determinado site para outros sites ou pessoas. Esta técnica fornece aos seus consumidores um pequeno sumário ou, às vezes, em sua íntegra, conteúdos que foram recentemente adicionados ao site. A partir da versão 3.5, o WCF disponibiliza uma API para suportar a criação de serviços que expõem o seu conteúdo em um dos formatos conhecidos para o syndication e esta API que será abordada no decorrer deste artigo.

Um dos exemplos mais comuns que conhecemos atualmente que fazem o uso desta técnica são sites de conteúdo bastante dinâmico, como é caso de notícias, fóruns e blogs. Disponibilizar o seu conteúdo através do syndication é uma forma simples que temos de manter nossos leitores atualizados, sempre informando que um novo conteúdo foi adicionado ao site. Os sites que publicam este tipo de conteúdo geralmente disponibilizam um endereço onde o mesmo é listado. Esse endereço também é conhecido como feed, e os interessados podem assiná-lo a partir deste endereço, cadastrando-o em um agregador que nada mais é do que uma aplicação Web ou Windows e que tem a finalidade de gerenciar todos os feeds.

Esse conteúdo é exposto via protocolo HTTP, podendo estar em texto puro ou até mesmo em formato HTML. Atualmente temos dois formatos que regem o syndication, sendo eles: RSS (Really Simple Syndication) e ATOM, ambos baseados em XML. O RSS foi criado antes do ATOM e este, por sua vez, tende a suprir algumas necessidades que o formato RSS deixa a desejar e, justamente por isso, as divergências entre os dois formatos são inevitáveis. A comparação e o detalhamento de cada um dos formatos estão além do escopo deste artigo. Se desejar analisar mais detalhes sobre isso, consulte estes endereços: RSS e ATOM.

Devemos separar as classes que o WCF fornece para a criação do syndication em duas seções, tendo a primeira delas classes para a criação dos metadados e dos itens que irão compor o feed e, isso tudo, de forma totalmente independente do formato; já a segunda seção terá as classes de infraestrutura, específicas ao formato (RSS 2.0 ou ATOM 1.0) e que serão responsáveis pela serialização do feed.

As classes que fazem parte da API do syndication estão contidas no namespace System.ServiceModel.Syndication e este, por sua vez, dentro do assembly System.ServiceModel.Web.dll. As primeiras classes que vamos analisar são as classes responsáveis pela serialização dos objetos que irão compor o feed. A primeira classe que temos que analisar é a SyndicationFeedFormatter. Esta classe é marcada como abstrata, servindo como base para todos os outros formatadores e, a partir dela, nascem suas especializações, como o Rss20FeedFormatter e Atom10FeedFormatter. A primeira delas, Rss20FeedFormatter, é responsável pela serialização do feed no formato RSS 2.0, enquanto a segunda é responsável pela serialização no formato ATOM 1.0. Ainda há a versão genérica para os dois formatos, que deve ser utilizada caso você tenha um feed customizado. A imagem abaixo ilustra essa hierarquia:

Figura 1 - Hierarquia dos formatadores.

A criação de syndication a partir do WCF nos obriga a criar o contrato do serviço seguindo algumas exigências. A primeira delas é que o método que retornará os itens (que podem ser notícias, posts, etc.) deve retornar a instância de uma classe que herde da SyndicationFeedFormatter, ou seja, uma das quatro opções que vimos na imagem acima. Neste caso, se quisermos ter as duas versões e permitir que o usuário escolha o formato, teríamos que ter dois métodos diferentes e, cada um deles, retornar a instância concreta do formatador. Uma alternativa a isso é especificar o tipo do retorno do método para a classe base SyndicationFeedFormatter, e o método passaria a receber um parâmetro que indica o formato desejado e, depois do feed criado, você cria a instância concreta da classe correspondente ao formato escolhido. O código abaixo ilustra a criação do contrato baseando-se nesta segunda opção:

using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Syndication;

[ServiceContract]
[ServiceKnownType(typeof(Atom10FeedFormatter))]
[ServiceKnownType(typeof(Rss20FeedFormatter))]
public interface IBlog
{
    [OperationContract]
    [WebGet(UriTemplate = "GetPosts?formato={formato}")]
    SyndicationFeedFormatter GetPosts(string formato);
}
Imports System
Imports System.ServiceModel
Imports System.ServiceModel.Web
Imports System.ServiceModel.Syndication

<ServiceContract(), _
    ServiceKnownType(GetType(Atom10FeedFormatter)), _
    ServiceKnownType(GetType(Rss20FeedFormatter))> _
Public Interface IBlog
    <OperationContract(), WebGet(UriTemplate:="GetPosts?formato={formato}")> _
    Function GetPosts(ByVal formato As String) As SyndicationFeedFormatter 
End Interface
C# VB.NET

Apesar do contrato ter apenas um único método, há algumas características que o mesmo utiliza e que não é uma exclusividade do syndication. A primeira delas é o atributo ServiceKnownTypeAttribute. Esse atributo especifica os possíveis tipos que podem ser retornados para o cliente (para maiores detalhes sobre know types, consulte este artigo). Se a operação já retornasse o tipo concreto, não haveria a necessidade de utilizar este atributo. Já a segunda característica trata-se do atributo WebGetAttribute. Este atributo faz parte de um conjunto de tipos que foram adicionados no WCF (também na versão 3.5) para expor serviços a partir do protocolo HTTP e acessíveis através dos métodos GET, PUT, POST, etc. Se desejar conhecer mais detalhes sobre o padrão REST no WCF, visite este endereço. O syndication não exige a criação do envelope SOAP, e justamente por isso que utiliza um formato mais simples de acesso, como o REST.

Depois de conhecer e saber como criar o serializador de um formato específico, precisamos saber como proceder para construir o feed. Ao contrário do formatador, a construção do feed nada sabe sobre qual formato ele será disponibilizado. A idéia aqui é criar os objetos de forma genérica, deixando o formatador se encarregar de como o feed será disponibilizado.

Uma das classes mais importantes neste processo é a SyndicationFeed. É através dela que iremos construir o feed, desde as informações de metadados até a coleção de itens que o mesmo irá disponibilizar. As informações de metadados consistem em quem são os autores, o título, categorias e a descrição do feed. Além dessas informações, temos ainda propriedades onde podemos definir a data de última atualização, idioma e a URL para possíveis imagens. O código abaixo ilustra como criar e configurar a classe que representará o feed:

using System;
using System.ServiceModel.Syndication;

public class BlogService : IBlog
{
    public SyndicationFeedFormatter GetPosts(string formato)
    {
        SyndicationFeed feed = new SyndicationFeed();
        feed.Title = new TextSyndicationContent("Blog do Israel Aece");
        feed.Authors.Add(
            new SyndicationPerson() 
            { 
                Email = "israel@projetando.net"
                , Name = "Israel Aece" 
            });
        feed.Description = new TextSyndicationContent("Blog Técnico");
        feed.Categories.Add(new SyndicationCategory("WCF"));
        feed.Categories.Add(new SyndicationCategory(".NET"));

        //resto da implementação
    }
}
Imports System
Imports System.ServiceModel.Syndication

Public Class BlogService
    Implements IBlog

    Public Function GetPosts(ByVal formato As String) As SyndicationFeedFormatter _
        Implements IBlog.GetPosts

        Dim feed As New SyndicationFeed()
        feed.Title = New TextSyndicationContent("Blog do Israel Aece")
        feed.Authors.Add( _
            New SyndicationPerson() With { _
                .Email = "israel@projetando.net", _
                .Name = "Israel Aece" _
            })
        feed.Description = New TextSyndicationContent("Blog Técnico")
        feed.Categories.Add(New SyndicationCategory("WCF"))
        feed.Categories.Add(New SyndicationCategory(".NET"))

        "resto da implementação
    End Function
End Class
C# VB.NET

Analisando o código acima nos deparamos com algumas novas classes que merecem ser abordadas. Seguindo a ordem de aparição, temos a classe TextSyndicationContent que representa qualquer conteúdo a ser exibido para o usuário final (mais detalhes sobre ela abaixo). Logo na sequência temos a classe SyndicationPerson que representa o autor do feed, e finalmente a classe SyndicationCategory que tem a finalidade de representar uma categoria de um feed ou de um item. É também importante notar que um feed pode conter um ou vários autores, bem como uma ou várias categorias e, justamente por isso, que a classe SyndicationFeed disponibiliza as coleções Authors e Categories.

Dando sequência no exemplo, a próxima classe a ser mencionada é a SyndicationItem. Cada instância desta classe representa um item (notícias, post, etc.) dentro do feed, mas a sua serialização será feita de forma diferente, dependendo de qual formatador é utilizado. Entre as principais propriedades temos o título (propriedade Title), um resumo da matéria (propriedade Summary) e o endereço até a mesma (propriedade Links). Além das propriedades, essas informações já podem ser definidas logo no construtor desta classe, conforme é mostrado no código abaixo:

using System;
using System.ServiceModel.Syndication;

public class BlogService : IBlog
{
    public SyndicationFeedFormatter GetPosts(string formato)
    {
        SyndicationFeed feed = new SyndicationFeed();
        feed.Title = new TextSyndicationContent("Blog do Israel Aece");
        feed.Authors.Add(
            new SyndicationPerson() 
            { 
                Email = "israel@projetando.net"
                , Name = "Israel Aece" 
            });
        feed.Description = new TextSyndicationContent("Blog Técnico");
        feed.Categories.Add(new SyndicationCategory("WCF"));
        feed.Categories.Add(new SyndicationCategory(".NET"));

        SyndicationItem item1 =
            new SyndicationItem(
                "Message Queue",
                "Descrição do Artigo de <b>Message Queue</b>",
                new Uri("http://www.projetando.net/Sections/ViewArticle.aspx?ArticleID=92"));

        SyndicationItem item2 =
            new SyndicationItem(
                "Transações",
                "Descrição do Artigo de <b>Transações</b>",
                new Uri("http://www.projetando.net/Sections/ViewArticle.aspx?ArticleID=91"));

        feed.Items = new SyndicationItem[] { item1, item2 };
    }
}
Imports System
Imports System.ServiceModel.Syndication

Public Class BlogService
    Implements IBlog

    Public Function GetPosts(ByVal formato As String) As SyndicationFeedFormatter _
        Implements IBlog.GetPosts

        Dim feed As New SyndicationFeed()
        feed.Title = New TextSyndicationContent("Blog do Israel Aece")
        feed.Authors.Add( _
            New SyndicationPerson() With { _
                .Email = "israel@projetando.net", _
                .Name = "Israel Aece" _
            })
        feed.Description = New TextSyndicationContent("Blog Técnico")
        feed.Categories.Add(New SyndicationCategory("WCF"))
        feed.Categories.Add(New SyndicationCategory(".NET"))

        Dim item1 As New SyndicationItem( _
            "Message Queue", _
            "Descrição do Artigo de <b>Message Queue</b>", _
            New Uri("http://www.projetando.net/Sections/ViewArticle.aspx?ArticleID=92"))

        Dim item2 As New SyndicationItem( _
            "Transações", _
            "Descrição do Artigo de <b>Transações</b>", _
            New Uri("http://www.projetando.net/Sections/ViewArticle.aspx?ArticleID=91"))

        feed.Items = New SyndicationItem() {item1, item2}
    End Function
End Class
C# VB.NET

Como exemplo, estamos definindo o conteúdo do feed a partir de itens estáticos mas, em uma aplicação real, isso deveria ser extraído de algum repositório, como por exemplo, o banco de dados. Independentemente da origem dos dados que irão compor o feed, cada item deve ser representado por uma instância da classe SyndicationItem e, depois de criado, adicionado de uma coleção temporária, como o List<SyndicationItem>. Isso é necessário pois a classe SyndicationFeed fornece uma propriedade chamada Items que recebe a coleção de itens do feed e esta, por sua vez, espera por uma coleção que implemente a Interface IEnumerable<SyndicationItem>.

Aqui vale uma pausa para comentarmos sobre as classes que encapsulam o conteúdo de um item. Os conteúdos são peças importantes durante a criação do feed e de seus respectivos itens. Os conteúdos não se referem somente ao texto da matéria, mas também trata-se do título, descrição, etc., e esses conteúdos podem ser um texto simples ou HTML. Para encapsular cada um desses diferentes tipos de conteúdos, foi criada uma classe abstrata chamada SyndicationContent e que serve como base para outras classes que lidam com um tipo de conteúdo específico. Para conhecer as classes atuais que representam um determinado tipo de conteúdo, vamos analisar a imagem abaixo:

Figura 2 - Hierarquia das classes de conteúdo.

A primeira delas, UrlSyndicationContent, representa uma URL para algum outro recurso ou até mesmo o endereço para o conteúdo publicado. Já a XmlSyndicationContent consiste em encapsular um conteúdo XML que não será visível ao usuário, e é geralmente utilizado para serializar algum objeto customizado. Finalmente temos a classe TextSyndicationContent, que representa um conteúdo a ser exibido para o usuário, e é capaz de exibir um conteúdo em texto simples ou códigos HTML. Um de seus construtores recebe uma string que representa o conteúdo e uma das opções do enumerador TextSyndicationContentKind que descreve o tipo do conteúdo. As opções fornecidas por esse enumerador são:

  • PlainText: Indica que o conteúdo deve ser texto puro, sem tags.

  • Html: Suporta tags HTML, mas elas serão codificadas.

  • XHtml: O conteúdo será serializado como XML, e as tags não serão codificadas.

Apesar de omitirmos a criação do TextSyndicationContent durante a criação do item (SyndicationItem), o construtor se encarrega de instanciar esta classe e definir o conteúdo com a string que é informada.

Para completar o código do método GetPosts ainda temos que definir o formatador. De acordo com a regra, temos que analisar o parâmetro formato que é passado para o método e, dependendo do seu valor, instanciar o serializador do RSS ou do ATOM. Ambos formatadores têm uma versão do construtor em que aceita uma instância da classe SyndicationFeed contendo as configurações do feed. Para deixar o código mais legível, um método chamado RecuperarFormatador foi criado para instanciar a classe concreta do serializador, de acordo com o parâmetro que é passado para a operação. O código abaixo ilustra essa condicional que determina o serializador:

private static SyndicationFeedFormatter RecuperarFormatador
(string formato, SyndicationFeed feed)
{
    if (formato == "rss")
        return new Rss20FeedFormatter(feed);

    return new Atom10FeedFormatter(feed);
}
Private Shared Function RecuperarFormatador(ByVal formato As String, _
                                            ByVal feed As SyndicationFeed) As SyndicationFeedFormatter
    If formato = "rss" Then Return New Rss20FeedFormatter(feed)

    Return New Atom10FeedFormatter(feed)
End Function
C# VB.NET

Hosting

A maioria dos casos onde se utiliza o recurso de syndication é em sites e, como eles são hospedados no IIS, provavelmente utilizaremos ele para servir como host para o serviço que construímos. Sendo assim, podemos adicionar no projeto do site que estamos construindo um item chamado WCF Service. Esse arquivo de extensão *.svc representa um serviço WCF e será ele que representará a classe do serviço, aquela que deve implementar a Interface que corresponde ao contrato. O Visual Studio .NET 2008 também disponibiliza uma template de projeto chamada WCF Service, onde podemos agrupar todos os serviços mas, se já existe o projeto do Web Site, então nada impede de criar dentro dele apenas o arquivo com extensão *.svc.

Como estamos utilizando recursos REST, então vamos expor o serviço a partir do binding WebHttpBinding para que seja possível acessar o feed a partir da URL, sem a necessidade do protocolo SOAP. Isso permitirá que alguns leitores, como é o caso do próprio Internet Explorer 7.0, interpretem o conteúdo XML no próprio navegador e o mostrem de forma amigável ao usuário que está querendo assiná-lo.

Ao utilizar o modelo baseado no arquivo *.svc, toda a configuração do endpoint deverá ser feita de forma declarativa, ou seja, através do arquivo Web.config. Neste tipo de serviço também não é necessária a criação de endpoints para expor os metadados (WSDL) do serviço, já que não faz sentido o usuário referenciá-lo. Para exemplificar a configuração do serviço, vamos analisar o código abaixo:

<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="BlogService">
        <endpoint address="" binding="webHttpBinding" contract="IBlog"/>
      </service>
    </services>
  </system.serviceModel>
</configuration>
Web.Config

O atributo name do elemento service obrigatoriamente deverá refletir o nome/tipo da classe que representa o serviço. Já o elemento endpoint fornece os atributos address, binding e contract que são autoexplicativos. Para maiores detalhes sobre hosting de serviços WCF, consulte este artigo.

Ao criar um arquivo do tipo WCF Service (*.svc), por padrão, ele possui uma diretiva chamada @ServiceHost que, entre seus atributos temos: Service e CodeBehind. O primeiro atributo reflete o tipo da classe que representa o serviço; já o atributo CodeBehind especifica o caminho físico até o arquivo do serviço. Isso é bem semelhante ao que acontece com o ASP.NET e os arquivos de code-behind. Além disso, ainda precisamos definir o atributo Factory que, quando omitido (que é o padrão), ele assume o tipo ServiceHostFactory (namespace System.ServiceModel.Activation) que, por sua vez, retornará instâncias da classe ServiceHost. Como estamos trabalhando com REST, então devemos alterar o valor do atributo Factory para System.ServiceModel.Activation.WebServiceHostFactory que retornará instâncias da classe WebServiceHost. Para nosso exemplo, a configuração deve ficar como:

<%@ ServiceHost 
        Language="C#" 
        Debug="true" 
        Service="BlogService" 
        Factory="System.ServiceModel.Activation.WebServiceHostFactory"
        CodeBehind="~/App_Code/BlogService.cs" %>
<%@ ServiceHost 
        Language="VB" 
        Debug="true" 
        Service="BlogService" 
        Factory="System.ServiceModel.Activation.WebServiceHostFactory"
        CodeBehind="~/App_Code/BlogService.vb" %>
C# [*.svc] VB.NET [*.svc]

Se executarmos a aplicação já teremos o conteúdo publicado pelo serviço à nossa disposição. Para acessar e visualizar o seu conteúdo não é necessária a criação de uma aplicação cliente, basta apenas acessar a URL diretamente no navegador que já conseguiremos visualizar o conteúdo. Para o exemplo deste artigo, o endereço é: "http://localhost:50569/Host/BlogService.svc/GetPosts?formato=atom", podendo variar o parâmetro formato para "rss", permitindo o usuário escolher o formato desejado simplesmente trocando o valor do parâmetro. A imagem abaixo ilustra o conteúdo interpretado pelo navegador:

Figura 3 - Conteúdo sendo exibido pelo navegador.

Como se pode perceber, temos que informar no endereço a extensão do arquivo *.svc e, em alguns casos, isso não é muito interessante. Se você desejar remover a extensão do arquivo do endereço do feed, você tem duas opções, dependendo de qual versão do IIS está utilizando. Caso esteja utilizando a versão 7.0, então você pode recorrer ao uso do Url Rewrite Module onde, através de expressões regulares, podemos customizar a URL de acesso ao serviço. Abaixo consta uma regra que podemos utilizar para remover a extensão *.svc do endereço:

<?xml version="1.0"?>
<configuration>
  <!-- outras configurações -->
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="RemoverExtensaoSvc" stopProcessing="true">
          <match url="^BlogService/(.*)$" />
          <action type="Rewrite" url="BlogService.svc/{R:1}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>
Web.Config

Com a regra acima aplicada ao diretório virtual, podemos acessar o serviço da seguinte forma: "http://localhost/Feeds/BlogService/GetPosts?formato=rss".

Para as versões anteriores do IIS, a saída para remover a extensão *.svc do endereço do serviço é escrever um módulo para manipular o endereço. Para isso, devemos criar uma classe e implementar a Interface IHttpModule, analisando no evento BeginRequest a URL solicitada e, caso seja a solicitação para um arquivo *.svc, devemos sobrescrever a requisição através do método RewritePath da classe HttpContext.

Conclusão: O artigo descreveu as principais classes que são fornecidas pelo WCF para a criação de syndication, exibindo as classes que são utilizadas para compor o feed, bem com as classes necessárias para a serialização do mesmo. Para finalizar, a idéia do syndication a partir do WCF é apenas uma alternativa para a criação dos feeds e não um substituto dos padrões ATOM ou RSS.
Israel Aéce

Israel Aéce - Especialista em tecnologias de desenvolvimento Microsoft, atua como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. Como instrutor Microsoft, leciona sobre o desenvolvimento de aplicações .NET. É palestrante em diversos eventos Microsoft no Brasil e autor de diversos artigos que podem ser lidos a partir de seu site http://www.israelaece.com/. Possui as seguintes credenciais: MVP (Connected System Developer), MCP, MCAD, MCTS (Web, Windows, Distributed, ASP.NET 3.5, ADO.NET 3.5, Windows Forms 3.5 e WCF), MCPD (Web, Windows, Enterprise, ASP.NET 3.5 e Windows 3.5) e MCT.