2

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#)

Dan Lowe
  • 51,713
  • 20
  • 123
  • 112
mattias
  • 870
  • 5
  • 18
  • 2
    This question seems a little open ended, I am not exactly sure what your question is. You could make sure you specify the encoding attribute in the xml declaration of the feed, and you could use the Accept-Charset header of an incoming request to write the feed using the requested encoding. – Oppositional Oct 22 '12 at 19:38
  • Do I need the XmlWriterResult class, or can I use atomFormatter.WriteTo and write to the response stream directly? – mattias Oct 22 '12 at 19:58
  • The feed formatter writes the SyndicationFeed abstraction, but does not write out the XML declaration. You would create an XML writer, write out the declaration, then use the feed formatter to write the content of the feed. – Oppositional Oct 22 '12 at 23:34

1 Answers1

2

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

mythz
  • 141,670
  • 29
  • 246
  • 390
  • Thank you mythz for the answer, but I do not really see the benefit of your proposal, since your custom media type is only for Atom10FeedFormatter, and it will not scale to RSS20FeedFormatter, http://bit.ly/ResU8B I was more hoping for an answer like, "You do not need XmlWriteResult at all, just return your object as an XXXXX" or similar, I wanted fewer lines of code. – mattias Oct 28 '12 at 17:46
  • The benefit is that you still get Content-Negotation, i.e. being able to view your service in XML, JSON, HTML, ETC. Whereas serializing to XML inside your service means it will only ever return the serialized XML. – mythz Oct 28 '12 at 18:57
  • Ok, then I understand. I really appreciate your answers. – mattias Oct 30 '12 at 08:48
  • Any suggestions on how this would be ported to the Rockstars example would be most appreciated. In that case, there is an existing service with various routing paths and returns RockstarsResponse - not just returning SyndicationFeed from Any. One approach would be to have all responses inherit from a SyndicatableResponse and catch it in the formatter, but that doesn't seem best. Ideally each type (rockstar, customer, etc) would be able to inject into the SyndicationFeed Items their own extra fields that are needed for Atom/Rss in the appropriate Get() paths. Thanks! – sirthomas Jun 13 '13 at 16:26