Transmit File AND HTML in Response

J

john

The standard method to transmit a file from an aspx page to a browser
is to stream the file to the response then end the response. The HTML
code generated by the aspx page is discarded, and the browser displays
or offers to save the binary file instead.

I would like to have the browser accept and display the revised HTML
code as well as offering to open or save the attached file. This
SHOULD be possible using a multipart MIME format - one part is declared
with a Content-Disposition of inline and a Content-Type of text/html,
and a second part with a Content-Disposition of attachment and a
Content-Type of application/octet-stream.

However, I haven't been able to get a valid multipart response out of
my aspx page (using multipart/mixed or multipart/x-mixed-replace). The
Page wrapper seems to be hard-coded to creating and sending headers
that are incompatible with this approach.

Has anyone succesfully done something like this? My example code is
below, which is a simple page that displays a file and updates a view
count. When the page is refreshed, the view count increments. When a
file is displayed, the view count is incremented in code but the
revised HTML is not sent to the browser, so you don't see the textbox
change. Also, since the ViewState is not updated in the browser, the
view count goes back to it's previous state when you next refresh.

I've tried removing the Response.End() at the end of the ShowDocument
method, but bad things happen (the next button click generates a
bizarre page that has the html code displayed twice with a partial HTTP
header between them...). I also tried manually setting the
Response.ContentType to multipart/mixed and writing the boundary
between the HTML output and the file streaming, but I couldn't change
the ContentType after the inital headers had been sent.

Thanks for any ideas...

John H.


== WebForm1.aspx ==

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false" Inherits="ShowDocumentTest.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>WebForm1</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema
content="http://schemas.microsoft.com/intellisense/ie5">
</head>
<body >
<form id="Form1" method="post" runat="server">
<p><asp:TextBox id="TextBox1" runat="server"
Width="400px"></asp:TextBox></p>
<p><asp:LinkButton id="btnPDF" runat="server">Show
file</asp:LinkButton></p>
<p><asp:Button id="Button1" runat="server"
Text="Refresh"></asp:Button></p>
</form>
</body>
</html>


== WebForm1.aspx.cs ==

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace ShowDocumentTest
{
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.LinkButton btnPDF;
protected System.Web.UI.WebControls.TextBox TextBox1;
protected System.Web.UI.WebControls.Button Button1;

private int viewCount = 0;

private void Page_Load(object sender, System.EventArgs e)
{
if (IsPostBack)
viewCount = (int)(ViewState["viewCount"]);
viewCount++;
ViewState["viewCount"] = viewCount;
TextBox1.Text = "You've seen this " + viewCount + " times";
}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnPDF.Click += new System.EventHandler(this.btnPDF_Click);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

private void btnPDF_Click(object sender, System.EventArgs e)
{
ShowDocument(Request.MapPath("files") + Path.DirectorySeparatorChar
+ "example.pdf");
}

private void ShowDocument(string filePath)
{
FileInfo fi = new FileInfo(filePath);
string fileName = Path.GetFileName(filePath);

Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=\""
+ fileName + "\"");
Response.AddHeader("Content-Length", fi.Length.ToString());

byte[] buffer = new byte[1024];
long byteCount;
FileStream inStr = File.OpenRead(filePath);

while ((byteCount = inStr.Read(buffer, 0, buffer.Length)) > 0)
{
if(Response.IsClientConnected)
{
Response.OutputStream.Write(buffer, 0, (int)(byteCount));
Response.Flush();
}
else
break;
}
inStr.Close();
Response.End();
}
}
}
 
M

MSDN

Tested your code in VS.NET 2005 to see if any difference but I see none
I would love to see a solution for this one.

Have you tried different possibilities

call JavaScript to get you the incremented number ( via AJAX, Web services
via JavaScript ) or get back the data then afterwards get a file via a
second page ( Target = _blank ) dedicated to get files for you?

Just thinking... Sorry for the lack of help here.

SA


The standard method to transmit a file from an aspx page to a browser
is to stream the file to the response then end the response. The HTML
code generated by the aspx page is discarded, and the browser displays
or offers to save the binary file instead.

I would like to have the browser accept and display the revised HTML
code as well as offering to open or save the attached file. This
SHOULD be possible using a multipart MIME format - one part is declared
with a Content-Disposition of inline and a Content-Type of text/html,
and a second part with a Content-Disposition of attachment and a
Content-Type of application/octet-stream.

However, I haven't been able to get a valid multipart response out of
my aspx page (using multipart/mixed or multipart/x-mixed-replace). The
Page wrapper seems to be hard-coded to creating and sending headers
that are incompatible with this approach.

