Dot-Net

WCF - 如果伺服器拋出 FaultException,則中止從客戶端到伺服器的流

  • October 11, 2017

我們有一個 WCF 服務,它接受來自客戶端的流(客戶端將文件上傳到伺服器)。但是,如果伺服器在流之前或期間拋出 FaultException,客戶端只會繼續流直到結束,此時它會從伺服器接收到 FaultException - 浪費客戶端的時間和頻寬。

類似問題:

如何從伺服器端方法中止 WCF 文件上傳

採取以下(簡化的WCF服務)

Namespace JP_WCF
   <ServiceContract> _
   Public Interface IJP_WCF
       <OperationContract> _
       <FaultContract(GetType(JP_WCF_Fault))> _
       Sub UploadFile(request As JP_WCF_FileUpload)

       <OperationContract> _
       <FaultContract(GetType(JP_WCF_Fault))> _
       Function fakeError(ByVal int1 As Integer, ByVal int2 As Integer) As Integer

       <OperationContract> _
       <FaultContract(GetType(JP_WCF_Fault))> _
       Function Ping() As Date
   End Interface

   <MessageContract> _
   Public Class JP_WCF_FileUpload
       Implements IDisposable

       <MessageHeader(MustUnderstand:=True)> _
       Public FileName As String

       <MessageHeader(MustUnderstand:=True)> _
       Public Length As Long

       <MessageBodyMember(Order:=1)> _
       Public FileByteStream As System.IO.Stream

       Public Sub Dispose() Implements IDisposable.Dispose
           If FileByteStream IsNot Nothing Then
               FileByteStream.Close()
               FileByteStream = Nothing
           End If
       End Sub
   End Class

   <DataContract> _
   Public Class JP_WCF_Fault
       <DataMember> _
       Public Property EventID() As Integer
       <DataMember> _
       Public Property Message() As String
       <DataMember> _
       Public Property Description() As String

       Public Sub New(ByVal _EventID As Integer, ByVal _Message As String, ByVal _Description As String)
           Me.EventID = _EventID
           Me.Message = _Message
           Me.Description = _Description
       End Sub
   End Class

End Namespace

範例伺服器方法:

Try
   Dim sourceStream As Stream = request.FileByteStream
   Dim uploadFolder As String = "C:\upload\"
   Dim filePath As String = Path.Combine(uploadFolder, request.FileName)

   Using targetStream = New FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)
       sourceStream.CopyTo(targetStream)
       targetStream.Close()
       sourceStream.Close()
   End Using
Catch ex As Exception
   Throw New FaultException(Of JP_WCF_Fault)(New JP_WCF_Fault(8, ex.Message, ex.ToString), ex.Message)
End Try

範例客戶端方法:

Dim fileInfo As New System.IO.FileInfo(filePath)
Dim startTime As DateTime = DateTime.Now
Console.WriteLine("Starting V2 upload: " + DateTime.Now.ToString())

Dim JPCS As New JP_WCFService.JP_WCFClient()

Using stream As New System.IO.FileStream(filePath, System.IO.FileMode.Open, System.IO.FileAccess.Read)
   Using uploadStreamWithProgress As New JP_StreamWithProgress(stream)
       AddHandler uploadStreamWithProgress.ProgressChanged, AddressOf uploadStreamWithProgress_ProgressChanged
       Try
           JPCS.UploadFile(fileInfo.Name, fileInfo.Length, uploadStreamWithProgress)
       Catch ex As FaultException(Of JP_WCFService.JP_WCF_Fault)
           Console.WriteLine("Upload Error: " & ex.Detail.Message & " (EventID: " & ex.Detail.EventID.ToString & ")")
       End Try
   End Using
End Using
Dim endTime As DateTime = DateTime.Now
Dim durationInMS As Double = (endTime - startTime).TotalMilliseconds
Console.WriteLine(vbCr & "V2 Upload Completed: " + DateTime.Now.ToString() + " (" + durationInMS.ToString() + ")")
JPCS.Close()

網路配置

<system.serviceModel>
   <bindings>
       <customBinding>
           <binding name="JP_WCFBinding">
               <!-- maxReceivedMessageSize 600MB, maxBufferSize 2MB -->
               <binaryMessageEncoding compressionFormat="GZip" />
               <httpsTransport transferMode="Streamed" maxReceivedMessageSize="629145600" maxBufferSize="2097152"/>
           </binding>
       </customBinding>
   </bindings>
   <services>
       <service behaviorConfiguration="JP_WCFbehavior" name="JP_WCF.JP_WCFServices">
           <endpoint address="" binding="customBinding" bindingConfiguration="JP_WCFBinding" contract="JP_WCF.IJP_WCF"/>
       </service>
   </services>
   <behaviors>
       <serviceBehaviors>
           <behavior name="JP_WCFbehavior">
               <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
               <serviceDebug includeExceptionDetailInFaults="true" />
           </behavior>
       </serviceBehaviors>
   </behaviors>
   <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>

應用程序配置

<system.serviceModel>
   <bindings>
       <customBinding>
           <binding name="CustomBinding_IJP_WCF">
               <binaryMessageEncoding compressionFormat="GZip" />
               <httpsTransport transferMode="Streamed" />
           </binding>
       </customBinding>
   </bindings>
   <client>
       <endpoint address="https://dev-wcf.localhost/JP_WCF.svc"
           binding="customBinding" bindingConfiguration="CustomBinding_IJP_WCF"
           contract="JP_WCFService.IJP_WCF" name="CustomBinding_IJP_WCF" />
   </client>
</system.serviceModel>

您的客戶有雙工通道嗎?如果是這樣,那麼在上傳文件時回調客戶端合約以發送資訊是非常直接的。

如果沒有,那麼這裡的一種好方法是使用記憶體緩衝區將數據以塊的形式流式傳輸到伺服器。我在下面舉了幾個很好的例子。

要點是您將文件拆分為客戶端上的塊並將其逐塊發送到伺服器。如果任何塊失敗,那麼它可以重試該塊,或者優雅地失敗而不發送任何更多數據。

這些引用使用一個StreamWithProgress類來處理客戶端上的這個邏輯。希望這可以幫助。

程式碼項目參考

StreamWithProgress使用類的簡單實現

如果您擔心此呼叫的性能,您始終可以在流式傳輸之前進行伺服器呼叫以檢查此上傳的有效性。這樣,如果出現問題,您可以完全避免流式傳輸文件,並避免應用程序中的任何異常狀態(也很昂貴)。

因此,您將相對快速地訪問伺服器以驗證諸如

  1. 有效的文件位置
  2. 寫入該位置的權限
  3. 有效文件的大小
  4. 任何相關的業務規則

然後,您可以撥打電話,而無需嘗試使用異常來管理應用程序流。記住:**例外應該是針對特殊情況的。**這樣,如果您的應用程序確實引發了異常,則意味著發生了非常異常的事情並且速度下降更可口(因為這種情況在理論上非常罕見)。

引用自:https://stackoverflow.com/questions/46450310