Click here to Skip to main content
Click here to Skip to main content

Custom ASP.NET MVC ActionResults

By , 25 Jan 2013
 

Download Source Code
 

 

Introduction   

In this article we shall see how to build or implement custom ActionResults. One has to go with custom ActionResults if we do not find an existing ActionResult. Take an example of a Controller ActionResult returning a xml data , Rss feeds, Excel data. Here comes the necessity to build one.  Depending upon the requirement one has to go with custom implementation. Here we will see with an example how to return a Xml , Rss feeds from controller actions. 

Background  

Basic understanding and experience in developing a web based application with ASP.NET MVC.

Here's a beginner level article to understand ActionResult - http://www.codeproject.com/Articles/195434/Actions-in-ASP-NET-MVC-3

Using the code 

Custom ActionResult to return Xml data

The below custom ActionResult (XmlActionResult) is used to return Xml response. One has to extend ActionResult class and override the ExecuteResult. You can see in the ExecuteResult method , we have written code to create a new Xml document by parsing the content , set the encoding and get bytes to return.

Also notice the default encoding is "utf-8" , one can also set the encoding so has to return the proper response.

 public enum EncodingType
    {
        UTF8,
        UTF16,
        UTF32
    }

    public class XmlActionResult : ActionResult
    {
        public XmlActionResult(string xml, string fileName, EncodingType encoding = EncodingType.UTF8 , LoadOptions loadOptions = System.Xml.Linq.LoadOptions.None)
        {
            XmlContent = xml;
            FileName = fileName;
            Encoding = encoding;
            LoadOptions = loadOptions;
        }

        public string FileName
        {
            get;
            set;
        }

        public string XmlContent
        {
            get;
            set;
        }

        public EncodingType Encoding
        {
            get;
            set;
        }

        public LoadOptions LoadOptions
        {
            get;
            set;
        }

        public XmlDocument ToXmlDocument(XDocument xdoc)
        {
            var xmldoc = new XmlDocument();
            xmldoc.Load(xdoc.CreateReader());
            return xmldoc;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            XDocument doc = XDocument.Parse(XmlContent, this.LoadOptions);
            context.HttpContext.Response.ContentType = "text/xml"; context.HttpContext.Response.AddHeader("content-disposition", string.Format("attachment; filename={0}", FileName));
           
            XmlDocument xmldoc = ToXmlDocument(doc);
            // Create an XML declaration. 
            XmlDeclaration xmldecl;
            xmldecl = xmldoc.CreateXmlDeclaration("1.0", null, null);
          
            switch (Encoding)
            {
                case EncodingType.UTF8:
                    doc.Declaration = new XDeclaration("1.0", "utf-8", null);
                    context.HttpContext.Response.Charset = "utf-8";                  
                    xmldecl.Encoding = "UTF-8";
                    XmlElement root = xmldoc.DocumentElement;
                    xmldoc.InsertBefore(xmldecl, root); 
                    context.HttpContext.Response.BinaryWrite(System.Text.UTF8Encoding.Default.GetBytes(xmldoc.OuterXml));           
                    break;
                case EncodingType.UTF16:
                    doc.Declaration = new XDeclaration("1.0", "utf-16", null);
                    context.HttpContext.Response.Charset = "utf-16";
                    xmldecl.Encoding = "UTF-16";
                    root = xmldoc.DocumentElement;
                    xmldoc.InsertBefore(xmldecl, root); 
                    context.HttpContext.Response.BinaryWrite(System.Text.UnicodeEncoding.Default.GetBytes(xmldoc.OuterXml));
                    break;
                case EncodingType.UTF32:
                    doc.Declaration = new XDeclaration("1.0", "utf-32", null);
                    context.HttpContext.Response.Charset = "utf-32";
                    xmldecl.Encoding = "UTF-32";
                    root = xmldoc.DocumentElement;
                    xmldoc.InsertBefore(xmldecl, root);
                    context.HttpContext.Response.BinaryWrite(System.Text.UTF32Encoding.Default.GetBytes(xmldoc.OuterXml));
                    break;
            }          
           
            context.HttpContext.Response.End();
        }
    }

