带有协议缓冲区的 ASP.NET WebApi - 错误处理

Posted

技术标签:

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

上下文:

我现在拥有的:

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

然而,

    JSON.NET 很慢 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] 类型。

问题:

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

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

【问题讨论】:

【参考方案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"))
        ;
    

【讨论】:

以上是关于带有协议缓冲区的 ASP.NET WebApi - 错误处理的主要内容,如果未能解决你的问题,请参考以下文章

带有 JWT Bearer 令牌和 ASP.NET Core 2 WebApi 的 Azure AD 用户信息

ASP.NET WebAPI和带有大型数据集的jQuery(json)

带有选项字段的 F# 记录在 Asp.Net WebApi 2.x 应用程序中无法正确反序列化

带有 AngularJS CORS 的 IE9、IE8 返回“访问被拒绝” - ASP.NET WebApi

如何使用角度修改 asp.net 核心 WebAPI 的 asp.net 身份 UI

asp.net webapi 2属性路由不起作用