Search code examples
vb.netasp.net-web-apiwebformsdownloadasp.net-apicontroller

How to resolve 'Server cannot set status after HTTP headers have been sent' error when trying to download files via web api call?


When I use a web api call to download a file, I can easily download the file. The only problem is I get Server cannot set status after HTTP headers have been sent in my error logs. Sorry if this is potentially a duplicate question, but none of the answers here have helped me.

<a href="/api/DownloadDocumentById?documentId=<%=doc.Id %>" download>
                                    <i class="fa fa-download text-primary"></i>
                                </a>
<HttpGet>
    <ActionName("DownloadDocumentById")>
    Public Function DownloadDocumentById(documentId As Integer)
        Dim document = xxxxxxxx

        Dim context = HttpContext.Current

        context.Response.ContentType = document.Type

        context.Response.OutputStream.Write(document.Content, 0, document.Size)

        context.Response.AddHeader("Content-Disposition", Baselib.FormatContentDispositionHeader($"{document.Name}"))

        context.Response.AddHeader("Last-Modified", DateTime.Now.ToLongDateString())

        context.Response.Flush()

        context.Response.End()

        Return HttpStatusCode.OK // Have also tried to create a sub without returning a value
    End Function

As mentioned before, I can easily download the document, but still IIS logs Server cannot set status after HTTP headers have been sent error. Again sorry of this is a duplicate question. Hope Someone can help me.


Solution

  • First and foremost, I think you should add all headers before you start writing the actual output/content. With a buffered stream (which is what I'm about to suggest) this should not make a difference and is mostly only semantical, but since headers should be added before writing the content (content is always last) it may avoid similar issues in the future if you decide to use an unbuffered stream.

    Therefore, I suggest you re-order your code accordingly:

    context.Response.ContentType = document.Type
    
    context.Response.AddHeader("Content-Disposition", Baselib.FormatContentDispositionHeader($"{document.Name}"))
    context.Response.AddHeader("Last-Modified", DateTime.Now.ToLongDateString())
    
    context.Response.OutputStream.Write(document.Content, 0, document.Size)
    

    Now if you use an unbuffered stream the content will be sent to the client immediately when you call OutputStream.Write(), therefore in order to set the HTTP result afterwards you need to make sure that your entire response is buffered so that it isn't sent until your internal request (action and controller) has finished executing. This can be done by setting Response.BufferOutput to True before outputting anything:

    context.Response.BufferOutput = True
    
    context.Response.ContentType = document.Type
    
    'The rest of the code...
    

    Finally, you need to remove the calls to Response.Flush() and Response.End() as they empty the buffer prematurely and writes everything to the client before you even get to return the status code.

    New code:

    (...)
    
    context.Response.BufferOutput = True
    
    context.Response.ContentType = document.Type
    
    context.Response.AddHeader("Content-Disposition", Baselib.FormatContentDispositionHeader($"{document.Name}"))
    context.Response.AddHeader("Last-Modified", DateTime.Now.ToLongDateString())
    
    Return HttpStatusCode.OK