how to return a xlsx file using memorystream web api

texas697 picture texas697 · Aug 26, 2015 · Viewed 10.7k times · Source

I am successfully making a web request, creating xlsx file and saving it to a directory. I would like to stream it and return it to the client without actually saving it on the server.

this is what i am currently using, it works fine

  private string generateStudyTemplate(string requestId)
    {
        var serviceUrl = ConfigurationManager.AppSettings["serviceUrl"];

        // create webRequest
        HttpWebRequest webRequest = createWebRequest(serviceUrl + "/" + requestId);

        // begin async call to web request
        IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);

        // suspend this thread until call is complete. You might want to
        // do something usefull here like update your UI
        asyncResult.AsyncWaitHandle.WaitOne();

        // get the response from the completed web request
        var filename = string.Format("{0}.xlsx", "NewWorkbook");
        string physicalPath = HttpContext.Current.Server.MapPath("/FilesForExport");
        string relativePath = Path.Combine(physicalPath, filename).Replace("\\", "/");
        var filePath = relativePath;

       // var filePath = directory + "\\NewWorkbook.xlsx";
        using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
        {
            var str = webResponse.GetResponseStream();
            var inBuf = new byte[webResponse.ContentLength];
            var bytesToRead = Convert.ToInt32(inBuf.Length);
            var bytesRead = 0;
            while (bytesToRead > 0)
            {
                var n = str.Read(inBuf, bytesRead, bytesToRead);
                if (n == 0)
                    break;

                bytesRead += n;
                bytesToRead -= n;
            }

            var fstr = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
            fstr.Write(inBuf, 0, bytesRead);
            fstr.Close();
        }

        return filePath;
    }

    private static HttpWebRequest createWebRequest(string url)
    {
        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
        webRequest.ContentType = "text/xml;charset=\"utf-8\"";
        webRequest.Accept = "text/xml";
        webRequest.Method = "GET";
        return webRequest;
}

Here is what I have put together from some other examples.

 public HttpResponseMessage GenerateMarketStudyResult([FromBody]Result id)
    {
        if (id.requestId == null)
        {
            throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest));
        }
        var serviceUrl = ConfigurationManager.AppSettings["serviceUrl"];

        var streamContent = new PushStreamContent((outputStream, httpContext, transportContent) =>
        {
            try
            {
                HttpWebRequest webRequest = createWebRequest(serviceUrl + "/" + id.requestId);
                IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);
                asyncResult.AsyncWaitHandle.WaitOne();

                using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
                {
                    using (MemoryStream memoryStream = new MemoryStream())
                    {
                        var str = webResponse.GetResponseStream();
                        var inBuf = new byte[webResponse.ContentLength];
                        var bytesToRead = Convert.ToInt32(inBuf.Length);
                        var bytesRead = 0;
                        while (bytesToRead > 0)
                        {
                            var n = str.Read(inBuf, bytesRead, bytesToRead);
                            if (n == 0)
                                break;

                            bytesRead += n;
                            bytesToRead -= n;
                        }
                        memoryStream.Write(inBuf, 0, bytesRead);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                outputStream.Close();
            }
        });
        streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        streamContent.Headers.ContentDisposition.FileName = "reports.xlsx";

        var result = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = streamContent
        };
        return result;

    }

I am not getting any exceptions, but the xlsx file is returning with 0bytes.

The breakpoint here

memoryStream.Write(inBuf, 0, bytesRead);

here is the javascript serving the returned file

   $http.post('/api/GenerateMarketStudyResult/', Result, { responseType: 'arraybuffer' })
                   .success(function (response, status, headers, config) {
                       saveAs(new Blob([response], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" }), 'reports.xlsx');
                   })

shows that the

bytesRead = 112336

Answer

Tamas Hegedus picture Tamas Hegedus · Aug 26, 2015

I assume that you write a web service which acts as a proxy between your JavaScript and some third party web service.

First of all, if you use at least .NET 4.0, you can use the Stream.CopyTo method to copy a stream to another. So instead of this:

using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
{
    var str = webResponse.GetResponseStream();
    var inBuf = new byte[webResponse.ContentLength];
    var bytesToRead = Convert.ToInt32(inBuf.Length);
    var bytesRead = 0;
    while (bytesToRead > 0)
    {
        var n = str.Read(inBuf, bytesRead, bytesToRead);
        if (n == 0) break;
        bytesRead += n;
        bytesToRead -= n;
    }
    var fstr = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write);
    fstr.Write(inBuf, 0, bytesRead);
    fstr.Close();
}

You could write:

using (var webResponse = webRequest.EndGetResponse(asyncResult))
using (var fstr = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write)) 
{
    webResponse.GetResponseStream().CopyTo(fstr);
}

Second, assuming you use WCF to build a web service, you could pipe the response to a memory stream, and return it. (dont forget to reset the stream's position after you finished writing)

Put together:

[WebGet(UriTemplate = "GenerateMarketStudyResult/{id}")]
public Stream GenerateMarketStudyResult(string id)
{
    var serviceUrl = ConfigurationManager.AppSettings["serviceUrl"];

    // create webRequest
    HttpWebRequest webRequest = createWebRequest(serviceUrl + "/" + id);

    // begin async call to web request
    IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);

    // suspend this thread until call is complete. You might want to
    // do something usefull here like update your UI
    asyncResult.AsyncWaitHandle.WaitOne();

    var memStream = new MemoryStream();
    // var filePath = directory + "\\NewWorkbook.xlsx";
    using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
    {
        webResponse.GetResponseStream().CopyTo(memStream);
    }
    memStream.Position = 0;

    var response = WebOperationContext.Current.OutgoingResponse;
    response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    response.ContentLength = (int)memStream.Length;
    return memStream;
}

EDIT: You didn't copy the contents of the memoryStream to the outputStream. You can omit the memoryStream. Try this:

public HttpResponseMessage GenerateMarketStudyResult([FromBody]Result id)
{
    if (id.requestId == null)
    {
        throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest));
    }
    var serviceUrl = ConfigurationManager.AppSettings["serviceUrl"];

    var streamContent = new PushStreamContent((outputStream, httpContext, transportContent) =>
    {
        try
        {
            HttpWebRequest webRequest = createWebRequest(serviceUrl + "/" + id.requestId);
            IAsyncResult asyncResult = webRequest.BeginGetResponse(null, null);
            asyncResult.AsyncWaitHandle.WaitOne();

            using (WebResponse webResponse = webRequest.EndGetResponse(asyncResult))
            {
                webResponse.GetResponseStream().CopyTo(outputStream);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            outputStream.Close();
        }
    });
    streamContent.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    streamContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    streamContent.Headers.ContentDisposition.FileName = "reports.xlsx";

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = streamContent
    };
    return result;
}