Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RSS and Atom ASP.NET Core ActionResult classes #2218

Closed
RehanSaeed opened this issue Aug 22, 2017 · 5 comments
Closed

RSS and Atom ASP.NET Core ActionResult classes #2218

RehanSaeed opened this issue Aug 22, 2017 · 5 comments
Assignees

Comments

@RehanSaeed
Copy link

RehanSaeed commented Aug 22, 2017

It is fairly common to want to return an RSS or Atom feed from an ASP.NET Core application. It would be nice to include an ActionResult class that wraps the Rss20FeedWriter or AtomFeedWriter. These classes could be included in another assembly so it's opt-in.

Unfortunately, the Rss20FeedWriter class as it is now is not easily wrappable, this is the best I could do below. Perhaps the answer is that the new feed writer is simple enough it does not need an ActionResult. I used to to this with the old SyndicationFeed object.

public class RssActionResult : ActionResult
{
    private const string RssMimeType = "application/rss+xml";
    private readonly Func<XmlWriter, Task<Rss20FeedWriter>> getWriter;

    public RssActionResult(Func<XmlWriter, Task<Rss20FeedWriter>> getWriter) =>
        this.getWriter = getWriter;

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        context.HttpContext.Response.ContentType = RssMimeType;
        var xmlWriterSettings = new XmlWriterSettings()
        {
            Async = true,
            Encoding = Encoding.UTF8
        };
        using (var xmlWriter = XmlWriter.Create(context.HttpContext.Response.Body, xmlWriterSettings))
        {
            var feedWriter = await this.getWriter(xmlWriter);
            xmlWriter.Flush();
        }
    }
}
@zhenlan
Copy link
Member

zhenlan commented Sep 5, 2017

@drago-draganov thoughts?

@zhenlan zhenlan added this to the Future milestone Sep 5, 2017
@drago-draganov
Copy link

@RehanSaeed
Appreciate the cool ideas!

Here is my take on it:

The difference between forward-only writer and document oriented is that the first one doesn't have understanding of state and intrinsic data. That allows it to be very lightweight at almost every aspect.

MVC ActionResult primary focus is on passing along already established data (content, status, type, etc.). An MVC Controller action is the proper place for business logic to establish that data (model).
ActionResult for ServiceModel.SyndicationFeed make sense, since the data is already part of the feed. But passing along Microsoft.SyndicationFeedWriter is not the same, since no content is defined with it and the writer is not actually a data model. Not saying that it's bad idea, but in my opinion it's an overstretch to what ActionResult is for.

FileResult perhaps is a close analogy of the topic here. Note that once returned it already knows all the information needed (file path) to proceed. It augments only the details of how to put the entire file content on the wire. Microsoft.SyndicationFeedWriter in contrast still requires all the feed data: title, description, links, items, etc. to be placed - this is where most of the code goes anyway.

Here is pretty much what raw code (without special RssActionResult) could look like when using SyndicationFeedWriter in ASP.NET MVC. It seems simple already.

[Produces("application/rss+xml")]
public async Task GetRssFeed() {
    using (var xmlWriter = CreateXmlWriter())
    {
        var feedWriter = new Rss20FeedWriter(xmlWriter);

        await writer.WriteTitle("Rss Feed Title");
        await writer.WriteDescription("Description of the Feed");
    
        await feedWriter.Flush();
    }
});

private XmlWriter CreateXmlWriter() 
{
     return XmlWriter.Create(Response.Body, new XmlWriterSettings() {
         Async = true,
         Encoding = Encoding.UTF8
     });
}

@RehanSaeed
Copy link
Author

Agreed, the stream model does not work well with ActionResult.

@ToddThomson
Copy link

@RehanSaeed You could always create your own SyndicationFeed model. Your MVC action would then pass the model to your RssResult. The RssResult class would then write the model to the Response.

    public class RssResult : ActionResult
    {
        #region Properties/Fields

        public SyndicationFeed RssFeed { get; private set; }

        #endregion

        #region Constructor

        public RssResult( SyndicationFeed rssFeed )
        {
            RssFeed = rssFeed;
        }

        #endregion

        #region Public Methods

        public async override Task ExecuteResultAsync( ActionContext context )
        {
            context.HttpContext.Response.ContentType = "application/rss+xml";

            using ( var xmlWriter = CreateXmlWriter( context.HttpContext.Response ) )
            {
                var feedWriter = new RssFeedWriter( xmlWriter );

                await feedWriter.WriteTitle( RssFeed.Title );
                await feedWriter.WriteDescription( RssFeed.Description );
                await feedWriter.WriteCopyright( RssFeed.Copyright );
                await feedWriter.WriteLanguage( new System.Globalization.CultureInfo( RssFeed.Language ) );
                await feedWriter.Write( new SyndicationLink( RssFeed.BaseUri ) );

                // Add Items
                foreach ( var item in RssFeed.Items )
                {
                    await feedWriter.Write( item );
                }

                await feedWriter.Flush();
            }
        }

        #endregion

        #region Private Methods

        private XmlWriter CreateXmlWriter( HttpResponse response )
        {
            return XmlWriter.Create( response.Body, new XmlWriterSettings()
            {
                Async = true,
                Encoding = Encoding.UTF8
            } );
        }

        #endregion
    }

@BryanWilhite
Copy link

@ToddThomson indeed I did make a simple SyndicationFeed model and you are very helpful, showing me how to use it above. However, what would one do when content negotiation comes into the picture? What would one do when we want to produce Atom or RSS output from a single endpoint?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants