asp.netvb.nethttp-headershttp-get

How do I avoid the status error after Http Headers sent?


I have an API called using HttpGet. The API validates the call and returns a report file directly (Excel or CSV). If the validation fails, it returns a Json Result object with an error code and message.

This is the API function:

<HttpGet>
Public Function AdvReport(APIKey As String, SubID As String) As Object
    Dim lRes As New Result

    Try
        ' Check Key
        If APIKey Is Nothing OrElse APIKey.Length = 0 Then Return lRes.Init(Result.ResultCode.NoAction, "API Key is missing")
        lRes = APIKeyClass.APIHit(APIKey)
        If lRes.NoSuccess Then Return lRes

        ' Get RepID
        If String.IsNullOrEmpty(SubID) Then Return lRes.Init(Result.ResultCode.NoAction, "SubID is missing")
        SubID = Encrypter.Decrypt(SubID)
        If SubID.Length = 0 OrElse Not IsNumeric(SubID) Then Return lRes.Init(Result.ResultCode.NoAction, "Invalid SubID")

        ' Load Advanced Report Subscription 
        Dim lSub As New AdvRepSubClass
        lRes = lSub.Load(CInt(SubID))
        If lRes.NoSuccess Then Return lRes

        If Not lSub.API Then Return lRes.Init(Result.ResultCode.NoAction, "Report not enabled for API access")

        ' Generate report
        If lRes.IsSuccess Then lRes = lSub.GetReportBin(Nothing, False)

        If lRes.IsSuccess Then LogClass.Log(Nothing, "API AdvReport", "Report Name: " + lSub.Title + "; Result: " + If(lRes.IsSuccess, "Success; Rows Affected: " + lRes.RowsAffected.ToString, lRes.Info) + "; API Key: " + APIKey, LogClass.LogTable.AdvRep, lSub.AdvRepID)

        ' Download it
        Try
            DownloadByteArray(CType(lRes.RetInfo, Byte()), GetReportFileName(lSub.Title, lSub.RepType), False)
            Return Nothing
        Catch ex As Exception
            lRes.Init(Result.ResultCode.Err, ex.Message)
        End Try

        Return lRes

    Catch ex As Exception
        Return lRes.Init(Result.ResultCode.Err, ex.Message)
    End Try
End Function

This is the code for DownloadByteArray()

Public Sub DownloadByteArray(aContent As Byte(), aFileName As String, aPreview As Boolean)

    Try
        HttpContext.Current.Response.Buffer = False
        HttpContext.Current.Response.Clear()
        HttpContext.Current.Response.AddHeader("Content-Disposition", If(aPreview, "inline", "attachment") + "; filename=""" & aFileName & """")
        If aPreview Then HttpContext.Current.Response.ContentType = Helper.GetContentType(aFileName) Else HttpContext.Current.Response.ContentType = "application/octet-stream"
        HttpContext.Current.Response.AddHeader("Content-Length", aContent.Length.ToString())
        HttpContext.Current.Response.BinaryWrite(aContent)
        HttpContext.Current.Response.Flush()
        HttpContext.Current.Response.SuppressContent = True
        HttpContext.Current.ApplicationInstance.CompleteRequest()
    Catch ex As Exception
        Dim lUserID As String = If(HttpContext.Current.Session IsNot Nothing AndAlso HttpContext.Current.Session("UserID") IsNot Nothing, HttpContext.Current.Session("UserID").ToString, Nothing)
        LogClass.Log(Nothing, lUserID, "DownloadByteArray", String.Concat("Doc: ", aFileName, "; Ex: ", ex.ToString()))
    End Try
End Sub

The code runs fine and the file is downloaded successfully on the browser using the API call. The issue is that the global application error handler is also fired after the API call with this error:

System.Web.HttpException (0x80004005): Server cannot set status after HTTP headers have been sent. at System.Web.HttpResponse.set_StatusCode(Int32 value) at System.Web.Http.WebHost.HttpControllerHandler.<CopyResponseStatusAndHeadersAsync>d__31.MoveNext()

I understand what the error means. I assume the function is trying to set a return value to Nothing after the Http headers have been sent. How do I resolve this and somehow "stop" the function after the file has been sent?


Solution

  • I managed to solve the issue by not calling the common download function and writing the file return inline only.

    ' If file required, download it
    Try
        Dim lResult As New HttpResponseMessage(HttpStatusCode.OK)
        lResult.Content = New ByteArrayContent(CType(lRes.RetInfo, Byte()))
        lResult.Content.Headers.ContentDisposition = New ContentDispositionHeaderValue("attachment") With {
            .FileName = GetReportFileName(lSub.Title, lSub.RepType)
        }
        lResult.Content.Headers.ContentType = New MediaTypeHeaderValue("application/octet-stream")
        Return lResult
    Catch ex As Exception
        lRes.Init(Result.ResultCode.Err, ex.Message)
    End Try