2011-09-09 188 views
1

摘要從遠程Web服務

我需要檢索存儲在父應用程序從一個鏈接的子應用程序的客戶端附件返回附件。附件可通過Web服務調用在父應用程序中獲得 - 該應用程序返回標準FileContentResult,其內容類型爲「application/octet-stream」。我能想到的最好方法是通過WebRequest檢索,並將生成的響應流傳遞給FileStreamResult,儘管我有一些替代方法可用。

有沒有人知道是否在製作WebRequest時,一旦響應的第一部分被返回或緩衝後立即響應流變爲可用,所以我沒有得到響應,直到檢索到所有數據?

除了下面完整問題中列出的那些選項以外,還有其他選擇嗎? (除了將附件保留在兒童和父母的數據庫中 - 我真的不想這樣做,因爲我需要定期同步它們)。

TLDR版本

我有過一個RESTful網絡服務,通信的兩個相關應用。父應用程序維護可能具有附件的實體的集合。例如,一個請求可能有一個Excel電子表格作爲附件。實體及其附件存儲在數據庫中,使用與訪問請求相同的邏輯來控制對附件的訪問。也就是說,如果您無法查看請求,則無法下載附件。

在子應用程序中,我爲分配給特定機構的實體維護一些集成粘合劑 - 該應用程序用於在我們的董事會和每個董事會學校之間進行通信。我不想維護和同步整個實體/附件。我只想維護足夠的信息以允許我連接父應用程序中的Web服務,並獲取子應用程序的特定實例有權訪問的實體的詳細信息。

這適用於實體數據本身。數據量很小,子應用程序中的緩衝開銷並不會在訪問數據時出現明顯的延遲。如有必要,我可以在本地緩存數據以避免性能損失。

我擔心的是附件。我已經考慮了三種不同的機制來提供從子應用程序的客戶端訪問附件。

  1. 生成一次性使用令牌和關聯的url,允許客戶端直接從父應用程序下載附件。令牌生成Web服務調用將確保子應用程序的用戶應該有權訪問該附件。這樣做的缺點是你只能在客戶端點擊鏈接一次。再次點擊會導致錯誤,而不是獲取附件。

  2. 緩衝子應用中的附件。在這種情況下,我會提供一個控制器/操作以將附件下載到子應用程序中,然後調用Web服務方法來獲取附件並讓子應用程序將附件作爲FileContentResult發送。這消除了只能點擊鏈接一次的問題,但附件可能相當大,並且緩衝子應用程序中的數據可能會使下載附件的時間增加一倍,並且更糟糕的是,會在附件下載開始。

  3. 鏈接在子應用程序中,但直接將來自Web服務請求的流提供給FileStreamResult。對我來說,這看起來是FileStreamResult讀入塊的最佳選擇,而不是在將所有數據發送到客戶端之前都可用。我在這裏可以看到的唯一缺點是我無法直接處理WebResponse,因爲直到我的操作返回後纔會執行FileStreamResult。

以下是我對爲(2)和(3)API包裝代碼代碼:

private class ResponseModel<T> : IDisposable 
{ 
    public T Model { get; set; } 
    public WebResponse Response { get; set; } 

    private bool Disposed { get; set; } 
    private void Dispose(bool disposing) 
    { 
     if (!Disposed) 
     { 
      if (disposing) 
      { 
       ((IDisposable)this.Response).Dispose(); 
      } 
      Disposed = true; 
     } 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
    } 
} 

private ResponseModel<T> GetAttachmentResponse<T>(long id) where T : IDownloadModel, new() 
{ 
    var request = GetRequest(string.Format("{0}/api/getattachment/{1}/{2}", this.BaseUrl, this.Key, id)); 

    var response = request.GetResponse(); 
    var model = (T)Activator.CreateInstance<T>(); 
    var contentDisposition = response.Headers["Content-Disposition"]; 
    if (!string.IsNullOrEmpty(contentDisposition)) 
    { 
     var filename = contentDisposition.Split(new[] { ';', ' ' }, StringSplitOptions.RemoveEmptyEntries) 
             .SingleOrDefault(s => s.StartsWith("filename", StringComparison.OrdinalIgnoreCase)); 
     if (!string.IsNullOrEmpty(filename)) 
     { 
      model.Name = filename.Split('=').Skip(1).FirstOrDefault(); 
     } 
    } 
    if (string.IsNullOrEmpty(model.Name)) 
    { 
     model.Name = "untitled"; 
    } 

    return new ResponseModel<T> { Model = model, Response = response }; 
} 

public FileDownloadModel GetAttachment(long id) 
{ 
    using (var response = GetAttachmentResponse<FileDownloadModel>(id)) 
    { 
     var reader = new BinaryReader(response.Response.GetResponseStream()); 
     response.Model.Content = reader.ReadBytes((int)response.Response.ContentLength); 
     return response.Model; 

    } 
} 

public FileStreamDownloadModel GetAttachmentStream(long id) 
{ 
    // since we're returning the stream, we can't dispose of the response when done. 
    var response = GetAttachmentResponse<FileStreamDownloadModel>(id); 
    response.Model.Stream = response.Response.GetResponseStream(); 
    return response.Model; 
} 


public interface IDownloadModel 
{ 
    string ContentType { get; } 
    string Name { get; set; } 
} 

模型類

public class FileDownloadModel : IDownloadModel 
{ 
    public byte[] Content { get; set; } 
    public string Name { get; set; } 
    public string ContentType { get { return "application/octet-stream"; } } 
} 

public class FileStreamDownloadModel : IDownloadModel 
{ 
    public Stream Stream { get; set; } 
    public string Name { get; set; } 
    public string ContentType { get { return "application/octet-stream"; } } 
} 
+0

Bueller? Bueller?我所得到的就是這個蟋蟀。 – tvanfosson

+0

如果兩個應用程序都在您的控制之下,您可以允許來自其他域的跨站點腳本,並使用Javascript顯示您喜歡的任何附件。請參閱:HttpContext.Current.Response.AddHeader(「Access-Control-Allow-Origin」,「」); – wilsotc

回答

0

我會建議一個選項1的變體[稱爲選項1(a)]。

不是生成一次性令牌,而是「借用」MVC AntiForgeryToken類,並讓父應用程序將自定義令牌和cookie返回給子應用程序,以便將其包含在返回給用戶的表單中。

如果子應用程序可能在單個頁面上有多個文檔的鏈接,請求令牌信息時,讓子應用程序提交一個唯一標識符(標識來自用戶的頁面請求)作爲請求的一部分。然後,您可以使用此標識符生成令牌,並且可以將標識符存儲爲驗證過程的一部分。這將爲您提供一個多用途標記,對頁面上的每個鏈接都是唯一的。

在獨特的標識符上拍一段過期時間,你應該很好。

+0

我喜歡使用過期日期而不是使其成爲一次性唯一標記的想法。對於如何處理與其他人分享鏈接的用例,您有什麼建議嗎?假定後一個人被授權使用該應用程序並使用該鏈接。我並非試圖解決授權人員與未經授權人員分享附件的問題;一旦他們下載它,我認爲他們不會不恰當地分享它,但他們可能想要與授權人員分享鏈接。 – tvanfosson

+0

如果鏈接令牌基於AntiForgeryToken類,則它不能共享,因爲它需要cookie進行驗證。如果授權用戶被允許共享鏈接,那麼只需將您的一次性使用令牌轉換爲即將到期的令牌。但是,我不希望允許共享鏈接,而是希望讓用戶能夠與下載鏈接共享頁面鏈接,因爲這會加強您的安全模型。 – counsellorben