Sunday, June 8, 2014

How to stream a FileResult from one web server to another with ASP.NET MVC

MVC has a lot of great built in tooling, including the ability to stream very large file results straight from disk without having to load the whole file stream into memory.

What about the scenario where you want to stream a large file from one web server to another?

For example, I have an ASP.NET MVC application that needs to expose a download for a file hosted on another server, but I can not just redirect my users directly to the other URL. For that, we need to create a custom ActionResult type!

WebRequestFileResult

Here is a simple of example of what your controller might look like:

public class FileController : Controller
{
    public ActionResult LocalFile()
    {
        return new FilePathResult(@"c:\files\otherfile.zip", "application/zip");
    }
 
    public ActionResult RemoteFile()
    {
        return new WebRequestFileResult("http://otherserver/otherfile.zip");
    }
}

Here is the implementation for WebRequestFileResult:

public class WebRequestFileResult : FileResult
{
    private const int BufferSize = 32768; // 32 KB
    private const string DispositionHeader = "Content-Disposition";
    private const string LengthHeader = "Content-Length";
 
    private readonly string _url;
 
    public WebRequestFileResult(string url)
        : base("application/octet-stream")
    {
        if (String.IsNullOrWhiteSpace(url))
            throw new ArgumentNullException("url", "Url is required");
 
        _url = url;
    }
 
    protected override void WriteFile(HttpResponseBase localResponse)
    {
        var webRequest = WebRequest.Create(_url);
 
        using (var remoteResponse = webRequest.GetResponse())
        using (var remoteStream = remoteResponse.GetResponseStream())
        {
            if (remoteStream == null)
                throw new NullReferenceException(
                    "Request returned null stream: " + _url);
 
            var dispositionKey = remoteResponse.Headers.AllKeys.FirstOrDefault(
                k => k.Equals(
                    DispositionHeader, 
                    StringComparison.InvariantCultureIgnoreCase));
 
            if (!String.IsNullOrWhiteSpace(dispositionKey))
                localResponse.AddHeader(
                    DispositionHeader, 
                    remoteResponse.Headers[DispositionHeader]);
 
            var contentLenthString = remoteResponse.ContentLength
                .ToString(CultureInfo.InvariantCulture);
                
            localResponse.ContentType = remoteResponse.ContentType;
            localResponse.AddHeader(LengthHeader, contentLenthString);
 
            var buffer = new byte[BufferSize];
            var loopCount = 0;
 
            while (true)
            {
                if (!localResponse.IsClientConnected)
                    break;
 
                var read = remoteStream.Read(buffer, 0, BufferSize);
 
                if (read <= 0)
                    break;
 
                localResponse.OutputStream.Write(buffer, 0, read);
 
                // Flush after 1 MB; note that this
                // will prevent server side caching.
                loopCount++;
                if (loopCount % 32 == 0)
                    localResponse.Flush();
            }
        }
    }
}

Enjoy,
Tom

8 comments:

  1. You should use Stream.Copy method instead of while(true) loop.

    ReplyDelete
    Replies
    1. When using Stream.CopyTo the response stream would not always auto flush, and the server could built up a lot of memory pressure.

      Delete
    2. How can filestreamresult support resume download from url , Tom ?

      Delete
  2. You completed certain reliable points there. I did a search on the subject and found nearly all persons will agree with your blog. gostream

    ReplyDelete
  3. I loved as much as you will receive carried out right here. The sketch is attractive, your authored material stylish. nonetheless, you command get got an nervousness over that you wish be delivering the following. unwell unquestionably come further formerly again since exactly the same nearly very often inside case you shield this increase. Stream2Watch

    ReplyDelete
  4. Salutation, maybe this is not on theme but anyway, I have been reading about your site and it looks truly tidy. impassioned about your activity. I’m building a new blog and hard put to make it appear great, and provide really good content. I have revealed much on your site and I look forward to more updates and will be coming back. Stream2Watch

    ReplyDelete
  5. These incorporate Apple, which gives QuickTime just as the HTML5-based innovation to arrive at iOS gadgets; Adobe with Flash; and Microsoft with Windows Media and Silverlight. In the beginning of streamingenter mytv code

    ReplyDelete

Real Time Web Analytics