2013-10-24 27 views
0

下面是一個非常簡單的HelloWorld API方法返回在MVC的Web API的值,然後運行日誌代碼之後

[HttpGet] 
    [Route("api/helloworld")] 
    public string SayHello() 
    { 
     try 
     { 
      return "Hello World - API Version 1 - " + DateTime.Now.ToLongTimeString(); 
     } 
     finally 
     { 
      Log("I'd like logging to not hold up the string from getting returned"); 
     } 
    } 

不幸的是,最後的代碼不會以這種方式工作,所以在這個日誌方法大小寫會阻止字符串返回,直到日誌完成。

是否有可能在MVC Web API中返回一個值,然後再運行代碼?在我的特殊情況下,我希望事後記錄,但沒有理由讓數據庫日誌記錄花費時間讓客戶端收到響應,因爲它不會影響響應。

+0

[動作濾波器(http://www.asp.net/mvc/tutorials/hands-on-labs/aspnet-mvc-4-custom-action-filters)是處理這種事情的首選機制。 – HackedByChinese

+0

@HackedByChinese - 由於沒有「發送後請求」事件,動作過濾器不會提供幫助。 (加上你的鏈接到常規的過濾器,而不是WebAPI的)。 –

+0

等待;你確定日誌消息沒有被寫入? C#語言規範指出,除非線程上發生異步異常,否則最終將始終執行。 http://stackoverflow.com/questions/345091/will-code-in-a-finally-statement-fire-if-i-return-a-value-in-a-try-block –

回答

2

是的,但您需要在單獨的線程上運行它。

1

儘管WebAPI在過濾器上沒有OnRequestExecuted方法,但您可能正在尋找它,我認爲過濾器仍然是正確的方法。

您需要的是一個過濾器,它與派生類ObjectContent類組合,將您的後請求邏輯推遲到寫入響應之後。我使用這種方法在請求開始時自動創建NHibernate會話和事務,並在請求完成時提交它們,這與您在評論中描述的類似。請記住,這大大簡化以說明我的答案。

public class TransactionAttribute : ActionFilterAttribute 
{ 
    public override void OnActionExecuting(HttpActionContext actionContext) 
    { 
     // create your connection and transaction. in this example, I have the dependency resolver create an NHibernate ISession, which manages my connection and transaction. you don't have to use the dependency scope (you could, for example, stuff a connection in the request properties and retrieve it in the controller), but it's the best way to coordinate the same instance of a required service for the duration of a request 
     var session = actionContext.Request.GetDependencyScope().GetService(typeof (ISession)); 
     // make sure to create a transaction unless there is already one active. 
     if (!session.Transaction.IsActive) session.BeginTransaction(); 

     // now i have a valid session and transaction that will be injected into the controller and usable in the action. 
    } 

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) 
    { 
     var session = actionExecutedContext.Request.GetDependecyScope().GetService(typeof(ISession)); 
     var response = actionExecutedContext.Response; 

     if (actionExecutedContext.Exception == null) 
     { 
      var content = response.Content as ObjectContent; 

      if (content != null) 
      { 
       // here's the real trick; if there is content that needs to be sent to the client, we need to swap the content with an object that will clean up the connection and transaction AFTER the response is written. 
       response.Content = new TransactionObjectContent(content.ObjectType, content.Value, content.Formatter, session, content.Headers); 
      } 
      else 
      { 
       // there is no content to send to the client, so commit and clean up immediately (in this example, the container cleans up session, so it is omitted below) 
       if (session.Transaction.IsActive) session.Transaction.Commit(); 
      } 
     } 
     else 
     { 
      // an exception was encountered, so immediately rollback the transaction, let the content return unmolested, and clean up your session (in this example, the container cleans up the session for me, so I omitted it) 
      if (session.Transaction.IsActive) session.Transaction.Rollback(); 
     } 
    } 
} 

而神奇的事情發生在這ObjectContent衍生物。它的責任是將對象流式傳輸到響應,支持異步操作,並在響應發送完成後執行某些操作。你可以添加你的日誌記錄,db,無論在這裏。在這個例子中,它只是在成功編寫響應之後提交一個事務。

public class TransactionObjectContent : ObjectContent 
{ 
    private ISession _session; 

    public TransactionObjectContent(Type type, object value, MediaTypeFormatter formatter, ISession session, HttpContentHeaders headers) 
     : base(type, value, formatter) 
    { 
     _session = session; 

     foreach (var header in headers) 
     { 
       response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value); 
     } 
    } 

    protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context) 
    { 
     await base.SerializeToStreamAsync(stream, context); // let it write the response to the client 
     // here's the meat and potatoes. you'd add anything here that you need done after the response is written. 
     if (_session.Transaction.IsActive) _session.Transaction.Commit(); 
    } 

    protected override void Dispose(bool disposing) 
    { 
     base.Dispose(disposing); 
     if (disposing) 
     { 
      if (_session != null) 
      { 
       // if transaction is still active by now, we need to roll it back because it means an error occurred while serializing to stream above. 
       if (_session.Transaction.IsActive) _session.Transaction.Rollback(); 
       _session = null; 
      } 
     } 
    } 
} 

現在,您可以在全局過濾器中註冊過濾器,也可以將其直接添加到操作或控制器中。您不必爲了在另一個線程中的每個操作中執行邏輯而不斷複製和粘貼冗餘代碼;該邏輯只會自動應用於您使用過濾器定位的每個動作。更乾淨,更容易,乾燥。

實施例的控制器:

[Transaction] // will apply the TransactionFilter to each action in this controller 
public DoAllTheThingsController : ApiController 
{ 
    private ISession _session; 


    public DoAllTheThingsController(ISession session) 
    { 
      _session = session; // we're assuming here you've set up an IoC to inject the Isession from the dependency scope, which will be the same as the one we saw in the filter 
    } 

    [HttpPost] 
    public TheThing Post(TheThingModel model) 
    { 
      var thing = new TheThing(); 
      // omitted: map model to the thing. 


      // the filter will have created a session and ensured a transaction, so this all nice and safe, no need to add logic to fart around with the session or transaction. if an error occurs while saving, the filter will roll it back. 
      _session.Save(thing); 

     return thing; 
    } 
} 
相關問題