2013-11-04 26 views
0

我使用HttpClient將文件上載到要求Content-MD5頭的API。如何在發送前從HttpClient獲得完整的請求體,以便我可以在內容上運行MD5並在請求頭中使用它?請注意,我還需要在多部分表單數據(即Content-Disposition)和每個部分中的所有其他標題之間包含標題。如何在上傳文件時使用HttpClient獲取請求主體?

我使用下面的代碼,從this answer拍攝。

private System.IO.Stream Upload(string url, string param1, Stream fileStream, byte [] fileBytes) 
{ 
    HttpContent stringContent = new StringContent(param1); 
    HttpContent fileStreamContent = new StreamContent(fileStream); 
    HttpContent bytesContent = new ByteArrayContent(fileBytes); 
    using (var client = new HttpClient()) 
    using (var formData = new MultipartFormDataContent()) 
    { 
     formData.Add(stringContent, "param1", "param1"); 
     formData.Add(fileStreamContent, "file1", "file1"); 
     formData.Add(bytesContent, "file2", "file2"); 
     var response = client.PostAsync(url, formData).Result; 
     if (!response.IsSuccessStatusCode) 
     { 
      return null; 
     } 
     return response.Content.ReadAsStreamAsync().Result; 
    } 
} 

回答

2

現在,我承認我沒有測試過這段代碼,因爲我沒有測試站點設置。但是,我已經測試過在LINQPad中發佈數據,並且代碼沒有錯誤,並且MD5哈希值已設置。以下內容適用於您想要執行的操作:

private System.IO.Stream Upload(string url, string param1, Stream fileStream, byte[] fileBytes) 
{ 
    HttpContent stringContent = new StringContent(param1); 
    HttpContent fileStreamContent = new StreamContent(fileStream); 
    HttpContent bytesContent = new ByteArrayContent(fileBytes); 

    using (HttpClient client = new HttpClient()) 
    { 
     using (MultipartFormDataContent formData = new MultipartFormDataContent()) 
     { 
      formData.Add(stringContent, "param1", "param1"); 
      formData.Add(fileStreamContent, "file1", "file1"); 
      formData.Add(bytesContent, "file2", "file2"); 

      using (MD5 md5Hash = MD5.Create()) 
      { 
       formData.Headers.ContentMD5 = md5Hash.ComputeHash(formData.ReadAsByteArrayAsync().Result); 
      } 

      var response = client.PostAsync(url, formData).Result; 
      if (!response.IsSuccessStatusCode) 
      { 
       return null; 
      } 

      return response.Content.ReadAsStreamAsync().Result; 
     } 
    } 
} 
2

您的文件有多大?亞當回答的問題是,它將文件的全部內容緩衝在內存中。這可能會導致您的程序耗盡大容量文件的內存,或者由於磁盤交換過多而導致性能下降。

實際上,我發現即使是MultipartFormDataContent.ReadAsStreamAsync()也會將整個內容緩衝到內存中(可能是通過調用MultipartFormDataContent.LoadIntoBufferAsync())。使用StreamContent.ReadAsStreamAsync()時,似乎並不存在這個問題,所以如果您遇到內存問題,它似乎是唯一的解決方案是編寫您自己的MultipartFormDataContent實現,該實現不會緩衝整個內容,而是利用StreamContent.ReadAsStreamAsync()。

注意,我發現MultipartFormDataContent.CopyToAsync()將不會在緩衝存儲器的全部內容所提供的接收流沒有。編寫一個Stream實現可能是值得的,該實現用作一種管道,其中任何寫入的字節都會立即被md5Hash.ComputeHash(Stream)使用。

編輯:這是我在.NET 4.0的經驗。我聽說.NET 4.5在客戶端緩衝方面的表現有所不同,所以我不確定MultipartFormDataContent.ReadAsStreamAsync()如何執行。

1

通用的方式來處理這類與HttpClient的是使用HttpMessageHandler你可能其他工作如做添加授權頭,對消息簽名等

我還重新寫了這個任務使用基於語法,因爲它更ideomatic的HttpClient - 來電者可以撥打。結果需要。

private await Task<System.IO.Stream> Upload(string url, string param1, Stream fileStream, byte[] fileBytes) 
{ 
    HttpContent stringContent = new StringContent(param1); 
    HttpContent fileStreamContent = new StreamContent(fileStream); 
    HttpContent bytesContent = new ByteArrayContent(fileBytes); 

    var handler = new HttpClientHandler(); 
    var md5Handler = new RequestContentMd5Handler(); 
    md5Handler.InnerHandler = handler; 

    using (HttpClient client = new HttpClient(md5Handler)) 
    { 
     using (MultipartFormDataContent formData = new MultipartFormDataContent()) 
     { 
      formData.Add(stringContent, "param1", "param1"); 
      formData.Add(fileStreamContent, "file1", "file1"); 
      formData.Add(bytesContent, "file2", "file2"); 

      using (var response = await client.PostAsync(url, formData)) 
      { 
       if (!response.IsSuccessStatusCode) 
       { 
        return null; 
       } 

       return await response.Content.ReadAsStreamAsync(); 
      } 
     } 
    } 
} 

而且,它的做法普遍較差每個請求重新創建HttpClient的(見What is the overhead of creating a new HttpClient per call in a WebAPI client?等),但我離開這裏與問題的風格保持一致。

這裏所用的處理器...

/// <summary> 
/// Handler to assign the MD5 hash value if content is present 
/// </summary> 
public class RequestContentMd5Handler : DelegatingHandler 
{ 
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     if (request.Content == null) 
     { 
      return await base.SendAsync(request, cancellationToken); 
     } 

     await request.Content.AssignMd5Hash(); 

     var response = await base.SendAsync(request, cancellationToken); 

     return response; 
    } 
} 

而且擴展方法...

/// <summary> 
    /// Compute and assign the MD5 hash of the content. 
    /// </summary> 
    /// <param name="httpContent"></param> 
    /// <returns></returns> 
    public static async Task AssignMd5Hash(this HttpContent httpContent) 
    { 
     var hash = await httpContent.ComputeMd5Hash(); 

     httpContent.Headers.ContentMD5 = hash; 
    } 

    /// <summary> 
    /// Compute the MD5 hash of the content. 
    /// </summary> 
    /// <param name="httpContent"></param> 
    /// <returns></returns> 
    public static async Task<byte[]> ComputeMd5Hash(this HttpContent httpContent) 
    { 
     using (var md5 = MD5.Create()) 
     { 
      var content = await httpContent.ReadAsStreamAsync(); 
      var hash = md5.ComputeHash(content); 
      return hash; 
     } 
    } 

讓您輕鬆進行單元測試的各個部分。

相關問題