Еще немного о NancyFX
Введение
Сегодня я хотел бы немного углубиться в “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.