Статьи

NancyFX — Пересмотр согласования контента и API (часть 2)

В первой части я рассмотрел действительно базовый сценарий, и одной из странных вещей, которые у нас были, было имя представления для возврата коллекции.

Использование Negotiateдает больше гибкости в отношении того, что мы можем вернуть, что позволяет нам настраивать ответ так, чтобы он по-разному реагировал на разные типы носителей или возвращал частичное содержимое в некоторых сценариях.

Возвращаясь к моему предыдущему посту, я сказал, что мы сможем использовать тот же API для создания нашего веб-сайта, что и мы. Но что делать, когда мы хотим разместить на веб-сайте дополнительную информацию, которая не предоставляется клиенту?

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

Покажи мне кодез!

Итак, давайте обновим, Productчтобы включитьSpecialPrice

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public decimal? SpecialPrice { get; set; }
}

Примечание: а также обновите репозиторий, чтобы возвращать Специальную цену для каждогоProduct

Мы также собираемся представить новую модель под названием PartialProduct. Возможно, есть более подходящее название, но я выбрал его для демонстрации. Это будет так же, как Productбез SpecialPriceсобственности.

public class PartialProduct
{
    public PartialProduct()
    {
    }

    public PartialProduct(Product product)
    {
        Id = product.Id;
        Name = product.Name;
        Price = product.Price;
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Конструктор используется для заполнения модели на основе Product. Вы можете использовать что-то вроде AutoMapper, чтобы сделать это для вас, или написать несколько методов расширения и т. Д.

Теперь с нашим маршрутом, мы хотим вернуть PartialProductпри ответе application/jsonилиapplication/xml

Get["/{id}"] = _ =>
{
    var product = productRepository.Get((int)_.id);

    return Negotiate.WithView("product")
                    .WithModel(product)
                    .WithMediaRangeModel(MediaRange.FromString("application/json"),
                                         new PartialProduct(product))
                    .WithMediaRangeModel(MediaRange.FromString("application/xml"),
                                         new PartialProduct(product));
};

Таким образом, мы говорим, что мы хотим согласовать ответ с представлением, productиспользуя модель product, но если MediaRange — это application/jsonмы хотим вернуть частичный продукт, аналогично, если application/xmlмы хотим также вернуть частичную модель. (если это выглядит много, не волнуйтесь, мы можем привести в порядок это)

Теперь, когда мы настраиваем наш почтальон, text/htmlмы получаем SpecialPriceвозвращенный продукт:

Однако, если мы обновим его до, application/jsonмы получим:

Anddddd application/xmlмы получаем:

Но… Нам пришлось написать много кода для медиа-диапазонов. Методы расширения, однако, замечательны, поэтому мы можем привести их в порядок, представив наши собственные методы расширения:

public static class NegotiateExtensions
{
    public static Negotiator ForJson(this Negotiator negotiator, object model)
    {
        return negotiator.WithMediaRangeModel(MediaRange.FromString("application/json"), model);
    }

    public static Negotiator ForXml(this Negotiator negotiator, object model)
    {
        return negotiator.WithMediaRangeModel(MediaRange.FromString("application/xml"), model);
    }
}   

Это уменьшает вещи до:

Get["/{id}"] = _ =>
{
    var product = productRepository.Get((int)_.id);

    return Negotiate.WithView("product")
                    .WithModel(product)
                    .ForJson(new PartialProduct(product))
                    .ForXml(new PartialProduct(product));
};

Все еще слишком много? Мы можем ввести еще одно расширение

public static Negotiator OrPartial(this Negotiator negotiator, object model)
{
    return negotiator.ForJson(model).ForXml(model);
}

И теперь все, что у нас есть, это

Get["/{id}"] = _ =>
{
    var product = productRepository.Get((int)_.id);

    return Negotiate.WithView("product")
                    .WithModel(product)
                    .OrPartial(new PartialProduct(product));
};

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

В третьей части я расскажу о реализации вашего собственного Media Type ?