In the comments of this page:
http://msdn.microsoft.com/en-us/library/12s31dhy%28v=VS.90%29.aspx
..it says that TransmitFile() cannot be used with UNC shares. As far as I can tell, this is the case; I get this error in Event Log when I attempt it:
TransmitFile failed. File Name: \\myshare1\e$\file.zip, Impersonation Enabled: 0, Token Valid: 1, HRESULT: 0x8007052e
The suggested alternative is to use WriteFile(), however, this is problematic because it loads the file into memory. In my application, the files are >200MB, so this is not going to scale.
Is there a method in ASP.NET for streaming files to users that's:
Mapping a network drive as a virtual directory is not an option for us. I would like to avoid copying the file to the local web server as well.
Thanks
Have you tried setting up an IIS vroot using the remote UNC path as its home directory? If it works, this may be the easiest solution. You can still enforce authentication barriers on the files (e.g. via an HttpModule, or perhaps even via the out-of-box forms auth module) but you can rely on IIS to efficiently stream the content once your auth filters give the go-ahead.
A caveat: the last time I configured IIS in a UNC scenario was a long time ago (1998!!!) and I ran into intermittent problems with files being locked on the remote machine, making updating files sometimes problematic. Dealing with recovery after a UNC server reboot was also interesting. I assume in the 11 years since then those problems have been ironed out, but you can never be sure!
Another approach may be to install a web server on the UNC machine, and then install a reverse proxy server on your web server, like the new IIS7 Application Request Routing module.
If you're willing to tie up a server thread, you can use the approach recommended in KB812406 which deals with issues around RAM consumption, timeouts, client disconnection, etc. Make sure to turn off Response Buffering!
The ideal maximum-control solution would be a "streaming HttpHandler" where, instead of having to send the output all at once, you could return a stream to ASP.NET and let ASP.NET deal with the details about chunking results to the client, dealing with disconnects, etc. But I was unable to find a good way to do this. :-(
Something you could try is opening the network file using a FileStream
, and using a loop to read a chunk of the file, transmit the chunk (using Response.Write(Char[], Int32, Int32)
), dispose of the chunk, and repeat until the file has been completely read.
You could write the file to a local directory, and have a robocopy job monitoring the directory to do the copy.
Since you want to avoid writing to the local server, though, you may want to investigate putting a server (HTTP or FTP, e.g.) on the target server, and writing the file to that service.
could you set up another IIS website which points to the UNC, then redirect them to the file on that other website?
Response.Redirect("http://files.somewhere.com/some/file.blah");
That way it will be running in a separate worker process and have no effect on your current site, and the files will be served directly by IIS, which is clearly best.
The actual error you got back was a logon failure to the file share (which isn't massively surprising considering the user IIS is likely to be running as, as well as you trying to access an administrative share). Have you tried setting up a share which has no access restrictions at all just to check if that is the issue rather than any specific limit in TransmitFile.
If that fixes it then you need to login to that share one way or another as the current user, or impersonate a user which does have permissions.
It is also worth pointing out that after abit of looking around with reflector TransmitFile can potentially end up reading the file into memory anyway, and that WriteFile has another version which takes a boolean value which decides whether to read the file into memory or not (in fact the default WriteFile passes false for this parameter). Might be worth you poking around in the code.
I would recommend the second site approach and implement a token-based authentication mechanism. Encode an authentication cookie in the URL passed to the client via Redirect. This could be an opaque value that is shared behind-the-scenes, or it could be something as simple as a hash of a secret password and the current date, for example.
One project I did used the hashing. One server generated a hash of a shared secret password and an extension number. The second server (which was on a different network) took the same password and extension and hashed them to confirm the user was allowed to place a call from that extension to the desired phone number.
The code for TransmitFile is very simple, why not modify it to do what you need?
public void TransmitFile(string filename, long offset, long length)
{
if (filename == null)
{
throw new ArgumentNullException("filename");
}
if (offset < 0L)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "offset");
}
if (length < -1L)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "length");
}
filename = this.GetNormalizedFilename(filename);
using (FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
long num = stream.Length;
if (length == -1L)
{
length = num - offset;
}
if (num < offset)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "offset");
}
if ((num - offset) < length)
{
throw new ArgumentException(SR.GetString("Invalid_range"), "length");
}
if (!this.UsingHttpWriter)
{
this.WriteStreamAsText(stream, offset, length);
return;
}
}
if (length > 0L)
{
bool supportsLongTransmitFile = (this._wr != null) && this._wr.SupportsLongTransmitFile;
this._httpWriter.TransmitFile(filename, offset, length, this._context.IsClientImpersonationConfigured || HttpRuntime.IsOnUNCShareInternal, supportsLongTransmitFile);
}
}
private void WriteStreamAsText(Stream f, long offset, long size)
{
if (size < 0L)
{
size = f.Length - offset;
}
if (size > 0L)
{
if (offset > 0L)
{
f.Seek(offset, SeekOrigin.Begin);
}
byte[] buffer = new byte[(int) size];
int count = f.Read(buffer, 0, (int) size);
this._writer.Write(Encoding.Default.GetChars(buffer, 0, count));
}
}
internal void TransmitFile(string filename, long offset, long size, bool isImpersonating, bool supportsLongTransmitFile)
{
if (this._charBufferLength != this._charBufferFree)
{
this.FlushCharBuffer(true);
}
this._lastBuffer = null;
this._buffers.Add(new HttpFileResponseElement(filename, offset, size, isImpersonating, supportsLongTransmitFile));
if (!this._responseBufferingOn)
{
this._response.Flush();
}
}
Thanks,
Phil.
User contributions licensed under CC BY-SA 3.0