0

下面是一个非常简单的 HelloWorld 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");
        }
    }

不幸的是,finally 代码不能以这种方式工作,因此这种情况下的 log 方法会阻止字符串返回,直到日志完成之后。

是否可以在 MVC Web API 中返回一个值,然后运行代码?在我的特殊情况下,我想在之后记录,但是数据库记录没有理由占用时间让客户端接收响应,因为它不会影响响应。

4

2 回答 2

2

是的,但您需要在单独的线程上运行它。

于 2013-10-24T20:54:57.493 回答
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衍生品中。它的职责是将对象流式传输到响应,支持异步操作,并在响应发送下来后做一些事情。您可以在此处添加您的日志记录、数据库等。在这个例子中,它只是在成功写入响应后提交一个事务。

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;
     }
 }
于 2013-10-25T05:23:15.483 回答