Has anyone succesfully done something like this? My example code is
below, which is a simple page that displays a file and updates a view
count. When the page is refreshed, the view count increments. When a
file is displayed, the view count is incremented in code but the
revised HTML is not sent to the browser, so you don't see the textbox
change. Also, since the ViewState is not updated in the browser, the
view count goes back to it's previous state when you next refresh.

I've tried removing the Response.End() at the end of the ShowDocument
method, but bad things happen (the next button click generates a
bizarre page that has the html code displayed twice with a partial HTTP
header between them...). I also tried manually setting the
Response.ContentType to multipart/mixed and writing the boundary
between the HTML output and the file streaming, but I couldn't change
the ContentType after the inital headers had been sent.

Thanks for any ideas...

John H.


== WebForm1.aspx ==

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false" Inherits="ShowDocumentTest.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>WebForm1</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema
content="http://schemas.microsoft.com/intellisense/ie5">
</head>
<body >
<form id="Form1" method="post" runat="server">
<p><asp:TextBox id="TextBox1" runat="server"
Width="400px"></asp:TextBox></p>
<p><asp:LinkButton id="btnPDF" runat="server">Show
file</asp:LinkButton></p>
<p><asp:Button id="Button1" runat="server"
Text="Refresh"></asp:Button></p>
</form>
</body>
</html>


== WebForm1.aspx.cs ==

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace ShowDocumentTest
{
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.LinkButton btnPDF;
protected System.Web.UI.WebControls.TextBox TextBox1;
protected System.Web.UI.WebControls.Button Button1;

private int viewCount = 0;

private void Page_Load(object sender, System.EventArgs e)
{
if (IsPostBack)
viewCount = (int)(ViewState["viewCount"]);
viewCount++;
ViewState["viewCount"] = viewCount;
TextBox1.Text = "You've seen this " + viewCount + " times";
}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnPDF.Click += new System.EventHandler(this.btnPDF_Click);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

private void btnPDF_Click(object sender, System.EventArgs e)
{
ShowDocument(Request.MapPath("files") + Path.DirectorySeparatorChar
+ "example.pdf");
}

private void ShowDocument(string filePath)
{
FileInfo fi = new FileInfo(filePath);
string fileName = Path.GetFileName(filePath);

Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=\""
+ fileName + "\"");
Response.AddHeader("Content-Length", fi.Length.ToString());

byte[] buffer = new byte[1024];
long byteCount;
FileStream inStr = File.OpenRead(filePath);

while ((byteCount = inStr.Read(buffer, 0, buffer.Length)) > 0)
{
if(Response.IsClientConnected)
{
Response.OutputStream.Write(buffer, 0, (int)(byteCount));
Response.Flush();
}
else
break;
}
inStr.Close();
Response.End();
}
}
}
 
B

bruce barker \(sqlwork.com\)

couple of issues. there is only one content-type header, and headers come
before content, so you can not change them after you started a download
(they have been written).

when set the content-type, you include the boundry header. then you need to
write out the content, with content headers and boundries:

Content-type: multipart/mixed; boundary=23xx1211
CRLF
--23xx1211
Content-type: text/html
CRLF
..... html document data .(first "part" of the message)...
--23xx1211
Content-type: audio/aiff
CRLF
...... audio data ..... (second "part" of the message)....
--23xx1211--

now. most browsers see multipart.mixed as a replacement for server push, and
multipart/related as a mail messages with inline content such as audio and
images. they will save the whole page or display, i don't know if it will do
what you want.

-- bruce (sqlwork.com)



The standard method to transmit a file from an aspx page to a browser
is to stream the file to the response then end the response. The HTML
code generated by the aspx page is discarded, and the browser displays
or offers to save the binary file instead.

I would like to have the browser accept and display the revised HTML
code as well as offering to open or save the attached file. This
SHOULD be possible using a multipart MIME format - one part is declared
with a Content-Disposition of inline and a Content-Type of text/html,
and a second part with a Content-Disposition of attachment and a
Content-Type of application/octet-stream.

However, I haven't been able to get a valid multipart response out of
my aspx page (using multipart/mixed or multipart/x-mixed-replace). The
Page wrapper seems to be hard-coded to creating and sending headers
that are incompatible with this approach.

Has anyone succesfully done something like this? My example code is
below, which is a simple page that displays a file and updates a view
count. When the page is refreshed, the view count increments. When a
file is displayed, the view count is incremented in code but the
revised HTML is not sent to the browser, so you don't see the textbox
change. Also, since the ViewState is not updated in the browser, the
view count goes back to it's previous state when you next refresh.

