Статьи

NancyFX и контентные переговоры

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

При выполнении этого в ASP.NET MVC мне нужно будет проверить тип содержимого и решить, как я хочу ответить на запрос.

Это привело к созданию дублирующих методов, которые будут использоваться обычным GET-запросом, а 2-й — для AJAX-запроса. Или, если это было похоже, используйте условную логику в одном методе, чтобы решить, как должно реагировать действие…

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

Get["/negotiated"] = parameters => {
    return Negotiate
        .WithModel(new RatPack {FirstName = "Nancy "})
        .WithMediaRangeModel("text/html", new RatPack {FirstName = "Nancy fancy pants"})
        .WithView("negotiatedview")
        .WithHeader("X-Custom", "SomeValue");
};

Примечание: образец взят из Nancy GitHub Repo

Что такое согласование контента?

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

Чтобы узнать больше, вы можете посетить Википедию или Шаблоны SOA … или … Google

Почему я должен волноваться?

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

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

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

Тот же сценарий происходит, если пользователь (маловероятно) отключил JavaScript.

Реализация

Таким образом, используя приведенный выше пример, удаляя товар из корзины и обновляя страницу, используя JavaScript, выдает полный постбэк страницы, используя точно такой же маршрут.

Давайте создадим действительно простой модуль:

public class HomeModule : NancyModule
{
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
    }
    public static IList<Product> Products = new List<Product>()
    {
        new Product {Id = 1, Name = "Surface", Price = 499},
        new Product {Id = 2, Name = "iPad", Price = 899},
        new Product {Id = 3, Name = "Nexus 10", Price = 599},
        new Product {Id = 4, Name = "Think Pad", Price = 499},
        new Product {Id = 5, Name = "Yoga", Price = 699},
    };

    public dynamic Model = new ExpandoObject();

    public HomeModule()
    {
        Model.Deleted = false;

        Get["/"] = _ =>
        {
            Model.Products = Products;

            return View["index", Model];
        };
    }
}

Я вложил туда класс Product и создал статический список продуктов для демонстрации.

Нам нужен View для отображения продуктов:

<h2>Products</h2>
<p>Posted back using: <span class="status">@if ((bool)Model.Deleted) { @Html.Raw("Full Postback"); }</span></p>

<table>
  <thead>
    <tr>
      <th style="width: 50px;">Id</th>
      <th style="width: 90px;">Name</th>
      <th style="width: 50px;">Price</th>
      <th style="width: 150px;"> </th>
      <th style="width: 160px;"> </th>
    </tr>
  </thead>

  @foreach (var product in Model.Products)
  {
    <tr>
      <td>@product.Id</td>
      <td>@product.Name</td>
      <td>@product.Price.ToString("c")</td>
      <td><a href="/delete/@product.Id">Delete With JavaScript</a></td>
      <td><a href="/delete/@product.Id">Delete Without JavaScript</a></td>
    </tr>  
  }
</table>

Это отображается будет отображать следующее:

Поэтому, когда мы нажимаем кнопку «Удалить без JavaScript», мы хотим, чтобы он удалил элемент, поэтому мы можем добавить новый маршрут:

Get[@"/delete/{id}"] = _ =>
{
    var id      = (int) _.id;
    var item    = Products.Single(x => x.Id == id);

    Products.Remove(item);
    Model.Products = Products;
    Model.Deleted = true;

    return View["index", Model];
};

Теперь, если мы нажмем кнопку:

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

Теперь мы хотим, чтобы кнопка «Удалить с помощью JavaScript» работала.

Итак, мы можем добавить немного JavaScript:

<script src="/Scripts/jquery-1.8.2.min.js"></script>
<script>
  (function ($) {
    $(document).on("click", 'a:contains(Delete With JavaScript)', function (e) {

      e.preventDefault();

      var that = $(this),
          tr = that.closest('tr');

      $.ajax({
        url: this.href,
        type: 'GET',
        dataType: 'JSON',
        contentType: 'application/json; charset=utf-8'
      }).success(function (data) {
        if (data.Deleted === true) {
          tr.remove();
          $('.status').text("Using JavaScript");
        }
      });

    });

  })(jQuery);
</script>

Так что это просто ищет тег привязки с текстом «Удалить с помощью JavaScript», так как мы не хотим останавливать работу других кнопок.

Теперь нам нужно обновить маршрут для обработки согласования содержимого.

Get[@"/delete/{id}"] = _ =>
{
    var id      = (int) _.id;
    var item    = Products.Single(x => x.Id == id);

    Products.Remove(item);
    Model.Products = Products;
    Model.Deleted = true;

    return Negotiate
        .WithModel((object) Model)
        .WithMediaRangeModel("application/json", new
        {
            Model.Deleted
        })
        .WithView("index");
};

Реализация идентична предыдущей, с той лишь разницей, что мы заменили «View» на «Negotiate».

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

Если тип отличается, в данном случае запрос был для application / json, то мы возвращаем только те данные, которые нам нужны, а не все.

Теперь, если мы снова запустим ту же страницу, но нажмем «Удалить с помощью JavaScript»

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

Теперь, когда мы нажимаем на любую ссылку, оба сценария обрабатываются одним маршрутом.

Также…

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

Согласование контента — это потрясающе.

Демо для этого проекта можно найти здесь, на github .