Референсная архитектура приложения на asp.net mvc
TRANSCRIPT
Референсная архитектура приложения на ASP.NET MVC
Андрей МайоровBYTE-force
twitter.com/xorets
MVC
Model-View-Controller
• Разделяет данные и представление• Модель: активная или пассивная
Controller
View Model
Holy war
• Где держать бизнес-логику?• Можно брать доменные объекты в модель?
Модификации: MVP
• Model-View-Presenter
Presenter
View Model
Модификации: MVVM
• Model-View-ViewModel
ViewModelView Model
ASP.NET
Олдскульный .ASPX
• <% … %>• Code behind• Web controls
• Раздутый HTML на выходе
Олдскульный .ASPX
// Hello World in C#class HelloWorld {
static void Main() {
System.Console.WriteLine("Hello, World!"); }
}
ASP.NET MVC
Since 2009 A.D.
Контроллер
public class HomeController : Controller{
public ActionResult Index(){
var model = …return View(model); // of type ViewResult
}}
Маршруты
routes.MapRoute(name: "Default",url: "{controller}/{action}/{id}",defaults: new {
controller = "Home", action = "Index", id = UrlParameter.Optional
} );
/home/index
Передача параметров
routes.MapRoute(url: "{controller}/{action}/{id}",…
/home/index/123?x=red
public ActionResult Index( int id, string x ) { … }
View
• «Представление», «вьюха»• Модель → HTML• Отделено от контроллера
Реализация вьюхи
• Файл /view/home/index.*• Расширения файлов:• ASPX → Web Forms Engine• CHTML → Razor• ... → Что душа пожелает
DemoШаблонный проект
Проблема: 1 метод на 1 URL
• Огромные контроллеры• Нечеткие критерии группировки методов• Мало базовых классов• Сервисные методы раздувают
контроллеры
Решение:Только один action в
контроллере
Один action на контроллер
• Execute()• Исключение из правил – NextPage ()
Single responsibility principle
Kаждый объект должен иметь одну обязанность и эта обязанность должна быть полностью инкапсулирована в класс.
SOLID
Сложности с именами view
/View/Home
/Execute.chtml/News
/Execute.chtml/Products
/Execute.chtml/NextPage.chtml
/Product/Execute.chtml
Свой ViewEngine
/View/Home.xslt/News.xslt/Products.xslt/Products_NextPage.xslt/Product.xslt
Преимущества
• Один контроллер – одно действие• Контроллеры маленькие• Общий функционал → в базовый класс• Создаем DSL*• Дружит с Dependency Injection*
ControllerBase
+Model
+FillCommonData()
IndexController
+Execute()
ListControllerBase
NewsController
+Execute()
ProductsController
+Execute()
Правила
1. Один контроллер – одно действие
Модель
Модель
• Данные для View• НЕ интерфейс получения/записи данных*
* — холивар варнинг
ViewModel vs.
Domain Objects
Типизированная модельvs.
Динамическая
Typed vs. Dynamic
public ActionResult Execute(){
var model = new Model();…return View(model);
}
dynamic model = new ExpandoObject();
Модель – член класса
Правила
1. Один контроллер – одно действие2. Модель – член класса
Декларация маршрутов
Явная декларация маршрута
routes.MapRoute( name: "Home", url: "home",new { controller = "Home", action = "Execute" });
routes.MapRoute( name: "News", url: "news",new { controller = "News", action = "Execute" });
Url.RouteUrl( home ) // Returns “/home”
Правила
1. Один контроллер – одно действие2. Модель – член класса3. Задавайте маршруты явно
DemoSingle action controllers
Domain Specific Language
Domain Specific Language
public ActionResult Execute(){
CheckAccessRights( “index” );Model.news = SelectObjects()
.OfType( “article” )
.FilterPublished()
.Execute();
return View(Model); }
ControllerBase
+Model
+FillCommonData()+CheckAccessRights()+SelectObjects()
IndexController
+Execute()
ListControllerBase
+PreparePagingData()
NewsController
+Execute()
ProductsController
+Execute()
Сервисные классыControllerBase
+Model
+FillCommonData()+CheckAccessRights()+SelectObjects()
IndexController
+Execute()
ListControllerBase
+PreparePagingData()
NewsController
+Execute()
ProductsController
+Execute()
NewsService
+GetNews()
DemoDSL and helpers
Правила
1. Один контроллер – одно действие2. Модель – член класса3. Задавайте маршруты явно4. Делайте собственные DSL
Dependency Injection
Dependency inversion principle
Модули верхних уровней не должны зависеть от модулей нижних уровней. Модули должны зависеть от абстракций.
Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
SOLID
Dependency Inversion
IndexController
+Execute()
NewsServiceImpl
+GetNews()
INewsService<<interface>>
+GetNews()
IndexController
+Execute()
NewsServiceImpl
+GetNews()
INewsService<<interface>>
+GetNews()
Assembler
+Resolve()
Dependency Injection
Указание зависимостей
• Параметры конструктора• Свойства с атрибутами• Правила именования полей и аргументов
Указание зависимостей
public class HomeController : Controller{
[Dependency] public INewsService NewsService { get; set; }
public HomeController( IDatabase database ) { … }…
}
Настройка DI-контейнера
• Связь между интерфейсом и реализацией*• Контроль за жизнью объекта
* Не обязательно
<register type="INewsService"mapTo="NewsService"><lifetime type="singleton"/>
</register>
DemoDependency Injection
DI delivers
• Уменьшает связность приложения• Помогает писать модульные тесты• Заставляет правильно мыслить
Правила
1. Один контроллер – одно действие2. Модель – член класса3. Задавайте маршруты явно4. Делайте собственные DSL5. Используйте DI
Редактирование данных
Обычный процесс
1. Браузер запрашивает URL2. Сервер формирует HTML, шлет на клиент3. Браузер показывает HTML4. Пользователь редактирует данные. Submit5. Браузер шлет данные в POST-запросе6. Сервер обновляет базу, goto 2
Проблемы обычного подхода
• Сериализация-десериализация состояния• Постоянные перезагрузки• Трафик
Ajax-редактирование
1. Браузер запрашивает URL2. Сервер формирует HTML, шлет на клиент3. Браузер показывает HTML4. Пользователь редактирует данные. Submit5. Браузер шлет данные в POST-запросе6. Сервер обновляет базу7. goto 4
JSON
// Браузер шлет серверу{
id: 245,price: 19.95
}
// Сервер отвечает{ success: true }
Ajax delivers
• Не надо сериализовать всю страницу• При ошибке можно повторить• Трафик меньше на порядки
Правила
1. Один контроллер – одно действие2. Модель – член класса3. Задавайте маршруты явно4. Делайте собственные DSL5. Используйте DI6. Используйте Ajax для редактирования
AngularJS - MVVM
// Model$scope.id = 245;$scope.title = “Red button”;$scope.price = 19.95;
<!-- View -->Price for {{title}}: <input type=“text” ng-model=“price” />