2014-01-25 66 views
23

我有一個名爲Log的屬性,它嘗試將請求和響應的內容記錄到文本文件中。我已經把它放在我的控制器上以涵蓋所有的操作。在LogAttribute中,我以字符串形式讀取內容(ReadAsStringAsync),所以我不會丟失請求主體。Web Api請求內容在操作中爲空過濾器

public class LogAttribute : ActionFilterAttribute 
{ 
    // .. 
    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     // stuff goes here 
     var content = actionContext.Request.Content.ReadAsStringAsync().Result; 
     // content is always empty because request body is cleared 
    } 

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 
    { 
     // other stuff goes here 
     var content = actionContext.Request.Content.ReadAsStringAsync().Result; 
     // content is always empty because request body is cleared 
    } 

    // .. 
} 

另一方面,我把FromBody屬性放在我的動作參數類之前,以利用它的好處。

[Log] 
public class SomethingController 
{ 
    public HttpResponseMessage Foo([FromBody] myModel) 
    { 
     // something 
    } 
} 

問題是內容在ActionExecuting或ActionExecuted中總是爲空。

我認爲這是因爲FromBody在我的Log屬性之前運行,不像它們在代碼中的順序。再次,我認爲它是因爲根據動作參數(路由處理)爲請求找到最佳動作/控制器匹配。之後,我的請求主體被清除,因爲請求主體在WebApi中是非緩衝的。

我想知道是否有任何方法來改變FromBody屬性和我的Log屬性的運行時間順序?或其他解決問題的東西!我應該提到,我不想刪除FromBody並使用HttpRequestMessage而不是我的Model或類似的東西。

+1

除此之外,您的異步處理是錯誤的。使用.Result是危險的,你應該避免,如果possilbe。而是重寫允許您等待的OnActionExecutingAsync方法。 –

+0

@盧卡斯克其實這是寫的只是爲了解釋我的問題。但你是對的。 –

回答

25

請求主體是不可倒回的流;它只能被讀取一次。格式化程序已經讀取了流並填充了模型。我們無法在動作過濾器中再次讀取流。

你可以嘗試:

public class LogAttribute : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     var myModel = actionContext.ActionArguments["myModel"]; 

    } 

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 
    { 
     var myModel = actionContext.ActionArguments["myModel"]; 
    } 
} 

其實,ActionArguments只是一個字典,我們可以循環儘管它,如果我們要避免硬編碼的參數名稱("myModel")。當我們創建一個通用動作過濾器需要針對某些特定需求的一類相似對象時,我們可以讓我們的模型實現一個接口=>知道哪個參數是我們需要處理的模型,然而我們可以調用這些方法界面。

示例代碼:

public class LogAttribute : ActionFilterAttribute 
    { 
     public override void OnActionExecuting(HttpActionContext actionContext) 
     { 
      foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable))) 
      { 
       ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable 
       //do something with it. Maybe call model.log 
      } 
     } 

     public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 
     { 
      foreach(var argument in actionContext.ActionArguments.Values.Where(v => v is ILogable))) 
      { 
       ILogable model = argument as ILogable;//assume that only objects implementing this interface are logable 
       //do something with it. Maybe call model.log 
      } 
     } 
    } 
+0

感謝兄弟。如果我有多個參數,我不知道他們的名字,我該怎麼辦?在這個解決方案中,我應該設置一個「總是我的參數名稱是myModel」的合同?是? –

+0

@Reza Ahmadi:'ActionArguments'只是一本字典。你可以循環它。 –

+0

@Reza Ahmadi:通常情況下,當你想應用這種類型的過濾器來針對一類相似對象進行某些特定的操作時。你可以讓你的模型實現一個接口,當通過字典循環時,你有一個線索來表明這是你需要處理的模型。 (也許通過界面調用方法) –

9

這種方式爲我工作:

using (var stream = new MemoryStream()) 
{ 
    var context = (HttpContextBase)Request.Properties["MS_HttpContext"]; 
    context.Request.InputStream.Seek(0, SeekOrigin.Begin); 
    context.Request.InputStream.CopyTo(stream); 
    string requestBody = Encoding.UTF8.GetString(stream.ToArray()); 
} 

發回我,我的動作參數對象的觸發記錄或異常情況下的JSON表示。

找到已接受的答案here