【问题标题】:asp.net Web API most efficient way to replace a request response body to have a consistant error structureasp.net Web API 最有效的方法来替换请求响应正文以具有一致的错误结构
【发布时间】:2019-01-16 15:28:25
【问题描述】:

我将使用 AuthorisationManager owin 中间件作为 Web API 来处理基于令牌的安全性。

我的问题是响应正文中的各种错误有各种不同的格式。

在我的 api 中,我通常用结构发回错误

 {"code": "error code", "message": "error message"}

但是来自安全性的一些错误可能会使用

 {"error": "error code", "error_description": "error message"}

或者有时只是

 {"error": "error mesage"}

我想将这些统一起来,使其具有我在其他地方使用的相同结构,即

{"code": "error code", "message": "error message"}

我看过很多关于替换响应正文的帖子。

我首先尝试了this method,即使用DelegatingHandler。这在大多数情况下都有效,但它没有捕捉到来自我的OAuthAuthorizationServerProvider 的我的授权失败错误消息

接下来我尝试使用中间件方法作为shown here。

这是我的完整解释..

    public override async Task Invoke(IOwinContext context)
    {
      try
      {
        // hold a reference to what will be the outbound/processed response stream object
        var stream = context.Response.Body;        

        // create a stream that will be sent to the response stream before processing
        using (var buffer = new MemoryStream())
        {
          // set the response stream to the buffer to hold the unaltered response
          context.Response.Body = buffer;

          // allow other middleware to respond
          await this.Next.Invoke(context);

          // Error codes start at 400. If we have no errors, no more to d0.
          if (context.Response.StatusCode < 400) // <---- *** COMMENT1 ***
            return;

          // we have the unaltered response, go to start
          buffer.Seek(0, SeekOrigin.Begin);

          // read the stream
          var reader = new StreamReader(buffer);
          string responseBody = reader.ReadToEnd();

          // If no response body, nothing to do
          if (string.IsNullOrEmpty(responseBody))
            return;

          // If we have the correct error fields names, no more to do
          JObject responseBodyJson = JObject.Parse(responseBody);
          if (responseBodyJson.ContainsKey("code") && responseBodyJson.ContainsKey("message"))
            return;

          // Now we will look for the known error formats that we want to replace...
          byte[] byteArray = null;

          // The first one from the security module, errors come back as {error, error_description}.
          // The contents are what we set (so are correct), we just want the fields names to be the standard {code, message}
          var securityErrorDescription = responseBodyJson.GetValue("error_description");
          var securityErrorCode = responseBodyJson.GetValue("error");
          if (securityErrorDescription != null && securityErrorCode != null)
            byteArray = CreateErrorObject(securityErrorCode.ToString(), securityErrorDescription.ToString());

          // The next horrible format, is when a refresh token is just sends back an object with 'error'.
          var refreshTokenError = responseBodyJson.GetValue("error");
          if (refreshTokenError != null)
          {
            // We will give this our own error code
            var error = m_resourceProvider.GetRefreshTokenAuthorisationError(refreshTokenError.ToString());
            byteArray = CreateErrorObject(error.Item2, error.Item3);
          }
          else
          {
            byteArray = Encoding.ASCII.GetBytes(responseBody);
          }

          // Now replace the response (body) with our now contents

          // <---- *** COMMENT2 ***
          context.Response.ContentType = "application / json";          
          context.Response.ContentLength = byteArray.Length;
          buffer.SetLength(0);
          buffer.Write(byteArray, 0, byteArray.Length);
          buffer.Seek(0, SeekOrigin.Begin);
          buffer.CopyTo(stream);
        }
      }     
      catch (Exception ex)
      {
        m_logger.WriteError($"ResponseFormattingMiddleware {ex}");
        context.Response.StatusCode = 500;
        throw;
      }      
    }

     private byte[] CreateErrorObject(string code, string message)
        {
          JObject newMessage = new JObject();
          newMessage["code"] = code;
          newMessage["message"] = message;
          return Encoding.ASCII.GetBytes(newMessage.ToString());      
        }

所以这基本上似乎有效,并且可以捕获所有响应,这很好。

但是,我希望做的是,当没有错误时(或者错误已经采用正确的格式),只需传递响应而不做任何事情。

我主要考虑我的一些 GET,其中数据可能很大,我希望避免进行额外的复制。在上面的代码中,我已经标记了*** COMMENT1 ***,我有一个提前返回来尽量避免这种情况,即行...

    // Error codes start at 400. If we have no errors, no more to d0.
    if (context.Response.StatusCode < 400)
        return;

问题是,当我这样做时,我根本没有返回任何正文,即所有 GET 调用都没有数据,等等。

当我们不想做任何修改时,有没有办法避免这种额外的复制(即在*** COMMENT2 *** 行)?

提前感谢您的任何建议。

【问题讨论】:

  • 你这里不是清空回复context.Response.Body = buffer;。
  • 为了更清楚:您正在实例化一个新的空内存流using (var buffer = new MemoryStream())。所以即使这条线也没有多大意义,因为那时它仍然是空的buffer.Seek(0, SeekOrigin.Begin);
  • 为此,我基本上复制了示例代码,而且我必须承认,我发现那里发生的事情有点难以理解;对我来说,它确实看起来有点老套。 delegatingHandler 似乎是“更正确”的方法,但它似乎并没有抓住所有的回应。对于我认为有人想做的常见任务(即有一个常见的错误响应格式),我感到非常沮丧。

标签: asp.net asp.net-web-api owin


【解决方案1】:

添加一个答案,因为它有一个代码 sn-p 但这实际上只是一个评论。

我们的服务使用您提到的您首先尝试的 delegatingHandler 方法。你有没有尝试/赶上base.SendAsync的电话。在这个 sn-p 中,requestState 只是带​​有一些计时器、记录器等的传入请求的包装器。在许多情况下,我们会在您尝试时替换响应。我逐步检查了异常并使用 VS 调试器即时窗口来修改错误响应。它对我有用。(TM)

        try
        {
            return base
                .SendAsync(request, cancellationToken)
                .ContinueWith(
                    (task, requestState) => ((InstrumentedRequest)requestState).End(task),
                    instrumentedRequest,
                    CancellationToken.None,
                    TaskContinuationOptions.ExecuteSynchronously,
                    TaskScheduler.Default)
                .Unwrap();
        }
        catch (Exception ex)
        {
            instrumentedRequest.PrematureFault(ex);
            throw;
        }

【讨论】:

  • 谢谢你。我放入了一个异常处理程序,但在我从覆盖的OAuthAuthorizationServerProvider.GrantResourceOwnerCredentials 调用context.SetError("MyErrorCode", "My error message") 的情况下也没有输入它。除了处理程序之外,几乎所有其他东西都进入了,但在这种情况下没有
猜你喜欢
  • 2014-08-04
  • 1970-01-01
  • 2021-10-16
  • 2023-03-15
  • 2020-12-07
  • 1970-01-01
  • 2015-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多