Response.TransmitFile() with UNC share (ASP.NET)

2

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:

  • scalable (doesn't read entire file into RAM or occupy ASP.NET threads)
  • works with UNC shares

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

asp.net
download
impersonation
content-disposition
transmitfile
asked on Stack Overflow Oct 30, 2009 by frankadelic • edited Aug 19, 2011 by frankadelic

7 Answers

1

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. :-(

answered on Stack Overflow Nov 17, 2009 by Justin Grant
0

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.

answered on Stack Overflow Oct 30, 2009 by Adam Maras
0

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.

answered on Stack Overflow Nov 17, 2009 by brianary
0

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.

answered on Stack Overflow Nov 17, 2009 by Michael Baldry
0

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.

answered on Stack Overflow Nov 21, 2009 by tyranid • edited Nov 21, 2009 by tyranid
0

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.

answered on Stack Overflow Nov 24, 2009 by Dark Falcon
-1

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.

answered on Stack Overflow Nov 24, 2009 by Plip

User contributions licensed under CC BY-SA 3.0