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.
Steps to reproduce issue/problem
- 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> }
- 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)); }
- 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.
- 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"] }); }
- Step 2
At the end of the action redirect to another action which is actually responsible for downloading the file.
- 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?