Как получить ответ от Нэнси Переговорщик?
У меня есть 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);
};
}