PushStreamContent使用SqlDataReader.ExecuteReaderAsync将CSV或分隔文件流式传输到客户端

时间:2015-05-08 14:04:44

标签: vb.net asynchronous asp.net-web-api async-await sqldatareader

我尝试利用WebAPI将大量数据从SQL Server流式传输到分隔文件,而无需等待整个结果集从数据库返回。我对Async和Await非常可怕,并没有把我的头包裹起来。看来我应该使用PushStreamContent和SqlDataReader异步方法做什么。
这是我的DataAccessLayer:

   Public Async Function ExecuteToResponseStream(command As SqlCommand, responseStream As IO.Stream) As Threading.Tasks.Task
      Using responseStream
         Dim newConString As String = _conString
         Using con As SqlConnection = New SqlConnection(newConString)
            Await con.OpenAsync()
            'Add params
            command.Connection = con

            Using ms As IO.MemoryStream = New IO.MemoryStream
               Using ts As IO.StreamWriter = New IO.StreamWriter(ms)

                  Using reader As SqlDataReader = Await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess)
                     While Await reader.ReadAsync()
                        Dim loValues As New List(Of String)
                        For i = 0 To reader.FieldCount - 1
                           loValues.Add(reader(i).ToString)
                        Next
                        Await ts.WriteAsync(String.Join(vbTab, loValues.ToArray))
                        Await ms.CopyToAsync(responseStream)
                     End While
                  End Using
               End Using
            End Using
         End Using
      End Using
   End Function

我的存储库有一个签名为:     函数ExportAsync(id As String,responseStream As IO.Stream)As Task

最后,WebAPI客户端:

   Public Function Export(id As String) As HttpResponseMessage
      Dim resp As New HttpResponseMessage(HttpStatusCode.OK)
      Dim onStreamAvailable As Action(Of IO.Stream, HttpContent, TransportContext) = Async Sub(responseStream, content, context)
        Await repository.ExportAsync(rc, responseStream)
     End Sub

      resp.Content = New PushStreamContent(onStreamAvailable)
      resp.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue("application/octet-stream")
      resp.Content.Headers.ContentDisposition = New Headers.ContentDispositionHeaderValue("attachment")
      resp.Content.Headers.ContentDisposition.FileName = "Somefilename"

      Return resp
   End Function

客户端函数似乎正确退出并最终返回响应。对浏览器的最终响应确实看起来像一个附件,它是空的,我吮吸流,但似乎它不是以那种神奇的异步方式发生的。

另外,在我看来,这是一个有点简单的练习,虽然我无法在网上找到一个好的实现。也许我真的错了。

1 个答案:

答案 0 :(得分:0)

我发现了一些曾经/不起作用的东西。

<强>更新

存储库方法

Public Async Function ExportToCSV(inputParameters, stream As IO.Stream) As Threading.Tasks.Task
   'Build SqlCommand from inputParameters
   Await sqlClient.ReaderToDelimitedAsync(","c, _SqlCommand, stream)
End Function

自定义SqlClient包装器方法

   Async Function ReaderToDelimitedAsync(delimiter As Char, command As SqlCommand, stream As IO.Stream) As Threading.Tasks.Task
  Try

     Await command.Connection.OpenAsync
     Using rdr As SqlDataReader = Await command.ExecuteReaderAsync
        Using sw As New IO.StreamWriter(stream, Text.Encoding.GetEncoding("Windows-1252"))
           Dim los As New List(Of String)
           For i = 0 To rdr.FieldCount - 1
              los.Add(String.Format("{0}{1}{0}", ControlChars.Quote, rdr.GetName(i)))
           Next
           Await sw.WriteLineAsync(String.Join(delimiter, los.ToArray))
           Await sw.FlushAsync

           While Await rdr.ReadAsync
              Dim loVals As New List(Of String)
              For i = 0 To rdr.FieldCount - 1
                 Dim strVal As String = rdr.GetValue(i).ToString
                 strVal = strVal.Replace(ControlChars.Quote, Chr(39)).Trim(Chr(10), Chr(13), Chr(32)) '.Replace(Chr(13), " ").Replace(Chr(10), " ")
                 strVal = strVal.Replace(vbCrLf + vbCrLf, vbCrLf).Replace(vbCrLf + vbCrLf, vbCrLf)
                 strVal = strVal.Substring(0, Math.Min(strVal.Length, 32000))
                 loVals.Add(String.Format("{0}{1}{0}", ControlChars.Quote, strVal))
              Next
              Await sw.WriteLineAsync(String.Join(","c, loVals.ToArray))
              Await sw.FlushAsync()
           End While

        End Using
     End Using
  Catch ex As Exception
     Throw ex
  End Try
   End Function

使用它:

  <HttpPost, ActionName("Download"), ValidateAntiForgeryHeader(False)> Public Function DownloadReport(postparameters) As HttpResponseMessage

      Dim resp As New HttpResponseMessage(HttpStatusCode.OK)

         resp.Content = New PushStreamContent(Function(stream, http_content, transportcontextcontext)
                                                 Return repository.Export(postparameters, stream)
                                              End Function, "text/csv")
         resp.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue("text/csv")
     resp.Content.Headers.ContentDisposition = New Headers.ContentDispositionHeaderValue("attachment")
      resp.Content.Headers.ContentDisposition.FileName = reporttemplate.Filename

      Return resp

   End Function

我也尝试过:

   <HttpPost, ActionName("Download"), ValidateAntiForgeryHeader(False)> Public Function DownloadReport(postparameters) As HttpResponseMessage

      Dim resp As New HttpResponseMessage(HttpStatusCode.OK)

         resp.Content = New PushStreamContent(Async Function(stream, http_content, transportcontextcontext)
                                                 Await repository.Export(postparameters, stream)
                                              End Function, "text/csv")
         resp.Content.Headers.ContentType = New Headers.MediaTypeHeaderValue("text/csv")
     resp.Content.Headers.ContentDisposition = New Headers.ContentDispositionHeaderValue("attachment")
      resp.Content.Headers.ContentDisposition.FileName = reporttemplate.Filename

      Return resp

   End Function

问题是在SqlDataReader关闭(End Using语句)之前,下载不会在客户端启动。我的理解是PushStreamContent应该在遇到它的第一次Await时返回,随后对FlushAsync的调用应该开始将流传递给客户端。 我仍然很难理解这个Async Await模式。有什么想法吗?