- PVSM.RU - https://www.pvsm.ru -
Доброго всем времени суток, в этой статье хочу осветить ещё один компонент из библиотеки Incoding Framework.
Model View Dispatcher (MVD) — позволяет избавится от избыточного кода (а именно asp.net mvc controller) и упростить навигацию по проекту, уменьшив количество абстракций между клиентским и серверным кодом.
На хабре имеются несколько статей о IML и CQRS, которые входят в состав framework
MVD фигурировал в некоторых статьях, но как часть примера демонстрирующая возможности IML, поэтому я решил рассказать о нем отдельно, но обо всем по порядку…
Рассмотрим сценарий применения asp.net mvc + cqrs
public ActionResult Details(GetUserDetailsQuery query)
{
var model = dispatcher.Query(query);
return Json(model);
}
примечание: action отвечает за binding и передачу query в Dispatcher для возврата полученных данных в виде Json
$.get('@Url.Action("Details","Controller")',callback)
примечание: не хватает листинга Query для полной картины, но становится очевидно избыточность Action
Чтобы посадить controller на «диету» можно воспользоватся паттерном Mediator (как один из вариантов), который опирается на Interface и Generic, но это только позволяет упростить и объединить код, но не решить проблему полностью, потому что все равно приходится писать однотипные Controller/Action.
Model View Dispatcher (MVD) — позволяет выполнять Command/Query в «обход» asp.net mvc. Для демонстрации перепишем предыдущую задачу, но с MVD
$.get('@Url.Dispatcher().Query(new GetUserDetailsQuery()).AsJson()',callback)
Чтобы получить ответ, посмотрим как подключить MVD к проекту:
Создать DispatcherController (начиная с версии 1.1 устанавливается через nuget), который унаследовать от DispatcherControllerBase
public class DispatcherController : DispatcherControllerBase
{
public DispatcherController()
: base(typeof(T).Assembly)
{ }
}
примечание: конструктор принимает Assembly в котором объявлены Command/Query
DispatcherControllerBase [5]содержит следующие методы (Actions):
Query(string incType, string incGeneric, bool? incValidate)
Render(string incView, string incType, string incGeneric)
Push(string incType, string incGeneric)
Composite(string incTypes)
QueryToFile(string incType, string incGeneric, string incContentType, string incFileDownloadName)
пример url, после вызова которого будет выполнен Push command
Url.Action("Push", "Dispatcher", new { incType = typeof(Command).Name } )
Альтернативный способ через DSL (domain specific language)
Url.Dispatcher().Push(new Command())
примечание: дело не только в лаконичности синтаксиса (хотя это важно), но также в абстракции от деталей (имена параметров и т.д.)
Обратить внимание
{ success: true/false , data:something/null , redirectTo:url/null }
Вывод: текущая реализация MVD опирается на asp.net mvc, по факту просто делает обобщенный (не generic) Controller, но можно использовать httphandler или другой http обработчик в качестве платформы.
MVD покрывает большинство сценариев, которые встречаются при веб-разработке на платформе asp.net mvc:
примечание: исходный код примеров на GitHub [8]
Url.Dispatcher().Push(new AddUserCommand
{
Id = "59140B31-8BB2-49BA-AE52-368680D5418A",
Name = "Vlad"
})
примечание: вопросы валидирования далее
Url.Dispatcher().Push(new AddEntityCommand<T>())
Url.Dispatcher()
.Push(new AddUserCommand { Id = "1", Name = "Name" })
.Push(new ApproveUserCommand { UserId = "2" })
Url.Dispatcher()
.Query(new GetCurrentDtQuery())
.AsJson()
Url.Dispatcher()
.Query(new GetTypeNameQuery<T>())
.AsJson()
Url.Dispatcher()
.Query(new GetUserQuery())
.AsView("~/Views/Home/User.cshtml")
примечание: путь к View строится от корневой (в asp.net mvc относительно Controller) директории сайта, что позволяет строить любую структуру папок.
<a href="@Url.Dispatcher().Query(new GetFileQuery()).AsFile(incFileDownloadName: "framework")">Download</a>
примечание: требуется реализация QueryBase<byte[]> для Query
Url.Dispatcher()
.Model(new GetUserQuery.Response
{
Id = "2",
Name = "Incoding Framework"
})
.AsView("~/Views/Home/User.cshtml")
Url.Dispatcher().AsView("~/Views/Home/Template.cshtml")
MVD состоит из DispatcherController (серверная часть), который используя инфраструктуру CQRS выполняет Command/Query и Url.Dispatcher (адрес строится на сервере, но далее используется на клиенте) для построения url, но не является самостоятельным компонентом, поэтому примеры будут в контексте с IML.
@(Html.When(JqueryBind.Change)
.AjaxPost(Url.Dispatcher().Push<AddAcoGroupCommand>(new {Value = Selector.Jquery.Self() } ))
.OnSuccess(dsl => dsl.Utilities.Window.Alert("Success"))
.AsHtmlAttributes()
.ToCheckBox(true))
примечание: в качестве параметра используется анонимный объект, но проверка на соответствие (если Value отсутствует в AddAcoGroupCommand будет exception) полей будет.
@model AddAcoGroupCommand
<form action="@Url.Dispatcher().Push(new AddAcoGroupCommand())">
@Html.HiddenFor(r=>r.Id)
<input type="submit"/>
</form>
@(Html.When(JqueryBind.InitIncoding)
.AjaxGet(Url.Dispatcher().AsView("~/Views/Patient/BenefitListControl.cshtml"))
.OnSuccess(dsl => dsl.Self().Core().Insert.Html())
.AsHtmlAttributes()
.ToDiv())
примечание: удобно для поиска template
var urlTmpl = Url.Dispatcher().AsView("~/Views/Medication/MedicationTmpl.cshtml");
dsl.Self().Core().Insert.WithTemplateByUrl(urlTemplate).Append();
@(Html.When(JqueryBind.InitIncoding)
.AjaxGet(Url.Dispatcher()
.Model( new BenefitModel()
{
GroupName = Selector.Incoding.QueryString<BenefitModel>(r=>r.GroupName),
IsPrimary = true
})
.AsView("~/Views/Patient/BenefitListControl.cshtml"))
.OnSuccess(dsl => dsl.Self().Core().Insert.Html())
.AsHtmlAttributes()
.ToDiv())
примечание: возможность использовать Selector (вычисляется на клиенте) при формировании url позволяет не прибегать к построению routes на клиенте.
примечание: больше примеров в Inc-todo [9]
Это мощный механизм для реализации АОП (Аспектно-ориенти́рованное программирование), частым сценарием где он применяется, может быть проверка авторизации, поэтому рассмотрим возможные пути решения:
В рамках Incoding Framework есть готовая инфраструктура для валидации (в отличии от js framework тут Server/Client покрытие) и MVD интегрирован с ней.
@(using(Html.When(JqueryBind.InitIncoding)
.Direct()
.OnSuccess(dsl => dsl.Self().Core().Form.Validation.Parse())
.When(JqueryBind.Submit)
.PreventDefault()
.Submit()
.OnError(dsl => dsl.Self().Core().Form.Validation.Refresh())
.AsHtmlAttributes()
.ToBeginForm(Url.Dispatcher().Push(new AddUserCommand()))))
{
@Html.TextBoxFor(r=>r.Name)
@Html.ValidationMessageFor(r=>r.Name)
}
примечание: поскольку код будет однотипный для большинства форм, то можно написать html helper
Код знаком большинству разработчиков на asp.net mvc по стандартному Html.BeginForm (можно использовать его передав iml в html attributes), но с несколькими отличиями:
Для понимания, как работает метод Validation.Refresh посмотрим реализацию Push в dispatcher contoller
if (!ModelState.IsValid)
return IncodingResult.Error(ModelState)
try
{
dispatcher.Push(composite);
return IncodingResult.Success();
}
catch (IncWebException exception)
{
foreach (var pairError in exception.Errors)
{
foreach (var errorMessage in pairError.Value)
ModelState.AddModelError(pairError.Key, errorMessage);
}
return IncodingResult.Error(ModelState)
}
примечание: реальный код несколько сложнее из-за дополнительной логики, но пользователь Incoding Framework абстрагирован от этих деталей и работает с TryPush или MVD push
В catch мы перехватываем только IncWebException (на основе которого заполняем ModelState), а остальные exception считаем провальными и оставляем их обработку на совесть global.asax.
Ответ поможет проанализировать, чем же решение на базе Incoding Framework лучше того, что имеется в стандартном asp.net mvc
public ActionResult Add(AddUserCommand command)
{
if (ModelState.IsValid)
return View(command);
return Execute(command);
}
Если ModelState содержит ошибки, возвращается View, которое строится на основе command (хорошо, если не используется Container с дополнительными списками, которые тоже придется заново строить), чтобы сохранить состояние. Такое поведение введет к следующим проблема:
MVD является очень хорошим союзником для борьбы с однотипными Action, которые приводят к «разбуханию» кода, что особенно критично на поздних этапах проекта. MVD можно использовать без IML, но тогда теряется возможность использовать Selector в routes, что негативно скажется на типизации, из-за того, что придется в «ручную» (pure js) собирать параметры.
Конечно, могут быть сценарии с которыми MVD не способен (временно или просто не возможно реализовать) справится, но ничто не мешает написать Controller и Action для конкретных (может это всего 5% — 10%) случаев.
Если MVD поможет сократить код на 10-15% и ускорить разработку, то это уже очень хороший результат, но мы пошли дальше и реализовали возможность строить схему end point (идея взята у wcf endpoint)…
примечание: пример [10] такой страницы, которая находится на ранней стадии разработки, но отражает общую картину, то есть по большей части не хватает внимания к деталям, чтобы упаковать это решение в готовую библиотеку.
Вывод: возможность строить документацию на основе исходного кода, повышает обратную связь между разработчиками API и мобильного приложения. Кроме документации, можно расширить функционал, реализовав статистику запросов, профилирование и многое другое, что становится возможным реализовать из-за унифицированного кода, который можно разбирать и анализировать через рефлексию.
P.S. опубликована новая версия (пока beta) Incoding Framework в nuget, где появилось много нововведений и доработок. Я приведу несколько, а остальные постараюсь расписать в отдельных статьях (полный список на нашем bugtracker [11])
Break.If(r=>r.Is(()=>Selector.Jquery.Self()).And.Is(()=>"id".ToId()== 12)) // Old
Break.If(()=>Selector.Jquery.Self() && id.ToId() == 12) // New
inDsl.Core().JQuery.Attributes.SetAttr(HtmlAttribute.Checked) // Now
inDsl.Attr.Set(HtmlAttr.Checked) // Future
примечание: причина статуса beta (в конце мая уже будет стабильная версия) в том, что ещё не все наши проекты переведены на эту версию и остался ряд задач, которые планировали для этого релиза
Автор: vkopachinsky
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/asp/59506
Ссылки в тексте:
[1] Знакомство с IML: http://habrahabr.ru/post/209734/
[2] Incoding CQRS: http://habrahabr.ru/post/211206/
[3] IML vs AngularJs : http://habrahabr.ru/post/214293/
[4] Ответ на IMl vs AngularJs : http://habrahabr.ru/post/214963/
[5] DispatcherControllerBase : https://github.com/IncodingSoftware/Incoding-Framework/blob/master/src/Incoding/MvcContrib/MVD/DispatcherControllerBase.cs
[6] пример : https://github.com/IncodingSoftware/MVD/blob/master/MVD.Domain/Infrastructure/Bootstrapper.cs
[7] IncodingResult: https://github.com/IncodingSoftware/Incoding-Framework/blob/master/src/Incoding/MvcContrib/Core/IncodingResult.cs
[8] GitHub: https://github.com/IncodingSoftware/MVD
[9] Inc-todo: https://github.com/IncodingSoftware/inc-todo
[10] пример: http://mvd-endpoint.incframework.com/
[11] bugtracker: http://youtrack.incoding.biz/issues/Inc?q=%231.3+
[12] github: https://github.com/IncodingSoftware/Inc-Orm-Benchmark
[13] inc-todo-ravendb: https://github.com/IncodingSoftware/inc-todo-raven
[14] Источник: http://habrahabr.ru/post/221585/
Нажмите здесь для печати.