[ Log On ]

FeedResult - Kreiranje custom ActionResult-a

Posted by frennky on 7/13/2011 @ 11:49 PM in Web Development

U ovom postu biće prikazano kako može da se napravi custom ActionResult koji vraća feed kao odgovor na zahteva browser-a.

ActionResult

Svaka akcija kontrolera vraća objekat koji proizilazi iz apstraktne ActionResult klase. ASP.NET MVC sadrži nekoliko klasa koje nasleđuju ActionResult:

  • ContentResult
  • EmptyResult
  • FileResult (apstraktna)
    • FileContentResult
    • FilePathResult
    • FileStreamResult
  • HttpUnauthorizedResult
  • JavaScriptResult
  • JsonResult
  • RedirectResult
  • RedirectToRouteResult
  • ViewResultBase (apstraktna)
    • PartialViewResult
    • ViewResult

ActionResult klasa sadrži samo jednu metodu, tako da ako pravimo custom ActionResult potrebno je da preklopimo samo ExecuteResult metodu. Iako, u našem primeru možemo direktno da nasledimo ActionResult klasu, mi ćemo naslediti FileResult.

S obzirom da je MVC open source projekat, evo i source-a ove dve klase:

    public abstract class ActionResult {
        public abstract void ExecuteResult(ControllerContext context);
    }

     public abstract class FileResult : ActionResult {

        protected FileResult(string contentType) {
            if (String.IsNullOrEmpty(contentType)) {
                throw new ArgumentException(MvcResources.Common_NullOrEmpty, "contentType");
            }

            ContentType = contentType;
        }

        private string _fileDownloadName;

        public string ContentType {
            get;
            private set;
        }

        public string FileDownloadName {
            get {
                return _fileDownloadName ?? String.Empty;
            }
            set {
                _fileDownloadName = value;
            }
        }

        public override void ExecuteResult(ControllerContext context) {
            if (context == null) {
                throw new ArgumentNullException("context");
            }

            HttpResponseBase response = context.HttpContext.Response;
            response.ContentType = ContentType;

            if (!String.IsNullOrEmpty(FileDownloadName)) {
                // From RFC 2183, Sec. 2.3:
                // The sender may want to suggest a filename to be used if the entity is
                // detached and stored in a separate file. If the receiving MUA writes
                // the entity to a file, the suggested filename should be used as a
                // basis for the actual filename, where possible.
                ContentDisposition disposition = new ContentDisposition() { FileName = FileDownloadName };
                string headerValue = disposition.ToString();
                context.HttpContext.Response.AddHeader("Content-Disposition", headerValue);
            }

            WriteFile(response);
        }

        protected abstract void WriteFile(HttpResponseBase response);

    }

Kako bi browser shvatio da se radi o feed-u, potrebno je da u response-u dodamo header Content-Disposition sa vrednošću application/rss+xml. Ovo je ujedno i razlog zašto ćemo naslediti FileResult umesto ActionResult.

Atom i RSS

Atom i Rss predstavljaju dijalekte XML-a, ali na svu sreću nije potrebno da se bavimo parsiranjem XML-a. U .NET-u postoji skup klasa koje su namenjen za rad sa strukturama podataka kakve su Atom i Rss feed-ovi. U prostoru imena System.ServiceModel.Syndication nalazi se nekoliko klasa:

  • SyndicationFeed
  • SyndicationItem
  • SyndicationContent
  • itd.

Implementacija

Uzećemo za primer blog aplikaciju, odnosno Post klasu kao model za koji ćemo generisati feed:

public class Post
{
    public int PostId { get; set }
    public string Title { get; set; }
    public string Body { get; set; }
    public string Permalink { get; set; }
    public DateTime Posted { get; set; }
}

FeedController je krajnje jednostavan i mislim da nema potrebe detaljnije objašnjavati šta radi:

public class FeedController : Controller
{
	private BlogContext db = new BlogContext();
	
	public ActionResult Atom()
	{
		var posts = db.Posts.All();
		return FeedResult(posts, FeedFormat.Atom10);
	}
	
	public ActionResult Rss()
	{
		var posts = db.Posts.All();
		return FeedResult(posts, FeedFormat.Rss20);
	}	
}

FeedResult izgledaće ovako:

    public class FeedResult : FileResult
    {
        private IEnumerable items;
        private FeedFormat feedFormat;
        private Uri currentUrl;
        public SyndicationFeed Feed { get; set; }

        public FeedResult(IEnumerable items, FeedFormat format)
            : base("application/rss+xml")
        {
            this.items = items;
            this.feedFormat = format;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            this.currentUrl = context.RequestContext.HttpContext.Request.Url;
            base.ExecuteResult(context);
        }

        protected override void WriteFile(HttpResponseBase response)
        {
            Feed = new SyndicationFeed("Feed title", "Feed description", this.currentUrl, GetSyndicationItems());            
            Feed.Authors.Add(new SyndicationPerson("my@email.com", "My Name", "MyBlogUrl"));            
            Save(XmlWriter.Create(response.Output));
        }

        private void Save(XmlWriter writer)
        {
            switch (this.feedFormat)
            {
                case FeedFormat.Atom10:
                    Feed.SaveAsAtom10(writer);
                    break;
                case FeedFormat.Rss20:
                    Feed.SaveAsRss20(writer);
                    break;
                default:
                    Feed.SaveAsRss20(writer);
                    break;
            }
            writer.Close();
        }

        private List GetSyndicationItems()
        {
            var items = new List();
            foreach (var post in this.items)
            {
                var item = new SyndicationItem
                {
                    Title = SyndicationContent.CreatePlaintextContent(post.Title),
                    Content = SyndicationContent.CreateHtmlContent(post.Body),
                    Id = post.PostId.ToString(),
                    PublishDate = post.Posted,
                    LastUpdatedTime = post.Posted
                };
                item.AddPermalink(new Uri(post.Permalink));
                items.Add(item);
            }
            return items;
        }
    }

    public enum FeedFormat
    {
        Atom10,
        Rss20
    }

Mislim da kod FeedResult-a dovoljno sam sebe objašnjava, treba samo obratiti pažnju na dve metode koje su preklopljne: WriteFile i ExecuteResult. ExecuteResult je metoda nasleđena od ActionResult-a i nju okida ControllerActionInvoker. WriteFile je nasleđena on FileResult klase i ona je odgovorna za pisanje sadržaja fajla u response. U slučaju nekog fajla to bi bio niz bajtova, a u našem slučaju to je sadržaj XML fajla (Atom ili RSS feed).