File download issue with Sitecore MVC and resolution


The problem:

while working on the requirement of downloading calendar item(.ics) for an event on the site, I struck with the below given error.

OutputStream is not available when a custom TextWriter is used.

OutputStream is not available when a custom TextWriter is used.
Sitecore MVC error for File as ActionResult for post action

 

Steps to reproduce issue/problem

  1. Using Sitecore MVC form to do the form posting
 @using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, FormMethod.Post))
 {
     @Html.Sitecore().FormHandler("SeminarWebinarEvent", "DownloadCalendar")
     @Html.Hidden("eventId", @eventObj.Id.ToString())
     <button type="submit">Add to Calender</button>
 }
  1. Controller Post Action
 [HttpPost]
 public ActionResult DownloadCalendar(string eventId)
 {
     var eventItem = Sitecore.Context.Database.GetItem(new ID(eventId));
     if (eventItem == null)
     {
         //// TODO : Put the code for the error handling
     }

     //// get file stream
     var fileStream = this.eventService.GetCalendarFileStream(eventItem);
     return File(fileStream , "text/calendar", string.Format("{0}.ics", eventItem.Name));
 }
  1. Finally, Click on the Download link of the rendering on the page.

Root Cause

For the fix and the root cause I took the help of the God (not the real almighty but Google ;-)). I found that because of the basic page architecture of Sitecore, the page rendering has already started before the rendering control action returns the file response.

Solution :

After applying couple of different solutions(every time with fingers crossed), I found below given solution much handy and does the job. The solution is much simple and easy to implement.
In this case we need to split the whole action into two different actions like shown in below steps.

  1. Step 1

Put the logic of creating file stream in one action

 [HttpPost]
 public ActionResult DownloadCalendar(string eventId)
     {
          var eventItem = Sitecore.Context.Database.GetItem(new ID(eventId));
          if (eventItem == null)
          {
              //// TODO : Put the code for the error handling
          }

          //// read the file stream in the string format
          var fileStream = this.eventService.GetCalendarFileStream(eventItem);
          //// due to limitation of sitecore MVC redirect to the different action which is responsible for actual download
          return RedirectToAction("ActualCalenderDownload", new { fileString = fileStream, fileName = eventItem["Name"] });
        }
  1. Step 2

At the end of the action redirect to another action which is actually responsible for downloading the file.

  1. Step 3

Create the new action which takes the file stream from above action and returns the fileResult

 [HttpGet]
 public ActionResult ActualCalenderDownload(string fileString, string fileName)
 {
      if (string.IsNullOrWhiteSpace(fileString))
      {
          //// TODO : Put the code for the error handling
      }

      return File(Encoding.UTF8.GetBytes(fileString), "text/calendar", string.Format("{0}.ics", fileName));
 }

Happy Coding 🙂

Sitecore controller rendering action results – what can I return?

Advertisements