I've tried removing the Response.End() at the end of the ShowDocument
method, but bad things happen (the next button click generates a
bizarre page that has the html code displayed twice with a partial HTTP
header between them...). I also tried manually setting the
Response.ContentType to multipart/mixed and writing the boundary
between the HTML output and the file streaming, but I couldn't change
the ContentType after the inital headers had been sent.

Thanks for any ideas...

John H.


== WebForm1.aspx ==

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false" Inherits="ShowDocumentTest.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>WebForm1</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema
content="http://schemas.microsoft.com/intellisense/ie5">
</head>
<body >
<form id="Form1" method="post" runat="server">
<p><asp:TextBox id="TextBox1" runat="server"
Width="400px"></asp:TextBox></p>
<p><asp:LinkButton id="btnPDF" runat="server">Show
file</asp:LinkButton></p>
<p><asp:Button id="Button1" runat="server"
Text="Refresh"></asp:Button></p>
</form>
</body>
</html>


== WebForm1.aspx.cs ==

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace ShowDocumentTest
{
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.LinkButton btnPDF;
protected System.Web.UI.WebControls.TextBox TextBox1;
protected System.Web.UI.WebControls.Button Button1;

private int viewCount = 0;

private void Page_Load(object sender, System.EventArgs e)
{
if (IsPostBack)
viewCount = (int)(ViewState["viewCount"]);
viewCount++;
ViewState["viewCount"] = viewCount;
TextBox1.Text = "You've seen this " + viewCount + " times";
}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnPDF.Click += new System.EventHandler(this.btnPDF_Click);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

private void btnPDF_Click(object sender, System.EventArgs e)
{
ShowDocument(Request.MapPath("files") + Path.DirectorySeparatorChar
+ "example.pdf");
}

private void ShowDocument(string filePath)
{
FileInfo fi = new FileInfo(filePath);
string fileName = Path.GetFileName(filePath);

Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=\""
+ fileName + "\"");
Response.AddHeader("Content-Length", fi.Length.ToString());

byte[] buffer = new byte[1024];
long byteCount;
FileStream inStr = File.OpenRead(filePath);

while ((byteCount = inStr.Read(buffer, 0, buffer.Length)) > 0)
{
if(Response.IsClientConnected)
{
Response.OutputStream.Write(buffer, 0, (int)(byteCount));
Response.Flush();
}
else
break;
}
inStr.Close();
Response.End();
}
}
}
 
M

MSDN

Bruce,

You seem to know what you are talking about.
How do you make the example below to work.

Are you saying that:
Response.ContentType = "multipart/mixed";

Since I never done this before, Where do you add the boundary??

Response.ContentType = "multipart/mixed; boundary=23xx1211"; ???
Response.AddHeader("Content-Disposition", "attachment; filename=\"" +
fileName + "\"");
Response.AddHeader("Content-Length", fi.Length.ToString());
Response.Write("CRLF");
Response.Write("--23xx1211");
Response.Write("Content"); etc...

