Как получить ответ от Нэнси Переговорщик?

У меня есть NancyContext и мне нужно получить Response с телом, основанным на правильном посреднике контента для запроса. Я думаю, что могу использовать Нэнси Negotiator класс для добавления модели, установки статуса и прочего. Но тогда мне нужно вернуть подтип Response, Итак, что я могу использовать, чтобы построить ответ, используя Negotiator?

Вот мой метод:

public Response ConvertToHttpResponse(Exception exception, NancyContext context)
{
    var negotiator = new Negotiator(context)
        .WithStatusCode(HttpStatusCode.BadRequest)
        .WithReasonPhrase(exception.Message);

    return ???;
}

2 ответа

Лично я предпочитаю использовать посредник Нэнси, чтобы возвращать только результаты "Счастливого пути" (т. Е. Возвращается представление /jsondto), а затем возвращать объекты ответа Ванильной Нэнси для любых возможных ошибок.

Один из способов сделать это - вернуть ошибки непосредственно в ваш модуль, например:

public class ProductsModule : NancyModule
{
    public ProductsModule()
        : base("/products")
    {
        Get["/product/{productid}"] = _ => 
        {
            var request = this.Bind<ProductRequest>();

            var product = ProductRepository.GetById(request.ProductId);

            if (product == null)
            {
                var error = new Response();
                error.StatusCode = HttpStatusCode.BadRequest;
                error.ReasonPhrase = "Invalid product identifier.";
                return error;
            }

            var user = UserRepository.GetCurrentUser();

            if (false == user.CanView(product))
            {
                var error = new Response();
                error.StatusCode = HttpStatusCode.Unauthorized;
                error.ReasonPhrase = "User has insufficient privileges.";
                return error;
            }

            var productDto = CreateProductDto(product);

            var htmlDto = new {
              Product = productDto,
              RelatedProducts = GetRelatedProductsDto(product)
            };

            return Negotiate
                    .WithAllowedMediaRange(MediaRange.FromString("text/html"))
                    .WithAllowedMediaRange(MediaRange.FromString("application/json"))
                    .WithModel(htmlDto)  // Model for 'text/html'
                    .WithMediaRangeModel(
                          MediaRange.FromString("application/json"), 
                          productDto); // Model for 'application/json';
        }
    }
}

Это может стать довольно грязным, хотя. Мой предпочтительный подход состоит в том, чтобы настроить обработку ошибок "один раз" в загрузчике моего модуля Nancy, и он должен перехватывать известные / ожидаемые исключения и возвращать их с соответствующим объектом ответа.

Простой пример конфигурации загрузчика для этого может быть:

public class MyNancyBootstrapper : DefaultNancyBootstrapper
{
    protected override void ApplicationStartup(
        TinyIoCContainer container, IPipelines pipelines)
    {
        base.ApplicationStartup(container, pipelines);

        // Register the custom exceptions handler.
        pipelines.OnError += (ctx, err) => HandleExceptions(err, ctx); ;
    }

    private static Response HandleExceptions(Exception err, NancyContext ctx)
    {
        var result = new Response();

        result.ReasonPhrase = err.Message;

        if (err is NotImplementedException)
        {
            result.StatusCode = HttpStatusCode.NotImplemented;
        }
        else if (err is UnauthorizedAccessException)
        {
            result.StatusCode = HttpStatusCode.Unauthorized;
        }
        else if (err is ArgumentException)
        {
            result.StatusCode = HttpStatusCode.BadRequest;
        }
        else
        {
            // An unexpected exception occurred!
            result.StatusCode = HttpStatusCode.InternalServerError;    
        }

        return result;
    }
}

Используя это, вы можете реорганизовать свой модуль, чтобы просто выдать соответствующее исключение, которое вызовет правильный тип ответа. Вы можете начать создавать хороший набор стандартов для вашего API в этом отношении. Примером этого может быть:

public class ProductsModule : NancyModule
{
    public ProductsModule()
        : base("/products")
    {
        Get["/product/{productid}"] = _ => 
        {
            var request = this.Bind<ProductRequest>();

            var product = ProductRepository.GetById(request.ProductId);

            if (product == null)
            {
                throw new ArgumentException(
                    "Invalid product identifier.");
            }

            var user = UserRepository.GetCurrentUser();

            if (false == user.CanView(product))
            {
                throw new UnauthorizedAccessException(
                    "User has insufficient privileges.");
            }

            var productDto = CreateProductDto(product);

            var htmlDto = new {
              Product = productDto,
              RelatedProducts = GetRelatedProductsDto(product)
            };

            return Negotiate
                    .WithAllowedMediaRange(MediaRange.FromString("text/html"))
                    .WithAllowedMediaRange(MediaRange.FromString("application/json"))
                    .WithModel(htmlDto)  // Model for 'text/html'
                    .WithMediaRangeModel(
                          MediaRange.FromString("application/json"), 
                          productDto); // Model for 'application/json';
        }
    }
}

Это кажется мне чище, и теперь я внедряю набор стандартов в свои модули.:)


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

Основной пример этого будет:

result.Contents = responseStream =>
    {
        string errorBody = string.Format(
            @"<html>
                <head>
                    <title>Exception report</title>
                </head>
                <body>
                    <h1>{0}</h1>
                    <p>{1}</p>
                </body>
              </html>",
            ex.Message,
            ex.StackTrace);

        // convert error to stream and copy to response stream
        var byteArray = Encoding.UTF8.GetBytes(errorBody);
        using (var errorStream = new MemoryStream(byteArray))
        {
            errorStream.CopyTo(responseStream);
        }
    }

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

Основываясь на вашем примере кода, вот один из возможных способов:

public Response ConvertToHttpResponse(Exception exception, NancyContext context, IEnumerable<IResponseProcessor> processors, Nancy.Conventions.AcceptHeaderCoercionConventions coercionConventions)
{
    var negotiator = new Negotiator(context)
        .WithStatusCode(HttpStatusCode.BadRequest)
        .WithReasonPhrase(exception.Message);

    return new DefaultResponseNegotiator(processors, coercionConventions)
        .NegotiateResponse(negotiator, context);
}

В зависимости от вашей реализации, лучшим способом может быть processors а также coercionConventions в качестве параметров конструктора класса, и позволяют контейнеру IoC разрешать их как обычно. Однако в моем случае я разрешил их в своем загрузчике и дал метод расширения, который я создал для согласования Exception экземпляры в ответ XML или JSON.

protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
    //  Resolving outside the lambda because no more components will be registered at this point.
    var responseProcessors = container.Resolve<IEnumerable<Nancy.Responses.Negotiation.IResponseProcessor>>();
    var coercionConventions = container.Resolve<AcceptHeaderCoercionConventions>();

    pipelines.OnError += (context, exception) => 
    {
        return exception.GetErrorResponse(context, responseProcessors, coercionConventions);
    };
}
Другие вопросы по тегам