Введение

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

NancyDemo

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

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

Структура шаблонного проекта довольно проста: [code lang=text] - Content -- *.css -- *.png -- img -– *.png -- scripts -– *.js - Modules -- IndexModule.cs - Views -- index.hmtl -- layout.html Bootstrapper.cs [/code] Папка 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-запроса на добавление списка задач или задачи. Полный код модуля выглядит следующим образом: [code lang=text] 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} лучше посмотреть здесь. 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]; } } [/code]

Модели

Останавливаться детально на моделях не буду. Хочется остановиться чуточку более подробно лишь на одом аспекте - model binding. Model binding - это такая штука, которая позволяет из переданных параметров (через строку запроса GET или форму POST) инстанциировать объект нужного класса: [code lang=text] // Это как бы модель public class Foo { public string Name { get; set; } public int Age { get; set; } } [/code] Это наше простенькое представление [code lang=text] <form method=“post” action=“/foo/create”> <input name=“Name”/> <input name=“Age”/> <input type=“submit” value=“Save”/> </form> [/code] Это типа модуль: [code lang=text] public class FooModule: NancyModule { public FooModule() { Post[“/foo/create/”] = parameters => { var newFoo = this.Bind<Foo>(); // здесь в newFoo попадут параметры, которые пришли с клиента в parameters. return new RedirectAction(“/”); // Перекинем пользователя обратно на главную страницу приложения. } } } [/code]

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

Как в любом MVC-фреймворке в NancyFX есть представления. Более того, следуя своей основной концепции (возможность подменить или изменить любую компоненту фреймворка) стандартный View Engine - SuperSimpleViewEngine - на другой. Например, на знакомый по ASP.NET MVC - Razor. К тому же, по аналогии с другими MVC-фреймворками, в представление можно передать модель: [code lang=text] Get[“/products”] = parameters => { // products.html - имя представления; someModel - модель, которая “приедет” в представление. return View[“products.html”, someModel]; }; [/code] В представлении можно обращаться к свойствам модели используя код похожий на следующий: [code lang=text] <!–@Model - зарезервированное слово.–> <p>Hello, @Model.UserName</p> [/code] В этом проекте не будет ничего усложнять и воспользуемся встроенным SuperSimpleViewEngine, который имеет довольно приличные возможности. В приложении будет три представления:

  • index.html - главная страница сайта:

[code lang=text] @Master[‘layout’] @Section[‘Content’] @Each <h3>@Current.Title</h3> @Partial[‘list.html’, @Current.Todos] @EndEach <h3><a href=“/todolists/add”>Добавить новый список</a></h3> @EndSection [/code]

  • todolist/create.html - страница для добавления списка задач:

[code lang=text] @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 [/code]

  • todo/create.html - страница для добавления задачи;

[code lang=text] @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 [/code] И одно частичное - Partial - представление для списка задач. В процессе написания этого проекта наткнулся на два недостатка SSVE:

  • Super Simple View Engine не может формировать вложенные списки - для этого необходимо городить костыли с шаблонами. То есть, если у нас есть список списоков, то нельзя в одном представлении сделать что-то похожее на

[code lang=text] <ul> @Each.TodoLists <!–пробегаемся по всем спискам задач–> <li> <ol> @Current.Each.Todos <!–пробегаемся по всем задачам текущего списка–> <li>…</li> </ol> @EndEach </li> </ul> @EndEach [/code]

  • В представлении нельзя обратиться к элементу списка по его индексу (так пишут на stackoverflow). Т.е. @Model.Todos[0].Title работать не будет.

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

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