Below is the Controller ActionResult implementation

    [HttpGet]
    public XmlActionResult GetXmlData()
    {
        System.Xml.XmlTextReader reader = new System.Xml.XmlTextReader(Server.MapPath("~/Book.xml"));
        var xml = XElement.Load(reader);
        return new XmlActionResult(xml.ToString(), "book.xml");
    }

This is just to demostrate the xml response, The GetXmlData() method just reads the xml file Book.xml then loads the result as XML Document and returns the response as XmlActionResult.

Custom ActionResult to return RSS Feeds

 

Below is the implementation of a custom feeds ActionResult. The Syndication feeds can be of two types , Atom or Rss. Depending upon on your needs one can return either of the feed as a response. You can also see below , we are using the formatters to format the data and present it to the client. If you see out output of the FeedActionResult , it's also an xml response. You may ask a question why we have not used the XmlActionResult. However it's always good to separate these implementations and this class is designed in keeping in mind of Single Responsiblilty Principle. A class or a module should have one, and only one, reason to change.

You can notice the content type , it is "application/rss+xml"

    public enum FeedType
    {
        Atom,
        Rss
    }
 
    public class FeedActionResult : ActionResult
    {
        private SyndicationFeedFormatter formatter;
 
        public FeedActionResult(SyndicationFeed feed, FeedType feedType)
        {
            switch (feedType)
            {
                case FeedType.Atom:
                    formatter = new Atom10FeedFormatter(feed);
                    break;
                case FeedType.Rss:
                    formatter = new Rss20FeedFormatter(feed);
                    break;
            }
        }
 
        public SyndicationFeed Feed { get; set; }
 
        public override void ExecuteResult(ControllerContext context)
        {
            context.HttpContext.Response.ContentType = "application/rss+xml";
            using (var writer = XmlWriter.Create(context.HttpContext.Response.Output))
            {
                formatter.WriteTo(writer);
            }
        }
    }

Below is the Controller ActionResult implementation which returns the RSS feed.

    [HttpGet]
    public ActionResult GetRssFeeds()
    {
         XmlReader reader = XmlReader.Create("http://www.codeproject.com/webservices/articlerss.aspx",
         new XmlReaderSettings()
         {
             //MaxCharactersInDocument can be used to control the maximum amount of data 
             //read from the reader and helps prevent OutOfMemoryException
             MaxCharactersInDocument = 1024 * 64
         });
 
         SyndicationFeed feed = SyndicationFeed.Load(reader);         
         var rssFeed = new FeedActionResult(feed, FeedType.Rss);
         return rssFeed;
    }

In order to demonstrate the usage of RssActionResult , we have created a Controller Action method: GetRssFeeds() , it reads the rss feeds published by CodeProject , loads the same as SyndicationFeed and returns the feeds as a response.

Points of Interest

It is fun in coding the custom ActionResults. Lot of things I learnt while building these custom ActionResults and I hope it should help others too.

Here is an interesting link if you are looking to return a excel data as ActionResult - http://www.dotnetcurry.com/ShowArticle.aspx?ID=484  

History 

Version 1.0  - 1/24/2013 - First release with source code. Implemented custom ActionResults

Version 1.1 - 1/25/2013  - Updated XmlActionResult to support for multiple encoding types 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Ranjan.D
Web Developer
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionUTF-8 EncodingmemberPhilippe Mori24 Jan '13 - 12:37 
Could you explain why you are using ASCII encoding in the following line:
context.HttpContext.Response.BinaryWrite(System.Text.ASCIIEncoding.Default.GetBytes(doc.ToString()));
I would have though that UTF-8 encoding should have been used to get the bytes...
Philippe Mori

AnswerRe: UTF-8 Encoding [modified]memberRanjan.D24 Jan '13 - 12:52 
Hi,
 
It was my mistake, I thought ASCIIEncoding can be used for all encoding types, However it won't support all the required encoding types as below.
 
Thanks for correcting me. I just updated the article to support for multiple encoding for XmlActionResult.
 
UTF-8, which represents each code point as a sequence of one to four bytes.
UTF-16, which represents each code point as a sequence of one to two 16-bit integers.
UTF-32, which represents each code point as a 32-bit integer.
 
Thanks,
Ranjan.D


modified 25 Jan '13 - 9:12.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 25 Jan 2013
Article Copyright 2013 by Ranjan.D
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid