4

I am creating a file dynamically in my web application. I want to send this file to the user, just like many sites do when you download a file.

I first tried the approach at Download/Stream file from URL - asp.net. But that didn't work because I'm in a class that's a layer removed from the ASPX web page; my result was that the file overwrote itself, alternating with I was unable to create the proper Request/Response objects.

Then I tried this approach:

try
{

    WebClient myClient = new WebClient();
    string basefile = Path.GetFileName( file_name );
    myClient.DownloadFile( file_name, basefile );
}
    catch (WebException we)
{
        string message = we.Message;
}

This raised an exception: Access to the path 'C:\Program Files (x86)\IIS Express\file-being-sent.ext' is denied.

So, how do I stream the file?

ETA my previous attempt:

    //Create a WebRequest to get the file
    HttpWebRequest fileReq = (HttpWebRequest)HttpWebRequest.Create( url ); //Send request to this URL

    //Create a response for this request
    HttpWebResponse fileResp = (HttpWebResponse)fileReq.GetResponse();

    //if (fileReq.ContentLength > 0)
    //    fileResp.ContentLength = fileReq.ContentLength;

    //Get the Stream returned from the response
    //stream = new Stream();
    writer = new StreamWriter( file_name );
    //stream = writer.BaseStream.Length


    // prepare the response to the client. resp is the client Response
    var resp = HttpContext.Current.Response;

    //Indicate the type of data being sent
    //resp.ContentType = "application/octet-stream";
    resp.ContentType = "application/zip";

    //Name the file 
    resp.AddHeader( "Content-Disposition", "attachment; filename=\"" + Path.GetFileName( file_name ) + "\"" );
    resp.AddHeader( "Content-Length", writer.BaseStream.Length.ToString() );
    // Verify that the client is connected.

    if (resp.IsClientConnected)
    {
        resp.Write( writer );
        resp.Flush();
    }

I haven't figured out what to pass to the call to HttpCreateRequest().

ETA2: Here's what I'm presently using. Various SOF posts say this should prompt the user with an Open Or Save dialog, but it still does not do so for me.

StreamReader reader = new StreamReader( file_name );
var resp = HttpContext.Current.Response;

//Indicate the type of data being sent
resp.ContentType = "application/zip";
resp.AppendHeader( "Content-Disposition", "attachment; filename=\"" + Path.GetFileName( file_name ) + "\"" );
resp.AppendHeader( "Content-Length", reader.BaseStream.Length.ToString() );
// Verify that the client is connected.

if (resp.IsClientConnected)
{
    resp.Clear();
    resp.TransmitFile( file_name ); //does not buffer into memory, therefore scales better for large files and heavy usage
    resp.Flush();
}

ETA3: The IIS trace log reports an exception, System.Web.HttpException: Server cannot set content type after HTTP headers have been sent. This does not make sense to me, as I'm setting the content type before appending headers.

Stack trace is:

[HttpException (0x80004005): Server cannot set content type after HTTP headers have been sent.]
System.Web.HttpResponse.set_ContentType(String value) +9681570
System.Web.HttpResponseInternalWrapper.set_ContentType(String value) +41
System.Web.UI.PageRequestManager.RenderPageCallback(HtmlTextWriter writer, Control pageControl) +139
System.Web.UI.Control.RenderChildrenInternal(HtmlTextWriter writer, ICollection children) +268
System.Web.UI.Control.RenderChildren(HtmlTextWriter writer) +8
System.Web.UI.Page.Render(HtmlTextWriter writer) +29
System.Web.UI.Control.RenderControlInternal(HtmlTextWriter writer, ControlAdapter adapter) +57
System.Web.UI.Control.RenderControl(HtmlTextWriter writer, ControlAdapter adapter) +100
System.Web.UI.Control.RenderControl(HtmlTextWriter writer) +25
System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) +1386

ETA4: I moved the streaming into the ASPX page's code-behind file, which has direct access to the Response object. I'm still getting the same exception. I'm at a loss to explain why the generally accepted approach is complaining to me that a response has already been sent, or otherwise why my code won't prompt the user to download the file.

Community
  • 1
  • 1
Codes with Hammer
  • 788
  • 3
  • 16
  • 47

2 Answers2

0

Just move all your content type and header code after resp.Clear()

using (StreamReader reader = new StreamReader( file_name ))
{
  var resp = HttpContext.Current.Response;
  resp.BufferOutput = true; //this is from the SO article
  //resp.Buffer = True;       //this is from msdn comments

  // Verify that the client is connected.

  if (resp.IsClientConnected)
  {
    resp.Clear();
    resp.ClearHeaders();
    //Indicate the type of data being sent
    resp.ContentType = "application/zip";
    resp.AppendHeader( "Content-Disposition", "attachment; filename=\"" + Path.GetFileName( file_name ) + "\"" );
    resp.AppendHeader( "Content-Length", reader.BaseStream.Length.ToString() );
    resp.TransmitFile( file_name ); //does not buffer into memory, therefore scales better for large files and heavy usage
    resp.End();
  }
}
TombMedia
  • 1,962
  • 2
  • 22
  • 27
  • That _ought_ to work, Tomb. But it did not do so for me. (That's been the theme all along -- what ought to work has not succeeded in my case.) – Codes with Hammer Sep 11 '13 at 13:34
  • Is there a chance you have this wrapped up in a Task or a background thread? – TombMedia Sep 12 '13 at 04:30
  • I found [this SO post](http://stackoverflow.com/questions/9360857/server-cannot-set-content-type-after-http-headers-have-been-sent) that might help. I've added the the code to my answer, see if that helps. – TombMedia Sep 12 '13 at 04:33
  • I am not using `async/await/Task` for this. (But I attempted to do so for the cleanup.) And I'm pretty sure I had been using `resp.BufferOutput = true;` at some point since I first posted this. – Codes with Hammer Sep 12 '13 at 13:12
  • I really think its related to the fact you are "a layer removed" from the aspx page. Is there any way you can update your question with a little more code that provides an example of how that is? It could be a simple fix such as passing the pages context in or even passing the file back out and transmitting from the originating aspx page. It might also point out that you are in a webservice or something. – TombMedia Sep 12 '13 at 18:20
0

Set ContentType property in your ASPX <%@ Page...%> directive:

<%@ Page ContentType="application/zip" %>
Strelok
  • 50,229
  • 9
  • 102
  • 115