ASP.NET: How to avoid a ThreadAbortException on a file download


Scenario:

A common task in most web applications will be to send a file directly to the response stream so the user will receive a file/save dialog in order to save the file (for me this is usually sending a zip, pdf, xls or txt files for reporting or data downloading).

In the classic ASP days we would write to the response stream and then end the response with a hard "Response.End". The ASP.Net team brought this syntax forward in order to preserve backwards compability and it's been with us up until this point and will probably be with us for years and years to come.

In the .Net world though calling a "Response.End" at least from a web forms applications will cause an expensive "System.Threading.ThreadAbortException" to be thrown. The reason why is that the page still had executing it meant to do (which is write out the rest of the page life cycle). If you don't put the Response.End you'll end up with the HTML output from the web form at the bottom of the file you stream (which corrupts the file and obviously we don't want). If we can avoid it we don't want this Exception thrown (if you catch it, it's still has the overhead of being thrown which is ideal of avoid in every scenario I can think of).

There is a simple way around this. Instead of writing to the response and then ending it, we will instruct ASP to supress the additional content and then we'll call "ApplicationInstance.CompleteRequest" which will tell ASP.NET to go to the end of the life cycle and complete it after the scoped section is done executing. These are the important lines you'll need after you stream the file:

VB.Net

HttpContext.Current.Response.Flush()
HttpContext.Current.Response.SuppressContent = True
HttpContext.Current.ApplicationInstance.CompleteRequest()

C#

HttpContext.Current.Response.Flush();
HttpContext.Current.Response.SuppressContent = true;
HttpContext.Current.ApplicationInstance.CompleteRequest();

More fully, here is the above code with something that would write out a file that lives on your web-site.

VB.Net

HttpContext.Current.Response.Clear()
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=test.txt")
HttpContext.Current.Response.ContentType = "text/plain"
HttpContext.Current.Response.WriteFile(Server.MapPath("/Download/MyFile.txt"))

HttpContext.Current.Response.Flush()
HttpContext.Current.Response.SuppressContent = True
HttpContext.Current.ApplicationInstance.CompleteRequest()

C#

HttpContext.Current.Response.Clear();
HttpContext.Current.Response.AddHeader("Content-Disposition", "attachment; filename=test.txt");
HttpContext.Current.Response.ContentType = "text/plain";
HttpContext.Current.Response.WriteFile(Server.MapPath("/Download/MyFile.txt"));

HttpContext.Current.Response.Flush();
HttpContext.Current.Response.SuppressContent = true;
HttpContext.Current.ApplicationInstance.CompleteRequest();