一句话: POST上传文件时, UploadID等任何其它参数,均采用URL传输,不要使用Form变量. 否则必须保证该变量在文件上传完毕之后才读取.
Obviously, by this point, there is a lot of information on the web about implementing this sort of solution, and we are actually using the same approach at my company, so I am not going to comment on that. Suffice to say, this thread has proven to be very valuable.
The only problem with the code provided in this thread is that it does not work in IIS6 since the worker process used there is a subclass of ISAPIWorkerRequestInProc, so going directly to the base class gives you ISAPIWorkerRequestInProc and not ISAPIWorkerRequest. This can easily be fixed by walking up the inheritance tree until you get to ISAPIWorkerRequest instead of blindly going to the base class.
One problem that I had in my implementation specifically, was ReadEntityBody() returning 0 after a timeout under certain conditions. I've spent quite a bit of time trying to find the solution in various search engines, but unfortunately left empty-handed. It seems that a lot of people were having the same problem, but all of their posts either went unanswered or were answered with complete BS. Two particular replies come to mind. One is from an MVP who said "That's Internet for You..." and another from MS Support who said "Use Request.Files instead". Very helpful indeed :) I am sure that people who were having these problems eventually resolved them, but nobody bothered to post the answer. And it would have saved me 3 hours... Oh well. Hopefully, I can clear things up a little, so that the next poor soul does not have to repeat it.
The short version:
Make sure you are not referencing any FORM variables before you attempt to read the entity body.
The long version:
Simply put, this is a bug in the implementation and not in the .NET Framework / IIS. The WorkerProcess is not meant to be accessed from IHttpModule/HttpContext. It is marked internal for a reason. By using reflection go get access to an inaccessible property, we open up the Pandora's Box for problems like this. Unfortunately, in ASP.Net 1.0/1.1 this seems to be the only option.
This behavior occurs if you access FORM variables on the HttpRequest before attempting to call ReadEntityBody(). When the form is submitted using "multipart/form-data", all FORM variables contained in the message body - the FORM field you are querying might well be defined AFTER the file contents in the HTTP post body. So, in order to be able to read the FORM field value, it has to load the entire request body first. The getter for HttpRequest.Form initializes the field values collection by calling FillInFormCollection() which in turn calls GetMultipartContent():
- private MultipartContentElement GetMultipartContent()
- if (this._multipartContentElements == null)
- byte buffer1 = this.GetMultipartBoundary();
- if (buffer1 == null)
- return new MultipartContentElement;
- byte buffer2 = this.GetEntireRawContent();
- if (buffer2 == null)
- return new MultipartContentElement;
- this._multipartContentElements = HttpMultipartContentTemplateParser.Parse(buffer2, buffer2.Length, buffer1, this.ContentEncoding);
- return this._multipartContentElements;
When this method is called for the first time, it initializes _multipartContentElements by calling GetEntireRawContent() which does exactly what we are doing in our file upload module - it reads the rest of HTTP post body using ReadEntityBody(). The difference is that since GetEntireRawContent() does not reset WorkerProcess's _contentAvailLength, _contentTotalLength, _preloadedContent and _preloadedContentRead (nor should it), the next time we call ReadEntityBody(), there's nothing left to read in the pipeline!
Hope this helps,
Lead Software Developer