Response.ContentType = "text/html";
Response.AddHeader("Content-?????"");
Response.AddHeader("Content-Length", ????);
Response.Write("CRLF");
Response.Write("--23xx1211");
Response.Write("Content"); etc...


Corrections and/or a working example will be great.

Thank you Bruce,

SA

bruce barker (sqlwork.com) said:
couple of issues. there is only one content-type header, and headers come
before content, so you can not change them after you started a download
(they have been written).

when set the content-type, you include the boundry header. then you need
to write out the content, with content headers and boundries:

Content-type: multipart/mixed; boundary=23xx1211
CRLF
--23xx1211
Content-type: text/html
CRLF
.... html document data .(first "part" of the message)...
--23xx1211
Content-type: audio/aiff
CRLF
..... audio data ..... (second "part" of the message)....
--23xx1211--

now. most browsers see multipart.mixed as a replacement for server push,
and multipart/related as a mail messages with inline content such as audio
and images. they will save the whole page or display, i don't know if it
will do what you want.

-- bruce (sqlwork.com)



The standard method to transmit a file from an aspx page to a browser
is to stream the file to the response then end the response. The HTML
code generated by the aspx page is discarded, and the browser displays
or offers to save the binary file instead.

I would like to have the browser accept and display the revised HTML
code as well as offering to open or save the attached file. This
SHOULD be possible using a multipart MIME format - one part is declared
with a Content-Disposition of inline and a Content-Type of text/html,
and a second part with a Content-Disposition of attachment and a
Content-Type of application/octet-stream.

However, I haven't been able to get a valid multipart response out of
my aspx page (using multipart/mixed or multipart/x-mixed-replace). The
Page wrapper seems to be hard-coded to creating and sending headers
that are incompatible with this approach.

Has anyone succesfully done something like this? My example code is
below, which is a simple page that displays a file and updates a view
count. When the page is refreshed, the view count increments. When a
file is displayed, the view count is incremented in code but the
revised HTML is not sent to the browser, so you don't see the textbox
change. Also, since the ViewState is not updated in the browser, the
view count goes back to it's previous state when you next refresh.

I've tried removing the Response.End() at the end of the ShowDocument
method, but bad things happen (the next button click generates a
bizarre page that has the html code displayed twice with a partial HTTP
header between them...). I also tried manually setting the
Response.ContentType to multipart/mixed and writing the boundary
between the HTML output and the file streaming, but I couldn't change
the ContentType after the inital headers had been sent.

Thanks for any ideas...

John H.


== WebForm1.aspx ==

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs"
AutoEventWireup="false" Inherits="ShowDocumentTest.WebForm1" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<html>
<head>
<title>WebForm1</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema
content="http://schemas.microsoft.com/intellisense/ie5">
</head>
<body >
<form id="Form1" method="post" runat="server">
<p><asp:TextBox id="TextBox1" runat="server"
Width="400px"></asp:TextBox></p>
<p><asp:LinkButton id="btnPDF" runat="server">Show
file</asp:LinkButton></p>
<p><asp:Button id="Button1" runat="server"
Text="Refresh"></asp:Button></p>
</form>
</body>
</html>


== WebForm1.aspx.cs ==

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace ShowDocumentTest
{
public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.LinkButton btnPDF;
protected System.Web.UI.WebControls.TextBox TextBox1;
protected System.Web.UI.WebControls.Button Button1;

private int viewCount = 0;

private void Page_Load(object sender, System.EventArgs e)
{
if (IsPostBack)
viewCount = (int)(ViewState["viewCount"]);
viewCount++;
ViewState["viewCount"] = viewCount;
TextBox1.Text = "You've seen this " + viewCount + " times";
}

#region Web Form Designer generated code
override protected void OnInit(EventArgs e)
{
//
// CODEGEN: This call is required by the ASP.NET Web Form Designer.
//
InitializeComponent();
base.OnInit(e);
}

/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnPDF.Click += new System.EventHandler(this.btnPDF_Click);
this.Load += new System.EventHandler(this.Page_Load);

}
#endregion

private void btnPDF_Click(object sender, System.EventArgs e)
{
ShowDocument(Request.MapPath("files") + Path.DirectorySeparatorChar
+ "example.pdf");
}

private void ShowDocument(string filePath)
{
FileInfo fi = new FileInfo(filePath);
string fileName = Path.GetFileName(filePath);

Response.ContentType = "application/octet-stream";
Response.AddHeader("Content-Disposition", "attachment; filename=\""
+ fileName + "\"");
Response.AddHeader("Content-Length", fi.Length.ToString());

byte[] buffer = new byte[1024];
long byteCount;
FileStream inStr = File.OpenRead(filePath);

while ((byteCount = inStr.Read(buffer, 0, buffer.Length)) > 0)
{
if(Response.IsClientConnected)
{
Response.OutputStream.Write(buffer, 0, (int)(byteCount));
Response.Flush();
}
else
break;
}
inStr.Close();
Response.End();
}
}
}
 
J

JohnH

That's exactly what I tried, but you will get an error on the second
attempt to set Response.ContentType. In theory, you should be able to
set different content types for the different parts, hence the idea of
a part with a content type of application/octet-stream (the attached
file) and a second part with a content type of text/html (the HTML
page).

John H.
 
M

MSDN

John,

Where do we go from here... waiting for Bruce if he has any other Ideas??
Are you thinking of other ways to do this?
What are your thoughts?


SA
 
J

JohnH

I think the System.Web.HttpResponse class has some stuff in it that is
incompatible with the multipart response idea. That's the only place I
can think of that would prevent you from setting Response.ContentType
more than once for a given response.

If I was doing this from scratch, I'd just write a class to generate
the full and correct MIME-formatted response message. So the first
thing I want to do is try that using a basic TCP socket program to send
such a message to a browser, and make sure that browsers actually
handle the multipart content correctly. If that works, I think I'll
have to figure out how to con the Page class into responding via this
new HttpMultiPartResponse class instead of the HttpResponse class that
the framework sets up for it.

This might be fun, but it won't be easy...

John H.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top