Last week I was working on some kind of lightweight downloadhandler where i could pass some information from one page to the downloadhandler by using the HttpContext.Items collection.
To be able to use the HttpContext.Items collection on the downloadhandler, I have to use the HttpServerUtility.Transfer() method (details can found at MSDN), which sends the HttpContext to the page we’re transferring to. When looking at MSDN, I found out that there is an overload for HttpServerUtility.Transfer(IHttpHandler, Boolean) which takes IHttpHandler and a Boolean as parameters.
Nice…. at least that’s what I thought then. There wasn’t that many documentation about using this overload, so I gave it a try. First I created my DownloadHandler as below:
public class DownloadHandler : IHttpHandler { public bool IsReusable { get { return false; } } public void ProcessRequest(HttpContext context) { var content = context.Items["Content"].ToString(); var fileType = context.Items["FileType"].ToString(); var fileName = "CSV_" + DateTime.Now.ToString() + fileType; context.Response.ClearHeaders(); context.Response.ClearContent(); context.Response.Clear(); context.Response.ContentType = "application/ms-excel"; context.Response.AddHeader("content-disposition", "inline;filename=" + fileName); context.Response.Write(content); context.Response.End(); } }
And from my webpage did a call to HttpServerUtility.Transfer() like below:
protected void Page_Load(object sender, EventArgs e) { Context.Items.Add("Content", "colum1;column2;column3"); Context.Items.Add("FileType", ".csv"); DownloadHandler handler = new DownloadHandler(); Server.Transfer(handler, true); }
I run the project and BOOM:
Error executing child request for handler ‘WebApplication3.DownloadHandler’.
Initial I thought that it was the Boolean parameter which is causing the ViewState to be preserved and when calling the handler that would be indicated by IIS as not valid for this page. Setting the Boolean to false resulted in the same error.
I searched a bit on Google but couldn’t find a good answer why this was not working. So I used Reflector to check the .Net assembly to see what’s happening in that Transfer() method. (HttpServerUtility can be found in the System.Web dll.) And to my surprise I saw the following code within that method:
public void Transfer(IHttpHandler handler, bool preserveForm) { Page page = handler as Page; if ((page != null) && page.IsCallback) { throw new ApplicationException(SR.GetString("Transfer_not_allowed_in_callback")); } this.Execute(handler, null, preserveForm); this._context.Response.End(); }
If the handler is not of type Page than it will definitly be null. That’s what exactly is happening in my case. Second thought about it is that if handler could be of type Page, than Page will implement IHttpHandler. Let’s have a look at the Page class in Reflector. (Page can be found in System.Web.UI dll.) And there it is:
public class Page : TemplateControl, IHttpHandler { }
And our implementation of the ProcessRequest method:
[EditorBrowsable(EditorBrowsableState.Never)] public virtual void ProcessRequest(HttpContext context) { if ((HttpRuntime.NamedPermissionSet != null)
!HttpRuntime.DisableProcessRequestInApplicationTrust) { if (!HttpRuntime.ProcessRequestInApplicationTrust) { this.ProcessRequestWithAssert(context); return; } if (base.NoCompile) { HttpRuntime.NamedPermissionSet.PermitOnly(); } } this.ProcessRequestWithNoAssert(context); }
As you can see ProcessRequest is virtual, so we can override it with our own implementation. With this information I updated my DownloadHandler class to derive from Page instead of direct from IHttpHandler and override the ProcessRequest method. The DownloadHandler class will look like below:
public class DownloadHandler : Page { public override void ProcessRequest(HttpContext context) { var content = context.Items["Content"].ToString(); var fileType = context.Items["FileType"].ToString(); var fileName = "CSV_" + DateTime.Now.ToString() + fileType; context.Response.ClearHeaders(); context.Response.ClearContent(); context.Response.Clear(); context.Response.ContentType = "application/ms-excel"; context.Response.AddHeader("content-disposition", "inline;filename=" + fileName); context.Response.Write(content); context.Response.End(); } }
When running this code the file is outputted to the browser and after that the request is ended. I have added also the Page_Load and OnPreRender events, but there not executed in this sample. That’s a good thing, because right now we have our lightweight downloadhandler which is working in a very early stage of the Page lifecycle. (Actually it’s happening before the page lifecycle.)Well, I hope that this will help someone who is also trying to use this overload and is facing the same issues with the implementation.