【问题标题】:ASP.NET WebApi with Protocol Buffers - Error handling带有协议缓冲区的 ASP.NET WebApi - 错误处理
【发布时间】:2014-06-06 13:14:17
【问题描述】:

上下文:

我现在拥有的:

  1. 三层应用
  2. 客户端-服务器通信
    • 服务器:ASP.NET WebApi v1
    • 客户端:HttpClient
  3. 序列化 - JSON.NET

然而,

  1. JSON.NET 很慢
  2. JSON.NET 在第一次调用时甚至更慢(我认为这是因为动态生成序列化程序集)。这对我来说太慢了 - 根据我需要尽可能优化第一次调用的要求。

我正在考虑使用protobuf-net 而不是 JSON.NET。在一个简单的 PoC 应用程序上,即使是第一次调用,它也显示出两倍以上的结果,尤其是当我为协议缓冲区序列化程序预先生成程序集时。

所以我已经使用 protobuf-net 实现了 MediaTypeFormatter,除了一件事 - 序列化错误之外,一切都运行良好。

这是将异常传递给客户端的方式:

public class ExceptionShielderAttribute : ExceptionFilterAttribute
{
    public override void OnException(HttpActionExecutedContext context)
    {
        context.Response = context.Request.CreateErrorResponse(HttpStatusCode.InternalServerError, context.Exception);
    }
}

在内部,CreateErrorResponse 方法创建一个 HttpError 实例(继承自 Dictionary[string, object])并将其写入内容。

默认情况下,protobuf-net 对 HttpError 一无所知,所以我尝试将 HttpError 添加到 protobuf 运行时模型中,如下所示

typeModel.Add(typeof (HttpError), true);

但是当我打电话时它没有帮助

typeModel.Compile("ModelSerializer", "ModelSerializer.dll")

它抛出 InvalidOperationException: No serializer defined for type: System.Object. 可能是由于 protobuf-net 不支持的 Dictionary[string, object] 类型。

问题:

  1. 我可以做些什么来正确序列化错误,或者我应该避免使用开箱即用的错误处理并在使用 protobuf 知道的众所周知的类型的服务器上实现我自己的错误处理吗?

  2. protobuf 是解决我的问题的好选择吗?

【问题讨论】:

    标签: asp.net-web-api error-handling json.net protocol-buffers protobuf-net


    【解决方案1】:

    这是一个使用 protobuf-net 启用 System.Web.Http.HttpError 序列化的 sn-p

    namespace WS
        using System;
        using System.Runtime.Serialization;
        using System.Web.Http;
        using WebApiContrib.Formatting;
    
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                config.Routes.MapHttpRoute(
                    name: "DefaultApi",
                    routeTemplate: "api/{controller}/{id}",
                    defaults: new { id = RouteParameter.Optional }
                );
    
                config.Formatters.Add(new ProtoBufFormatter());
    
                ProtoBufFormatter.Model.Add(typeof(HttpErrorProto), true);
                var model = ProtoBufFormatter.Model.Add(typeof(HttpError), false);
                model.IgnoreListHandling = true;
                model.SetSurrogate(typeof(HttpErrorProto));
            }
        }
    
        [DataContract]
        public class HttpErrorProto
        {
            [DataMember(Order = 1)]
            public String ExceptionMessage { get; set; }
            [DataMember(Order = 2)]
            public String ExceptionType { get; set; }
            [DataMember(Order = 3)]
            public String InnerException { get; set; }
            [DataMember(Order = 4)]
            public String MessageDetail { get; set; }
            [DataMember(Order = 5)]
            public String Message { get; set; }
            [DataMember(Order = 6)]
            public String ModelState { get; set; }
            [DataMember(Order = 7)]
            public String StackTrace { get; set; }
    
    
            public static implicit operator HttpErrorProto(HttpError error)
            {
                return error == null ? null : new HttpErrorProto
                {
                    ExceptionMessage = error.ContainsKey("ExceptionMessage") ? error["ExceptionMessage"] as string : null,
                    ExceptionType = error.ContainsKey("ExceptionType") ? error["ExceptionType"] as string : null,
                    InnerException = error.ContainsKey("InnerException") ? error["InnerException"] as string : null,
                    MessageDetail = error.ContainsKey("MessageDetail") ? error["MessageDetail"] as string : null,
                    Message = error.Message,
                    ModelState = error.ContainsKey("ModelState") ? error["ModelState"] as string : null,
                    StackTrace = error.ContainsKey("StackTrace") ? error["StackTrace"] as string : null
                };
            }
    
            public static implicit operator HttpError(HttpErrorProto error)
            {
                return error == null ? null : new HttpError
                {
                    Message = error.Message
                    ...
                };
            }
        }
    }
    

    【讨论】:

    • 非常有用的解决方案。
    【解决方案2】:

    您最好的选择是使用代理;类似:

    typeModel.Add(typeof(HttpError), false)
        .SetSurrogate(typeof(MyError));
    

    MyError 是您的自定义类型,其中:

    • 具有与HttpError 之间的转换运算符(隐式或显式)
    • 适合从 protobuf-net 使用

    【讨论】:

    • 感谢您的回答。但这并没有帮助。我按照你说的做了,但是 typeModel.Compile() 抛出了 ArgumentException:重复数据(列表、集合等)具有内置行为,不能使用代理项。
    • 它是一个字典[string, object]
    • @olldman 它字典吗?或者它一本字典?但从根本上说:对象是行不通的。例如,如果您可以在从模型到 DTO 的转换过程中对值进行字符串化,例如
    • 它是一本字典 - 请在此处找到其来源 aspnetwebstack.codeplex.com/SourceControl/latest#src/… ,至于您的建议,我认为 WebApi 在调用 CreateErrorResponse 时不可能这样做(此处 - @987654322 @)。看起来自定义错误处理是唯一的方法:(
    • @olldman 这很尴尬。我可能可以围绕它编写代码,但它今天行不通
    【解决方案3】:

    我已经通过向我的 protobuf 媒体类型格式化程序添加一个特殊情况来解决这个问题:

        public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext)
        {
            var completionSource = new TaskCompletionSource<object>();
            try
            {
                if (type == typeof(HttpError))
                {
                    value = CreateDto((HttpError) value);
                }
                model.Value.Serialize(stream, value);
                completionSource.SetResult((object)null);
            }
            catch (Exception ex)
            {
                completionSource.SetException(ex);
            }
            return (Task)completionSource.Task;
        }
    
        private HttpErrorDto CreateDto(HttpError error)
        {
            if (error == null) return null;
    
            return new HttpErrorDto
            {
                Message = error.GetValueOrDefault<string>("Message"),
                ExceptionMessage = error.GetValueOrDefault<string>("ExceptionMessage"),
                StackTrace = error.GetValueOrDefault<string>("StackTrace"),
                ExceptionType = error.GetValueOrDefault<string>("ExceptionType"),
                InnerException = CreateDto(error.GetValueOrDefault<HttpError>("InnerException"))
            };
        }
    

    【讨论】:

      猜你喜欢
      • 2013-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多