asp.net Web API替换请求响应正文以保持一致的错误结构的最有效方法 [英] asp.net Web API most efficient way to replace a request response body to have a consistant error structure

查看:81
本文介绍了asp.net Web API替换请求响应正文以保持一致的错误结构的最有效方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am using as Web API what uses AuthorisationManager owin middleware to handle token based security.

我的问题是,响应主体中的各种错误具有各种不同的格式.

My problem is that various errors within the response body have various different formats.

在我的api中,我通常会通过结构将错误发送回去

Within my api, I usually send errors back with the structure

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

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

However some of the errors coming from the security may use

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

或有时只是

 {"error": "error mesage"}

我想将它们统一起来,使其具有与其他地方相同的结构,即

I would like to unify these to all have the same structure I use elsewhere, ie the

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

我已经看到很多关于替换响应正文的帖子.

I have seen quite a few posts on replacing a response body.

我首先尝试了

I first tried this method, ie using the DelegatingHandler. This worked in most cases, but it did not catch my authorization failed error messages comding out of my OAuthAuthorizationServerProvider

我接下来尝试使用中间件方法,如下所示:这里.

I next tried using a middleware approach as 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());      
        }

因此,这似乎基本可行,并且可以捕获所有响应,这很好.

So this basically seemed to work, and catch ALL responses, which is good.

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

However, what I was hoping to do, is, when there is no error, (or the error is already in the correct format), just pass the response on without doing anything with it.

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

I am mainly thinking of some of my GETs, where the data may be large, I was hoping to avoid having to do the extra copying back. In the above code, where I have marked *** COMMENT1 ***, I have an early return to try to avoid this, ie the line...

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

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

The problem, is when I do this, I get no body at all returned, ie no data for all the GET calls, etc.

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

Is there a way to avoid this extra copying (ie at the line *** COMMENT2 ***) when we don't want to do any modifications?

提前感谢您的任何建议.

Thanks in advance for any suggestions.

推荐答案

添加答案,因为它具有代码段,但这实际上只是注释.

Adding an answer since it has a code snippet but this is really just a comment.

我们的服务使用您提到的您首先尝试过的delegatingHandler方法.您是否有尝试/赶上base.SendAsync的呼叫.在此代码段中,requestState只是带有一些计时器,记录器等的传入请求的包装.在许多情况下,我们会在尝试时替换响应.我逐步处理了异常,并使用VS调试器立即窗口修改了错误响应.它对我有用.(TM)

Our services use the delegatingHandler approach you mentioned you tried first. Do you have try/catch around the call to base.SendAsync. In this snippet the requestState is just a wrapper around the incoming request with some timers, loggers, etc. In many cases we replace the response as you are trying. I stepped through the exception and used the VS debugger immediate window to modify the error response. It works for me.(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;
        }

这篇关于asp.net Web API替换请求响应正文以保持一致的错误结构的最有效方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