Еще немного о NancyFX

Введение

Сегодня я хотел бы немного углубиться в “MVC-модель” NancyFX. Разобраться что и как здесь это работает проще на простом примере – например, классический пример со списком дел (он же ToDo List): пользователь может иметь несколько список дел (TodoList) с некоторым количеством задач в каждом (Todo). Каждая задача имеет срок исполнения, заголовок и признак выполненности. Пользователь может создавать новые списки дел и добавлять новые задачи в существующие списки дел.

NancyDemo

На этот раз, в отличие от предыдущего опыта будем использовать другой шаблон проекта – NancyDemo.
Этот шаблон создатели NancyFX рекомендуют использовать всем желающим внести вклад в проект
(создать какое-то демо-приложение и выложить на их сайте).

Структура проекта

Структура шаблонного проекта довольно проста:

- Content
-- *.css
-- *.png
-- img
--- *.png
-- scripts
--- *.js
- Modules
-- IndexModule.cs
- Views
-- index.hmtl
-- layout.html
Bootstrapper.cs

Папка Content содержит различную статику: картинки, CSS и JavaScript. В Modules хранятся наши модули (своего рода контроллеры из ASP.NET MVC). Все представления, как не сложно догадаться, лежат в директории Views.

В данном шаблоне есть один модуль IndexModule и одно представление index.html. Помимо этого, есть мастер-представление layout.html, которое используется как базовое для представления index.html.

Bootstrapper.cs – это “входная точка” в Nancy-приложение. Этот класс выполняет некоторую магию: разрешение зависимостей, обнаружение модулей и т.п. Подробнее можно посмотреть здесь.

Это всё, на что хотелось бы обратить внимание на данный момент.

Для того, чтобы проверить, что всё работает – нажимаете в Visual Studio (у меня 2015 Community) F5 и, если всё сделано правильно (а сейчас иначе быть и не должно), то откроется ваш дефолтный браузер и вы увидите картинку примерно следующего содержания: [1.png].

Поехали!

На самом деле, практически ничего (от слова “совсем”) из созданного автоматически нам не понадобится: IndexModule.cs и index.html будут переписаны практически полностью.

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

Модули/контроллеры

Про контроллеры (которые здесь принято называть модулями) я писал ранее. Они простые и не вызывают никаких вопросов (если есть – можете попробовать спросить, а я попробую ответить :)).

Для нашего игрушечного проекта достаточно будет одного модуля, который будет обрабатывать все 5 запросов: открытие “главной” страницы, открытие страниц добавления списка и задачи и два POST-запроса на добавление списка задач или задачи.

Полный код модуля выглядит следующим образом:

public class IndexModule : NancyModule
    {
        public IndexModule()
        {
            // Bootstrapper.TodoLists - список всех списков задач (чтобы не усложнять - просто хранится в памяти).
            Get["/"] = _ => View["index", Bootstrapper.TodoLists];

            Post["/todolists/add"] = parameters =>
            {
                var newTodoList = this.Bind<TodoList>();
                Bootstrapper.TodoLists.Add(newTodoList);
                newTodoList.Todos = new Todos();
                newTodoList.Todos.TodoListId = newTodoList.Id;

                return new RedirectResponse("/");
            };

            // Про {TodoListId:int} лучше посмотреть [здесь](https://github.com/NancyFx/Nancy/wiki/Defining-routes).
            Post["/todolists/{TodoListId:int}/todos/add"] = parameters =>
            {
                var newTodo = this.Bind<Models.Todo>();
                var todoList = Bootstrapper.TodoLists.FirstOrDefault(x => x.Id == newTodo.TodoListId);
                if (todoList == null)
                    return Negotiate.WithModel("Not exists todo list").WithStatusCode(HttpStatusCode.BadRequest);

                todoList.Todos.Add(newTodo);
                return new RedirectResponse("/");
            };

            Get["/todolists/add"] = _ => View["todolist/create.html"];

            Get["/todolists/{todolistid:int}/todos/add"] = _ => View["todo/create.html", _.todolistid];
        }
    }

Модели

Останавливаться детально на моделях не буду. Хочется остановиться чуточку более подробно лишь на одом аспекте – model binding. Model binding – это такая штука, которая позволяет из переданных параметров (через строку запроса GET или форму POST) инстанциировать объект нужного класса:

// Это как бы модель
public class Foo 
{
    public string Name { get; set; }
    public int Age { get; set; }
}

Это наше простенькое представление

<form method="post" action="/foo/create">
    <input name="Name"/>
    <input name="Age"/>
    <input type="submit" value="Save"/>
</form>

Это типа модуль:

public class FooModule: NancyModule
{
    public FooModule() 
    {
        Post["/foo/create/"] = parameters => 
        {
            var newFoo = this.Bind<Foo>(); // здесь в newFoo попадут параметры, которые пришли с клиента в parameters.
            return new RedirectAction("/"); // Перекинем пользователя обратно на главную страницу приложения.
        }
    }
}

Представления

Как в любом MVC-фреймворке в NancyFX есть представления. Более того, следуя своей основной концепции (возможность подменить или изменить любую компоненту фреймворка) стандартный View Engine – SuperSimpleViewEngine – на другой. Например, на знакомый по ASP.NET MVC – Razor. К тому же, по аналогии с другими MVC-фреймворками, в представление можно передать модель:

Get["/products"] = parameters => {
    // products.html - имя представления; someModel - модель, которая "приедет" в представление.
    return View["products.html", someModel]; 
};

В представлении можно обращаться к свойствам модели используя код похожий на следующий:

  <!--@Model - зарезервированное слово.-->
  <p>Hello, @Model.UserName</p>

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

В приложении будет три представления:

  • index.html – главная страница сайта:
@Master['layout']

@Section['Content']
@Each
    <h3>@Current.Title</h3>
    @Partial['list.html', @Current.Todos]
@EndEach
<h3><a href="/todolists/add">Добавить новый список</a></h3>
@EndSection
  • todolist/create.html – страница для добавления списка задач:
@Master['../layout']
@Section['Content']
<form action="/todolist/create" method="POST">
    <input type="text" name="Title"/>
    <br/>
    <input type="text" name="Id"/>
    <br />
    <input type="submit" value="Create" class="btn btn-primary"/>
</form>
@EndSection
  • todo/create.html – страница для добавления задачи;
@Master['../layout']
@Section['Content']
<form action="/todolists/@Model/todos/add" method="POST">
    <input type="text" name="Title"/>
    <br />
    <input type="date" name="Deadline"/>
    <br />
    <input type="text" name="TodoListId" value="@Model"/>
    <br/>
    <input type="submit" value="Create" class="btn btn-primary"/>
</form>
@EndSection

И одно частичное – Partial – представление для списка задач.

В процессе написания этого проекта наткнулся на два недостатка SSVE:

  • Super Simple View Engine не может формировать вложенные списки – для этого необходимо городить костыли с шаблонами. То есть, если у нас есть список списоков, то нельзя в одном представлении сделать что-то похожее на
<ul>
  @Each.TodoLists <!--пробегаемся по всем спискам задач-->
  <li>
    <ol>
    @Current.Each.Todos <!--пробегаемся по всем задачам текущего списка-->
    <li>...</li>
    </ol>
  @EndEach
  </li>
</ul>
@EndEach
  • В представлении нельзя обратиться к элементу списка по его индексу (так пишут на stackoverflow). Т.е. @Model.Todos[0].Title работать не будет.

Как бы заключение

Как вы могли заметить – NancyFX довольно типичный MVC-фреймворк, но со своими плюшками.

Полный код проекта можно найти на github.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s