Search code examples
c#asp.net-web-api2httpresponsememorystreamsystem.net.httpwebrequest

how to return a xlsx file using memorystream web api


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


Solution

  • 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;
    }