Нэнси выполняет действие AfterRequest после OnError

Я хочу добавить время запроса в заголовки ответа на запрос к модулю Нэнси. Я добавил несколько обработчиков запросов до/после в RequestStartup и без проблем добавил заголовки (пример ниже), и все было хорошо. Я также добавил обработчик OnError в ApplicationStartup, чтобы перехватывать ошибки и возвращать хорошо отформатированный ответ Json.

pipelines.BeforeRequest += ctx =>
    {
        ctx.Items.Add("X-RequestStart", DateTime.Now.ToString());
        return null;
    };

pipelines.AfterRequest += ctx =>
    {
        var now = DateTime.Now;
        try
        {
            //Not got around to forcing the culture on the datetime parsing yet...
            var startTime = DateTime.Parse(ctx.Items["X-RequestStart"].ToString());

            ctx.Response.Headers.Add("X-RequestStart", startTime.ToString(CultureInfo.InvariantCulture));
            ctx.Response.Headers.Add("X-RequestComplete", now.ToString(CultureInfo.InvariantCulture));
            ctx.Response.Headers.Add("X-RequestDuration", (now - startTime).ToString());
        }
        catch (Exception)
        {
            ctx.Response.Headers.Add("X-RequestComplete", now.ToString(CultureInfo.InvariantCulture));
        }
    };

pipelines.OnError += (ctx, exception) =>
    {
        return ErrorResponse.FromException(exception);
    };

Однако я заметил, что когда у меня возникает ошибка, действие AfterRequest не выполняется, поэтому у меня нет заголовков синхронизации в ответе об ошибке. Я пытался перенести обработку запроса до/после на запуск приложения, но это тоже не дало результата.

На самом деле вопрос состоит из двух частей: во-первых, можно ли заставить фреймворк выполнять действие AfterRequest после того, как было выполнено действие OnError, или конвейер настроен таким образом, чтобы предотвратить это, а во-вторых, должны ли они выполняться до/ после действий запроса быть частью RequestStartup или ApplicationStartup? ApplicationStartup кажется разумным для обработки ошибок, тогда как RequestStartup кажется разумным для взаимодействия с заголовками ответов, как это должно быть для каждого запроса, но я не уверен, существует ли соглашение для этого, или мои предположения были неверны.


person Ian Cotterill    schedule 03.04.2014    source источник
comment
Я заметил подобное поведение при ошибках. Что я видел, так это то, что свойство ответа контекста равно null. Это может привести к тому, что ваш AfterRequest не запустится. Просто намек.   -  person Byron Sommardahl    schedule 06.04.2014
comment
Интересно. Я не проверял это. Я генерирую ответ, когда срабатывает OnError, но ответ устанавливается в делегате OnError, поэтому я предполагаю, что это не возвращается в контекст.   -  person Ian Cotterill    schedule 07.04.2014
comment
Кажется, что свойство ответа контекста имеет значение null, однако добавление ответа обратно в контекст в OnError, похоже, не имеет никакого значения — я предполагаю, что это связано с тем, что хук AfterRequest уже прошел к этому моменту. Это случай, когда я плохо обрабатываю ошибки, помещая действие в OnError, или есть способ обойти это, чтобы сохранить/добавить ответ в контекст?   -  person Ian Cotterill    schedule 08.04.2014
comment
Вы когда-нибудь находили ответ на этот вопрос?   -  person pashute    schedule 19.01.2015
comment
Нет, я не нашел решения - было приятно иметь то, что в конечном итоге не требовалось.   -  person Ian Cotterill    schedule 20.01.2015


Ответы (1)


К сожалению, в NancyFx это не представляется возможным. Я внимательно изучил исходный код, особенно DefaultRequestDispatcher. Dispatch перехватывает любые исключения, возникшие во время обработки маршрута, вызывает ResolveErrorResult, а затем получает ответ от переговорщика. По-видимому, не существует точки расширения для изменения ответов, сгенерированных таким образом.

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

public async Task<Response> Dispatch(NancyContext context, CancellationToken cancellationToken)
{
    // TODO - May need to make this run off context rather than response .. seems a bit icky currently
    var resolveResult = this.Resolve(context);

    context.Parameters = resolveResult.Parameters;
    context.ResolvedRoute = resolveResult.Route;

    try
    {
        context.Response = await ExecuteRoutePreReq(context, cancellationToken, resolveResult.Before)
            .ConfigureAwait(false);

        if(context.Response == null)
        {
            context.Response = await this.routeInvoker.Invoke(resolveResult.Route, cancellationToken,
                resolveResult.Parameters, context)
                .ConfigureAwait(false);

            if (context.Request.Method.Equals("HEAD", StringComparison.OrdinalIgnoreCase))
            {
                context.Response = new HeadResponse(context.Response);
            }
        }

        await this.ExecutePost(context, cancellationToken, resolveResult.After, resolveResult.OnError)
            .ConfigureAwait(false);
    }
    catch(Exception ex)
    {
        context.Response = this.ResolveErrorResult(context, resolveResult.OnError, ex);

        if (context.Response == null)
        {
            throw;
        }
    }

    return context.Response;
}

private Response ResolveErrorResult(NancyContext context, Func<NancyContext, Exception, dynamic> resolveResultOnError, Exception exception)
{
    if (resolveResultOnError != null)
    {
        var flattenedException = exception.FlattenInnerExceptions();

        var result = resolveResultOnError.Invoke(context, flattenedException);
        if (result != null)
        {
            return this.negotiator.NegotiateResponse(result, context);
        }
    }

    return null;
}
person NathanAldenSr    schedule 18.11.2016