Exposer un flux RSS/Atom avec Web API 1/2

Tout blog ou site d’actualité moderne doit disposer d’un flux RSS ou Atom. Ce n’est pas moi qui le dis, c’est un fait. Ce blog n’échappant pas à la règle, j’ai travaillé sur la réalisation d’un tel flux avec la Web API.

De prime abord, la Web API est plutôt bien dotée pour ce genre de travail et semble tout indiquée. Mais dans les faits, les choses sont plus complexes. J’ai donc séparé cet article en deux parties :

  1. La mise ne place simple.
  2. Les petits ajustements et/ou concessions à faire.

Libre à chacun de voir ensuivre s’il adhère aux solutions proposées.

Attaquons donc la mise en place de ce flux et son exposition via la Web API.

Pour ne pas avoir à parcourir les normes RSS et Atom, j’ai choisi d’utiliser la classe SyndicationFeed qui se trouve dans le namespace System.ServiceModel.Syndication.SyndicationFeed peut de plus être utilisé avec deux classes dédiées au formatage du XML : Atom10FeedFormatter et Rss20FeedFormatter. Ce qui simplifiera grandement le travail.

Si vous utilisez .net Core, vous l’aurez déjà compris, le code qui suit ne vous est d’aucune utilité car ce namespace ne vous est pas accessible (on ne gagne pas à tous les coups).

La première tâche consiste donc à produire une instance de ce fameux SyndicationFeed. Pour faire cela, j’ai codé une méthode GetPost qui a vocation à utiliser ma base de données et à en extraire les dix derniers articles (le code n’étant pas le sujet de cet article, la méthode est décrite à titre indicatif). Ceci permet d’avoir un code simple et concis lors de la création des items du SyndicationFeed.

public sealed class FeedService
{
    /// <summary>
    /// Get the blog feed
    /// </summary>
    /// <returns></returns>
    public static SyndicationFeed Get()
    {
        SyndicationFeed feed = new SyndicationFeed(
        Settings.Current.Title,
        Settings.Current.SubTitle,
        new Uri(Settings.Current.Url));

        using (var db = new DataService())
        {
            var posts = db.GetPosts(0, 10);
            if (posts != null && posts.Length > 0)
            {
                // Create a new list of items
                var items = posts.Select(c =>
                    new SyndicationItem(
                        c.Title,
                        c.HtmlSummary,
                        new Uri(c.Url),
                        c.Id.ToString(),
                        c.DateCreatedGmt)
                    ).ToArray();
                // Add items
                feed.Items = items;
            }
        }
        return feed;
    }
}
Une fois ce type de méthode disponible, on peut se concentrer sur la Web API. Pour cette partie, j’ai décidé de rester dans la simplicité :
  1. Un Controller expose une méthode Get qui retourne une instance de SyndicationFeed
  2. Un Formatter se charge de transformer les SyndicationFeed en messages formatés en ATOM ou RSS en fonction du besoin.


Côté Controller, le code est des plus simples, étant donné que la production du flux est dévolue à la méthode présentée plus haut :

public class _FeedController : ApiController
{
    public SyndicationFeed Get()
    {
        return FeedService.Get();
    }
}


Côté Formatter le code reste lui aussi très simple :

  1. On introduit la liste des requêtes pour lesquelles le formater est utile via le constructeur.
  2. On formate le SyndicationFeed en Atom ou RSS en fonction de la demande.
public class SyndicationFeedFormatter : BufferedMediaTypeFormatter
{
    private readonly string atom = "application/atom+xml";
    private readonly string rss = "application/rss+xml";

    public SyndicationFeedFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue(atom));
        SupportedMediaTypes.Add(new MediaTypeHeaderValue(rss));
    }

    public override bool CanReadType(Type type)
    {
        return false;
    }

    public override bool CanWriteType(Type type)
    {
        Boolean result = type == typeof(SyndicationFeed);
        return result;
    }

    public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
    {
        var feed = value as SyndicationFeed;
        var settings = new XmlWriterSettings
        {
            Encoding = new UTF8Encoding(false)
        };

        using (XmlWriter writer = XmlWriter.Create(writeStream, settings))
        {
            if (string.Equals(content.Headers.ContentType, atom))
            {
                Atom10FeedFormatter atomformatter = new Atom10FeedFormatter(feed);
                atomformatter.WriteTo(writer);
            }
            else
            {
                Rss20FeedFormatter rssformatter = new Rss20FeedFormatter(feed);
                rssformatter.WriteTo(writer);
            }
        }
    }
}


Reste alors à ajouter ce Formatter à la configuration de la Web API.

public class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services

        // Web API routes
        config.MapHttpAttributeRoutes();

        // Add the SyndicationFeedFormatter for RSS/ATOM
        config.Formatters.Add(new MyLib.Web.WebApi.SyndicationFeedFormatter());

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}


On est alors paré pour répondre aux demandes de flux Atom et RSS émanant de l’extérieur.

Encore faut-il que nos clients soient bien élevés. Mais il s’agit là du sujet de mon prochain article, je n’en dirai donc pas plus pour le moment ;)

Jérémy Jeanson

Comments

You have to be logged in to comment this post.