В OpenRasta, как вы должны обрабатывать ошибки или исключения кодеков?

Мой сценарий таков:

  1. Клиентское приложение выполняет HTTP POST для конечной точки, предоставляемой OpenRasta.
  2. Тело запроса содержит ошибку, которая вызывает проблему в кодеке, который является пользовательской реализацией OpenRasta.Codecs.IMediaTypeReader, Это преобразует полезную нагрузку JSON в ожидаемое обработчиком POCO.
  3. Кодек генерирует исключение, которое описывает ошибку полезным способом. Например: Newtonsoft.Json.JsonReaderException: After parsing a value an unexpected character was encountered: ". Line 4, position 5.
  4. Клиентское приложение получает HTTP 405 - MethodNotAllowed. Клиент не видит никаких деталей об исключении.

Если кодек изменен, чтобы поймать JsonReaderException и вернуться Missing.ValueАналогично реализации кодека вики, клиент получает HTTP 500 - Internal Server Error. Тело ответа также описывает следующее исключение:

System.InvalidOperationException: The operation is not ready for invocation.
   at OpenRasta.OperationModel.MethodBased.MethodBasedOperation.Invoke()
   at OpenRasta.OperationModel.Interceptors.OperationWithInterceptors.<Invoke>b__0()
   at OpenRasta.OperationModel.Interceptors.OperationWithInterceptors.Invoke()
   at OpenRasta.OperationModel.OperationExecutor.Execute(IEnumerable`1 operations)
   at OpenRasta.Pipeline.Contributors.OperationInvokerContributor.ExecuteOperations(ICommunicationContext context)
   at OpenRasta.Pipeline.PipelineRunner.ExecuteContributor(ICommunicationContext context, ContributorCall call)

Как я должен изменить свое приложение так, чтобы:

  • Клиент получает неверный запрос HTTP 400.
  • Клиент получает строку, содержащую сведения об исключении, обнаруженном в кодеке.

2 ответа

Решение

Найдя эту ветку в группах Google, которая содержит все ответы, моя текущая реализация выглядит примерно так.

В рамках моей реализации IConfigurationSource:

using (OpenRastaConfiguration.Manual)
{
    ResourceSpace.Uses.PipelineContributor<ErrorCheckingContributor>(); 

    // Other configuration here
}

затем ErrorCheckingContributor выглядит примерно так:

public class ErrorCheckingContributor : IPipelineContributor
{
    public void Initialize(IPipeline pipelineRunner)
    {
        pipelineRunner
            .Notify(CheckRequestDecoding)
            .After<KnownStages.IOperationResultInvocation>()
            .And.Before<KnownStages.ICodecResponseSelection>(); 
    }

    private static PipelineContinuation CheckRequestDecoding(ICommunicationContext context)
    {
        if (context.ServerErrors.Count == 0)
        {
            return PipelineContinuation.Continue;
        }

        var first = context.ServerErrors[0];
        if (first.Exception is Newtonsoft.Json.JsonReaderException)
        {
            context.Response.Entity.ContentType = MediaType.TextPlain;
            context.Response.Entity.ContentLength = first.Exception.Message.Length;
            using (var sw = new StreamWriter(context.Response.Entity.Stream))
            {
                sw.Write(first.Exception.Message);
            }
        }

        return PipelineContinuation.Continue;
    } 
}

Есть некоторые вещи, о которых следует помнить с учетом вышесказанного:

  • Если бы обработчик бросил JsonReaderException, это также будет обработано здесь.
  • Он не проверяет, какие типы носителей принимает клиент. Это отличается от исключений, выдаваемых обработчиками, которые проходят выбор кодека.
  • Пробная настройка context.OperationResult в context.ServerErrors - но это не проходит через кодек.

Вот небольшой вариант ответа выше - на этот раз с выбором кодека на основе данных результата операции.

В IConfigurationSource:

using (OpenRastaConfiguration.Manual)
{
    ResourceSpace.Uses.PipelineContributor<ErrorCheckingContributor>(); 

    ResourceSpace.Has.ResourcesOfType<ApplicationError>()
                 .WithoutUri
                 .TranscodedBy<ApplicationErrorCodec>();

                 // Or use a generic JSON serializer like this:
                 // .AsJsonDataContract();

    // Other configuration here
}

Сейчас ErrorCheckingContributor выглядит так:

public class ErrorCheckingContributor : IPipelineContributor
{
  public void Initialize(IPipeline pipelineRunner)
  {
    pipelineRunner
        .Notify(CheckRequestDecoding)
        .After<KnownStages.IOperationResultInvocation>()
        .And.Before<KnownStages.ICodecResponseSelection>();
  }

  private static PipelineContinuation CheckRequestDecoding(ICommunicationContext context)
  {
    if (context.ServerErrors.Count == 0)
    {
      return PipelineContinuation.Continue;
    }

    Error err = context.ServerErrors[0];

    // Get a suitable message (err.Message contains stack traces, so try to avoid that)
    string msg = err.Title;
    if (msg == null && err.Exception != null)
      msg = err.Exception.Message;
    if (msg == null)
      msg = err.Message;

    // Create instance of an error information resource which is specific for the application
    // - This one is rather simple and only contains a copy of the message
    ApplicationError error = new ApplicationError(msg);

    // Set operation result to be "400 Bad Request" and remove errors
    context.OperationResult = new OperationResult.BadRequest { ResponseResource = error };
    context.ServerErrors.Clear();

    // Render immediately without starting any handlers
    return PipelineContinuation.RenderNow;
  }
}

Класс ApplicationError является:

public class ApplicationError
{
  public string Message { get; set; }

  public ApplicationError(string message)
  {
    Message = message;
  }
}

Наконец-то нам нужен кодек ApplicationErrorCodec за ApplicationError, Это не отличается от любого другого кодека IMediaTypeWriter, но во многом зависит от вашего ожидаемого типа носителя ответа. См. https://github.com/openrasta/openrasta/wiki/Implementing-a-Codec для одного примера.

Другие вопросы по тегам