MS makes things simple but with limitations
I have been working on a project that requires files to be uploaded to a Web server. No big deal... ordinarily. However this project is .Net. As a result the most obvious and simple implementation requires the entire contents of the file to be moved into memory. This is due to the architecture of IIS. IIS 6.0 deals with this a little better when run in isolation mode. However, this also has implication.
I found some good writings on this topic. It would seem that MS dealt with the download issue, but not upload. In any case the following presents a good overview of the issue. The upload issue is a tougher one to deal with. All of the implemented solutions I found use an ISAPI filter or something at that level in the architecture. Any way here are the links:
A nice overview of the problem (from the download perspective) here:
http://www.objectsharp.com/Blogs/bruce/articles/1571.aspx
A good discussion of the upload issues (with solutions) here:
http://forums.asp.net/1/55127/ShowPost.aspx#55127
MS Solutions to the problem:
http://support.microsoft.com/kb/823409/EN-US/ (Download)
http://support.microsoft.com/kb/295626/EN-US/ (Upload)
I snipped a bit of VB code from coderage at the asp.net forum available after the jump
One thing to consider is that the headers and view state come over with the file. You need to turn off any runat=server controls. Play with a text file to get some sense of how to strip headers.
Code: |
Ok, here's the html file tha has the form: <html> <head><title>UploadForm</title></head> <body> <form name="UploadForm" method="post" encoding="multipart/form-data"> File to Upload: <input type="file" name="file"/><br /><br /> <input type="submit" value="Submit"/> </form> </body> </html> Ok, now add the following to your Web.config file inside the <system .web> element: <httpruntime executionTimeout="1800" maxRequestLength="524288000" /> <httpmodules> <add name="UploadModule" type="HttpUploadApp.UploadModule,HttpUploadApp" /> </httpmodules> This configures the timeout to 30 minutes, accepts up to 500MB files, and registers the HttpModule. My assembly name is HttpUploadApp as the namespace is the same thing. Next is the HttpModule class. This is a rough example that writes every HTTP request that has a content body to a file whose name is a GUID with a ".txt" extension saved in a directory named "C:\Upload\". This was just for test purposes!!! HttpModule class (VB.NET): Imports System Imports System.IO Imports System.Web Public Class UploadModule Implements IHttpModule Public Sub New() MyBase.New() End Sub Public Sub Dispose() Implements System.Web.IHttpModule.Dispose End Sub Public Sub Init(ByVal context As System.Web.HttpApplication) Implements System.Web.IHttpModule.Init AddHandler context.BeginRequest, AddressOf BeginRequest End Sub Private Sub BeginRequest(ByVal source As Object, ByVal e As EventArgs) Dim app As HttpApplication = CType(source, HttpApplication) Dim context As HttpContext = app.Context ' Get the HttpWorkerRequest Object!!! Dim hwr As HttpWorkerRequest = CType(context.GetType.GetProperty("WorkerRequest", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic).GetValue(context, Nothing), HttpWorkerRequest) If hwr.HasEntityBody Then Dim g As Guid = Guid.NewGuid() Dim fn As String = "C:\Upload\" + g.ToString + ".txt" Dim fs As New FileStream(fn, FileMode.CreateNew, FileAccess.Write, FileShare.Read) Try Const BUFFSIZE As Integer = 65536 Dim Buffer As Byte() Dim Received As Integer = 0 Dim TotalReceived As Integer = 0 Dim ContentLength As Integer = CType(hwr.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentLength), Integer) Buffer = hwr.GetPreloadedEntityBody() fs.Write(Buffer, 0, Buffer.Length) Received = Buffer.Length TotalReceived += Received If Not hwr.IsEntireEntityBodyIsPreloaded Then While (ContentLength - TotalReceived) >= Received Buffer = New Byte(BUFFSIZE) {} Received = hwr.ReadEntityBody(Buffer, BUFFSIZE) fs.Write(Buffer, 0, Received) TotalReceived += Received End While Received = hwr.ReadEntityBody(Buffer, (ContentLength - TotalReceived)) fs.Write(Buffer, 0, Received) TotalReceived += Received End If fs.Flush() Catch ex As Exception Finally fs.Close() End Try context.Response.Redirect("UploadForm.aspx") End If End Sub End Class Ok, that's it. What I didn't address here was keeping th aspnet_wp.exe process from recyling itself by opening another client-side browser winfow that loads an .aspx page with a client-side (JavaScript) timer refreshing itself every 10 seconds or so. Anyone can write that code --- it's so simple!!! CodeRage! |
And now a C# version (again credited to a forum poster BjornB.)
Code: |
using System; using System.Web; using System.IO; using System.Reflection; namespace My.PlayGround { /// <summary> /// Summary description for UploadHandler. /// </summary> public class UploadHandler : IHttpModule { public UploadHandler() { } public void Init(HttpApplication application) { // Register our event handler with with the application object application.BeginRequest += new EventHandler(this.BeginRequest); } public void Dispose() {} public void BeginRequest(object sender, EventArgs args) { try { // Create an instance of th application object HttpApplication application = (HttpApplication) sender; // Create an instance of the HTTP worker request HttpWorkerRequest request = (HttpWorkerRequest) application.Context.GetType().GetProperty("WorkerRequest", (BindingFlags)36).GetValue(application.Context, null); // Only trigger if the request is of type 'multipart/form-data' if(application.Context.Request.ContentType.IndexOf("multipart/form-data") > -1) { // Create a new unique identifier to identify each request string guid = Guid.NewGuid().ToString(); // Please alter pathname because the root of the C drive is not really the place to put your tempfiles string filename = "c:\\requests\\request_" + guid + ".txt"; // Check if a request body is sent by the browser if(request.HasEntityBody()) { // Get the content length of the request int content_length = Convert.ToInt32(request.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentLength)); int content_received = 0; // This is a nice feature to redirect users if they upload a file // larger then 100Mb BEFORE it is being uploaded if(content_length > 102400000) { application.Context.Response.Redirect("http://www.av.com"); } // Create a file to store the stream FileStream newFile = new FileStream(filename, FileMode.Create); // Get the preloaded buffered data byte[] body = request.GetPreloadedEntityBody(); content_received += body.Length; // Write the preloaded data to new file newFile.Write(body, 0, body.Length); // Get all the other data to be written to file if available if(!request.IsEntireEntityBodyIsPreloaded()) { // Create an input buffer to store the incomming data byte[] a_buffer = new byte[16384]; int bytes_read = 16384; while((content_length - content_received) >= bytes_read) { bytes_read = request.ReadEntityBody(a_buffer, a_buffer.Length); content_received += bytes_read; newFile.Write(a_buffer, 0, bytes_read); } // Read the last part of the stream bytes_read = request.ReadEntityBody(a_buffer, (content_length - content_received)); newFile.Write(a_buffer, 0, bytes_read); content_received += bytes_read; } // Flush all data to the file newFile.Flush(); // Close the file newFile.Close(); // Redirect to the page to avoid the browser hanging string current_page = application.Context.Request.CurrentExecutionFilePath; current_page = current_page.Substring(current_page.LastIndexOf("/")+1) + "?" + application.Context.Request.QueryString; application.Context.Response.Redirect(current_page); } } } catch(System.Threading.ThreadAbortException) {} catch(Exception exception) { object e = exception; } } } } |