How to improve my solution for Rss/Atom using SyndicationFeed with ServiceStack?

1.2k views Asked by At

I successfully used System.ServiceModel.Syndication.SyndicationFeed to add some Atom10 output from my ASP.NET 3.5 web site. It was my first production use of ServiceStack, and it all work fine.

My first attempt resulted in UTF-16 instead of UTF-8, which was ok for all browsers except IE. So I had to create XmlWriterResult class to solve this. My solution works, but how should I have done?

public class AsStringService : IService<AsString>
{
    public object Execute(AsString request)
    {
        SyndicationFeed feed = new SyndicationFeed("Test Feed " + request.Name, "This is a test feed", new Uri("http://Contoso/testfeed"), "TestFeedID", DateTime.Now);
        SyndicationItem item = new SyndicationItem("Test Item", "This is the content for Test Item", new Uri("http://localhost/ItemOne"), "TestItemID", DateTime.Now);

        List<SyndicationItem> items = new List<SyndicationItem>();
        items.Add(item);
        feed.Items = items;
        Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(feed);

        return new XmlWriterResult(xmlWriter => atomFormatter.WriteTo(xmlWriter));
    }
}

XmlWriterResult is:

public delegate void XmlWriterDelegate(XmlWriter xmlWriter);

/// <summary>
/// From https://groups.google.com/forum/?fromgroups=#!topic/servicestack/1U02g7kViRs
/// </summary>
public class XmlWriterResult : IDisposable, IStreamWriter, IHasOptions
{
     private readonly XmlWriterDelegate _writesToXmlWriter;

     public XmlWriterResult(XmlWriterDelegate writesToXmlWriter)
     {
         _writesToXmlWriter = writesToXmlWriter;
         this.Options = new Dictionary<string, string> {
              { HttpHeaders.ContentType, "text/xml" }
         };
     }

     public void Dispose()
     {
     }

     public void WriteTo(Stream responseStream)
     {
         using (XmlWriter xmlWriter = XmlWriter.Create(responseStream))
         {
             _writesToXmlWriter(xmlWriter);
         }
     }

     public IDictionary<string, string> Options { get; set; }
}

(Yes, I like delegates, I also do a lot of F#)

1

There are 1 answers

4
mythz On BEST ANSWER

As this isn't a question with any clear answer I'd just tell you how I'd do it.

Assuming SyndicationFeed is a clean DTO / POCO you should just return that in your service:

public class AsStringService : IService
{
    public object Any(AsString request)
    {
        SyndicationFeed feed = new SyndicationFeed("Test Feed " + request.Name, 
           "This is a test feed", new Uri("http://Contoso/testfeed"), 
           "TestFeedID", DateTime.Now);
        SyndicationItem item = new SyndicationItem("Test Item", 
           "This is the content for Test Item", 
            new Uri("http://localhost/ItemOne"), 
           "TestItemID", DateTime.Now);

        List<SyndicationItem> items = new List<SyndicationItem>();
        items.Add(item);
        feed.Items = items;

        return feed;
    }
}

This example uses ServiceStack's New API which is much nicer, you should try using it for future services.

This will allow you to get Content Negotiation in all of ServiceStack's registered Content-Types.

Registering a Custom Media Type

You could then register a Custom Media Type as seen in ServiceStack's Northwind v-card example:

private const string AtomContentType = "application/rss+xml";

public static void Register(IAppHost appHost)
{
    appHost.ContentTypeFilters.Register(AtomContentType, SerializeToStream, 
        DeserializeFromStream);
}

public static void SerializeToStream(IRequestContext requestContext, 
    object response, Stream stream)
{
    var syndicationFeed = response as SyndicationFeed;
        if (SyndicationFeed == null) return;

    using (XmlWriter xmlWriter = XmlWriter.Create(stream))
    {
            Atom10FeedFormatter atomFormatter = new Atom10FeedFormatter(feed);
            atomFormatter.WriteTo(xmlWriter);
    }
}

public static object DeserializeFromStream(Type type, Stream stream)
{
    throw new NotImplementedException();
}

Now the rss+xml format should appear in the /metadata pages and ServiceStack will let you request the format in all the supported Content-Negotation modes, e.g:

  • Accept: application/rss+xml HTTP Header
  • Using the predefined routes, e.g: /rss+xml/syncreply/AsString
  • Or appending /route?format=rss+xml to the QueryString

Note: you may need to url encode the '+' character