在 MVC Web API 中返回一个值,然后运行日志代码

本文关键字:一个 然后 代码 运行日志 Web MVC API 返回 | 更新日期: 2023-09-27 18:36:43

下面是一个非常简单的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 中返回一个值,然后运行代码? 在我的特殊情况下,我想事后记录,但没有理由让数据库日志记录占用客户端接收响应的时间,因为它不会影响响应。

在 MVC Web API 中返回一个值,然后运行日志代码

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

虽然 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;
     }
 }
相关文章: