ASP.NET MVC за пределами Hello World
1
Дятлов Александр
О себеВеб-разработка около 4х лет
ASP.NET MVC более 3х лет
Благодаря ASP.NET влюбился в платформу .NET
Люблю выпить и поговорить про архитектуру :)
2
О чем доклад?Архитектура MVC
Организация бизнес логики приложения
Повторное использование кода
Немного о модульном тестировании
Обработка исключительных ситуаций
3
История паттерна MVC
Трюгве Миккель Хейердал Реенскауг
1979
Smalltalk
Xerox
4
Пассивная модель
5
Controller
View Model
Активная модель
6
Controller
View Model
Первые шаги в изученииМануалы
Сайт ASP.NETHabrahabr Tutorials...
Учебники по ASP.NET MVCАдам ФрименДжеффри Палермо
...
7
8
Проблемы понимания
Последствия
1. Бизнес-логика в контроллерах
9
public ActionResult GetByTagAndDate( string tagName, DateTime date, string authorName)
{
var posts = _dbContext.Posts.Where(p => p.Tags.Any(t => t.Name == tagName)
&& p.CreateOn < date
&& p.Author.Name == authorName);
if (!posts.Any())
return HttpNotFound();
return View(posts);
}
10
public ActionResult GetByTagAndDate(string tagName, DateTime date, string authorName) {
...
bool troll = (bool)Session["IsTroll"];
if (troll || _dbContext.Posts.Where(p => p.Author.Name == User.Identity.Name).SelectMany(p => p.Marks).Count(m => m.IsLike == true) > _dbContext.Marks.Count(m => m.Author.Name == User.Identity.Name && m.IsLike
== false))
ViewBag.IsTroll = true;
...
11
И еще немножко бизнес-логики
12 public class PostsV1Controller : Controller {
13 public ActionResult GetByTagAndDate(string tagName, DateTime date, string authorName) { … }
40 public ActionResult GetPostByTitle(string title) { … }
90 public ActionResult GetAll() { … }
1090 public ActionResult History() { … }
}12
И начинается АД
Последствия1. Бизнес-логика в контроллерах2. Использование динамических объектов для передачи данных
представлению
13
public ActionResult GetByTagAndDate(string tagName, DateTime date,
string authorName)
{
...
ViewBag.IsTroll = true;
return View(posts);
}
14
Потенциальное падение во время исполнения
Проверка на стороне представления
@{
var troll = false;
if (ViewBag.IsTroll != null)
troll = (bool)ViewBag.IsTroll;
}
@if (troll) {
<p>Ты мерзкий тролль!!!</p>
}
15
Последствия1. Бизнес-логика в контроллерах2. Использование динамической типизации для передачи данных
представлению3. Навешивание на бизнес-сущность атрибутов валидации
16
public class User_Entity {
[Required]
[StringLength(100)]
[Remote("CheckUserName", "Account")]
public string Name { get; set; }
[Required]
[SecurePassword]
public string Password { get; set; }
}
17
public class User_Entity {
[Required]
[StringLength(100)]
[Remote("CheckUserName", "Account")]
public string Name { get; set; }
[Required]
[SecurePassword]
public string Password { get; set; }
}
18
Последствия1. Бизнес-логика в контроллерах2. Использование динамической типизации для передачи данных
представлению3. Навешивание на бизнес-сущность атрибутов валидации4. Использование одной модели для отображения и получения
данных
19
20
User_Entity[Required]
[StringLength(100)]
[Remote("CheckUserName", "Account")]
public string Name { get; set; }
[Required]
[SecurePassword]
public string Password { get; set; }
Список пользователей
21
User_Entity[Required]
[StringLength(100)]
[Remote("CheckUserName", "Account")]
public string Name { get; set; }
[Required]
[SecurePassword]
public string Password { get; set; }
Список пользователей
Информация о пользователе
22
User_Entity[Required]
[StringLength(100)]
[Remote("CheckUserName", "Account")]
public string Name { get; set; }
[Required]
[SecurePassword]
public string Password { get; set; }
Список пользователей
Информация о пользователе
Сменить пароль
23
User_Entity[Required]
[StringLength(100)]
[Remote("CheckUserName", "Account")]
public string Name { get; set; }
[Required]
[SecurePassword]
public string Password { get; set; }
Список пользователей
Информация о пользователе
Сменить пароль
Регистрация
Взаимодействия компонентов
24
Контроллер Модель
Представление
Хранение(обычно в реляционнойбазе данных)
Запрос HTTP
Ответ Модель представления
Взаимодействия компонентов
25
Контроллер Модель
Представление
Хранение(обычно в реляционнойбазе данных)
Запрос HTTP
Ответ Модель представления
Обязанности контроллера
26
Cache
Session
Cookies
Controller
Обязанности контроллера
27
Сервисы
Запросы
...
Команды
User.Identity.Name...
Controller
28
Тестирование контроллеров
public ActionResult GetByTagAndDate(string tagName, DateTime date, string authorName)
{
...
bool troll = (bool)Session["IsTroll"];
...
return View(posts);
}
29
Зависимость от сессии
Если необходимость в тестировании контроллеров осталась?
30
public ActionResult GetByTagAndDate(string tagName, DateTime date, string authorName,
Troll troll)
{
...
if (troll)
...
return View(posts);
}
31
Получение через параметры
public class TrollModelBinder : IModelBinder {public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext) {var troll =
(Troll)controllerContext.HttpContext.Session["IsTroll"];
if (troll == null) {troll = new Troll();controllerContext.HttpContext.Session["IsTroll"] = troll;
}return troll;
}}
32
Кастомный биндинг
Регистрация
protected void Application_Start()
{
...
ModelBinders.Binders.Add( typeof(Troll), new TrollModelBinder());
BundleTable.Bundles.EnableDefaultBundles();
...
}
33
Взаимодействия компонентов
34
Контроллер Модель
Представление
Хранение(обычно в реляционнойбазе данных)
Запрос HTTP
Ответ Модель представления
Модель / Виды
35
АМ~80%
БМ~20%
Личного опыт
АМ / БМАнемичная модель (бедная модель)
Сущности представляют данные
Сущности это плоские классы
Богатая модель
Единый язык между разработчиком и специалистом
Модель это дистиллированное знание36
Анемичная модель
37
CRUD_Service<T>
+Create()
+Read()
+Update()
+Delete()
Post
Topic
Tag
...
38
Единый механизм
В реальном мире
Интернет-блог
Каталог товаров
Сайты галереи
Сайты портфолио
Сайты визитки
и т.д.
39
В реальном мире
Интернет-блог
Каталог товаров
Сайты галереи
Сайты портфолио
Сайты визитки
и т.д.
40
Богатая модель / Кредит
41
Сredit_Entity
int DaysCountfloat InterestRatedecimal Sum
decimal Payment()
Богатая модель / Грузоперевозки
42
Shipping_Entity
float CargoTotalfloat CargoCurrentint OverflowRate
bool Add(float cargo)float FreePlace()
В реальном миреАвтоматизация банковских систем
Автоматизация транспортной логистики
Экспертные системы
и т.д.
43
В реальном миреАвтоматизация банковских систем
Автоматизация транспортной логистики
Экспертные системы
и т.д.
44
Доступ к данным
45
DAL / РекомендацииPersistence Ignorance
Сущности не должны зависеть от способа хранения данных
Использование паттерна Repository
46
Repository
47
CRUD_Repository<T>
+Create()
+Read()
+Update()
+Delete()
Post
Topic
...
CRUD_Service<T>
Troll_Detector
+ Detect()
DAL / Рекомендации
Persistence Ignorance
Сущности не должны зависеть от способа хранения данных
Использование паттерна Repository
Более гибкое решение:
Использование CQRS
48
Взаимодействия компонентов
49
Контроллер Модель
Представление
Хранение(обычно в реляционнойбазе данных)
Запрос HTTP
Ответ Модель представления
public class PostsGetByTagAndDateViewModel {
public IEnumerable<Post> Posts { get; set; }
public bool IsTroll { get; set; }
public PostsGetByTagAndDateViewModel (IEnumerable<Post> posts, bool isTroll) {
Posts = posts;
IsTroll = isTroll;
}
}50
Модель представления
Заполнение моделиpublic ActionResult GetByTagAndDate(string tagName, DateTime date,
string authorName, TrollState trollState)
{
...
if (!posts.Any())
return HttpNotFound();
return View(new PostsGetByTagAndDateViewModel(posts, trollState.IsTroll));
}
51
public class RegistrationViewModel {
[Required]
[StringLength(100)]
[Remote("CheckUserName", "Account")]
public string Name { get; set; }
[Required]
[StringLength(100)]
[SecurePassword]
public string Password { get; set; }
}
52
Классы помощникиpublic static class EmailHelpers {
public static IHtmlString Email(this HtmlHelper html, string address, string name = null) {
return new HtmlString($"<a href=\"mailto: { address }\"> { name ?? address } </a>");
}
}
<span>@Html.Email("[email protected]")</span>
<span>@Html.Email("[email protected]", "Отправить письмо")</span>
53
Классы помощники
@helper Email(string address, string name = null){
<a href="mailto:@(address)">@(name ?? address) </a>}
<span>@Helpers.Email("[email protected]")</span>
<span>@Helpers.Email("[email protected]", "Отправить письмо")</span>
54
App_Code\Helpers.cshtml
Классы помощники
@helper Email(string address, string name = null){
<a href="mailto:@(address)">@(name ?? address) </a>}
<span>@Helpers.Email("[email protected]")</span>
<span>@Helpers.Email("[email protected]", "Отправить письмо")</span>
55
App_Code\Helpers.cshtml
JavaScript код в представлениях
56
@model IEnumerable<Post>@{
Layout = "~/Areas/Admin/Views/Shared/_Layout.cshtml";var ajaxSave = new AjaxOptions {
Url = Url.Action("Edit"),OnSuccess = "ajaxSuccess"
};}
<table class="table-striped table-hover tablesorter table">...</table>
<script type="text/javascript" src="@Url.Content("~/Scripts/table.js")"></script><script type="text/javascript">
$(".tablesorter").tablesorter();$("#TableId").change(function () {
window.location.href = "/Posts/Edit/" + $("#PostId").val(); });
</script>
57
58
Вынос JS кода из представлений
App.views.Posts.Troll = ( function () {// private scope
return {init: function () {
// public API}
}})(jQuery);
59
Вызов в представленииPostsTroll.js
App.views.Posts.Troll = (function () {// private scope
return {init: function () {
// public API}
}})(jQuery);
<div> </div>
<script> App.views.Posts.Troll.Init();</script>
Проблема HTTP 1.0 / 1.1Дополнительные расходы на установку соединения для каждого запроса
Конвейерная передача HTTP
Параллельные HTTP соединения в современных браузерах от 4 до 8
60
Проблема HTTP 1.0 / 1.1Дополнительные расходы на установку соединения для каждого запроса
Конвейерная передача HTTP
Параллельные HTTP соединения в современных
браузерах от 4 до 8
61
МинификацияОбъединение JS в один файл при помощи bundle
62
public class BundleConfig {
public static void RegisterBundles(BundleCollection bundles) {
bundles.Add(new ScriptBundle("~/scripts/jquery").Include("~/Scripts/jquery-{version}.js").Include("~/Scripts/PostsTroll.js"));
}
}
МинификацияОбъединение JS в один файл при помощи bundle
Использование сторонних сборщиков
gulpgrunt...
63
public class BundleConfig {
public static void RegisterBundles(BundleCollection bundles) {
bundles.Add(new ScriptBundle("~/scripts/jquery").Include("~/Scripts/jquery-{version}.js").Include("~/Scripts/PostsTroll.js"));
}
}
Собрать части в целое
64
Архитектура современного веб-приложения
65
ВебUser Interface
Технические сервисы
Technical Services
Зави
сим
ости
Вспомогательная логикаServices
Бизнес-логика (М)Models
Dom
ain
Что делать если?
66
ВебUser Interface
Технические сервисы
Technical Services
Зави
сим
ости
Боле
е сп
ециф
ичес
кая
логи
ка
Вспомогательная логикаServices
Бизнес-логика (М)Models
Dom
ain
Инверсия управления (IoC)
67
ВебUser Interface
Технические сервисы
Technical Services
IPayService Зави
сим
ости
Боле
е сп
ециф
ичес
кая
логи
ка
Вспомогательная логикаServices
Бизнес-логика (М)Models
Dom
ain
Инверсия управления (IoC)
68
ВебUser Interface
Технические сервисы
Technical Services
IPayService
PayServiceIPayService
Зави
сим
ости
Боле
е сп
ециф
ичес
кая
логи
ка
Вспомогательная логикаServices
Бизнес-логика (М)Models
Dom
ain
Регистрация зависимостей
public class DIConfig : Module {
protected override void Load(ContainerBuilder builder) {
builder.RegisterType< PayService>().As<IPayService>();
…
base.Load(builder);
}
}
69
Что делать если что-то пошло не так?
70
Иерархия исключений .NET
71
Exception
ApplicationExceptionSystemException
Но не бывает так легко
72
Решение
73
DemoException
ApplicationException
Решение
74
DemoException
LogicExceptionFatalException
ApplicationException
Обрабатываем конкретные типы
75
try {
// Логика подтверждения оплаты
}
catch (PaymentNotVerifiedLogicException ex)
{
PaymentVerified = false;
_logger.Warning($"При оплате возникли проблемы { ex }");
}
Полезные материалыhttp://www.asp.net
http://sergeyteplyakov.blogspot.ru
http://metanit.com/sharp/mvc5/23.1.php
http://professorweb.ru/my/ASP_NET/mvc/level1/1_7.php
https://habrahabr.ru/post/73692/
http://andrey.moveax.ru/page/aspnet-mvc-3-in-depth
76