ASP.NET WebApi с буферами протоколов — обработка ошибок

Контекст:

Что у меня есть сейчас:

  1. 3-уровневое приложение
  2. Client-Server communication
    • Server: ASP.NET WebApi v1
    • Клиент: HttpClient
  3. Сериализация — JSON.NET

Однако,

  1. JSON.NET работает медленно
  2. JSON.NET еще медленнее при первом вызове (я так понимаю, это из-за генерации сборки сериализатора на лету). Это слишком медленно для меня - в соответствии с требованиями мне нужно максимально оптимизировать первый вызов.

Я рассматриваю возможность использования protobuf-net вместо JSON.NET. В простом приложении PoC он показал более чем в два раза более быстрый результат, даже при первом вызове, особенно когда я предварительно сгенерировал сборку для сериализатора буферов протокола.

Итак, я реализовал MediaTypeFormatter с помощью protobuf-net, и все работает хорошо, кроме одного — сериализации ошибок.

Вот как исключения передаются клиенту:

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: сериализатор не определен для типа: System.Object. Вероятно, из-за типа Dictionary[string, object], который не поддерживается protobuf-net.

Вопросы:

  1. Есть ли что-нибудь, что я могу сделать для правильной сериализации ошибок, или мне следует избегать использования готовой обработки ошибок и реализовать свою собственную обработку ошибок на сервере, который использует хорошо известные типы, о которых знает protobuf?

  2. Является ли protobuf хорошим выбором для моей проблемы?


person olldman    schedule 06.06.2014    source источник


Ответы (3)


Вот фрагмент, который включает сериализацию System.Web.Http.HttpError с использованием protobuf-net.

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
                ...
            };
        }
    }
}
person romi    schedule 30.12.2015
comment
Очень полезное решение. - person rolls; 16.04.2017

Лучше всего было бы использовать суррогатную мать; что-то вроде:

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

Где MyError — это ваш собственный тип, который:

  • имеет оператор преобразования (неявный или явный) в/из HttpError
  • подходит для использования из protobuf-net
person Marc Gravell    schedule 06.06.2014
comment
Спасибо за ответ. Однако это не помогло. Я сделал, как вы сказали, но typeModel.Compile() выдал исключение ArgumentException: повторяющиеся данные (список, коллекция и т. д.) имеют встроенное поведение и не могут использовать суррогат. - person olldman; 06.06.2014
comment
Это словарь[строка, объект] - person olldman; 06.06.2014
comment
@olldman это это словарь? Или у него есть словарь? Но принципиально: объект не будет работать. Если бы вы могли, например, точить значения во время перевода из модели в DTO, например - person Marc Gravell; 06.06.2014
comment
это словарь. Его источник можно найти здесь aspnetwebstack. codeplex.com/SourceControl/latest#src/ , Что касается вашего предложения, я не думаю, что это возможно, поскольку WebApi делает это изначально, когда вызывается CreateErrorResponse (здесь - aspnetwebstack.codeplex.com/SourceControl/latest#src/). Похоже, что настраиваемая обработка ошибок - единственный путь :( - person olldman; 06.06.2014
comment
@olldman, это неловко. Я мог бы, вероятно, обойти это, но сегодня это не сработает. - person Marc Gravell; 07.06.2014

Мне удалось обойти проблему, добавив специальный случай в мой форматировщик медиа-типа 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"))
        };
    }
person olldman    schedule 06.06.2014