59
Как создать по-настоящему гибкое ASP.NET MVC приложение Александр Зайцев IndyCode twitter.com/hazzik Вторая конференция .NET разработчиков

как создать по настоящему гибкое приложение

Embed Size (px)

Citation preview

Page 1: как создать по настоящему гибкое приложение

Как создать по-настоящему гибкое ASP.NET MVC приложение

Александр ЗайцевIndyCode

twitter.com/hazzik

Вторая конференция .NET разработчиков

Page 2: как создать по настоящему гибкое приложение

Давайте познакомимся?!

Page 3: как создать по настоящему гибкое приложение

О чем эта презентация?

• Как избежать дублирования кода на всех уровнях MVC

• Как эффектино повторно использовать компоненты приложений

• Как протестировать все, ну или почти все

Page 4: как создать по настоящему гибкое приложение

Что мы используем?

Web framework:• ASP.NET MVC http://www.asp.net/mvc • MvcExtensions http://mvcextensions.codeplex.com/

ORM:• NHibernate http://nhforge.org/• FluentNHibernate http://fluentnhibernate.org/

IoC:• Castle.Windsor http://castleproject.org/

Object-object mapper:• AutoMapper http://automapper.codeplex.com/

Page 5: как создать по настоящему гибкое приложение

Чем мы это тестируем?

TDD:• xUnit.net http://xunit.codeplex.com/• Moq http://code.google.com/p/moq/

Acceptance testing:• SpecFlow http://www.specflow.org/• Selenium http://seleniumhq.org/• Autoit http://www.autoitscript.com/

Page 6: как создать по настоящему гибкое приложение

У вас есть проблемы?

Page 7: как создать по настоящему гибкое приложение

Давайте их

решать!?

Page 8: как создать по настоящему гибкое приложение

MVC

Model

ControllerView

Page 9: как создать по настоящему гибкое приложение

View

Page 10: как создать по настоящему гибкое приложение

Проблема первая:дублирование кода

Page 11: как создать по настоящему гибкое приложение

Проблема@using (Html.BeginForm()) {

@Html.ValidationSummary(true, "Попытка входа неудачна.")

<div class="editor-label">@Html.LabelFor(m => m.UserName)</div>

<div class="editor-field">

@Html.TextBoxFor(m => m.UserName)

@Html.ValidationMessageFor(m => m.UserName)

</div>

<div class="editor-label">@Html.LabelFor(m => m.Password)</div>

<div class="editor-field">

@Html.PasswordFor(m => m.Password)

@Html.ValidationMessageFor(m => m.Password)

</div>

<div class="editor-label">

@Html.CheckBoxFor(m => m.RememberMe)

@Html.LabelFor(m => m.RememberMe)

</div>

<p><input type="submit" value="Впустите!" /></p>

}

Page 12: как создать по настоящему гибкое приложение

• Для каждого типа поля необходимо использовать свой хелпер. А если подходящего нет?

• Необходимо заботиться о вспомогательной верстке

• Много кода и дублирование;)

Page 13: как создать по настоящему гибкое приложение

Решение:Использовать мощь метаданных

@using (Html.BeginForm()) {

@Html.ValidationSummary(true, "Попытка входа неудачна.")

@Html.EditorForModel()

<p><input type="submit" value="Впустите!" /></p>

}

Page 14: как создать по настоящему гибкое приложение

• Не нужно думать какой тип поля используется

• Вспомогательная верстка из коробки• Стандартный вид• Расширяемо• Минусов нет

Page 15: как создать по настоящему гибкое приложение

Когда использовать?

• Много форм.• Все формы должны выглядеть

стандартно• Много полей на форме.

Page 16: как создать по настоящему гибкое приложение

Проблема вторая:сложность метаданных

Page 17: как создать по настоящему гибкое приложение

Проблемаpublic class Register

{

[Required]

[Display(Name = "Имя пользователя")]

public string UserName { get; set; }

 

[Required]

[DataType(DataType.EmailAddress)]

[Display(Name = "Адрес электронной почты")]

public string Email { get; set; }

 

[Required]

[ValidatePasswordLength]

[ValidatePasswordComplexy]

[DataType(DataType.Password)]

[Display(Name = "Пароль")]

public string Password { get; set; }

 

[DataType(DataType.Password)]

[Display(Name = "Пароль еще раз")]

[Compare("Password", ErrorMessage = "Пароль и подтверждение пароля должны совпадать")]

public string ConfirmPassword { get; set; }

}

Page 18: как создать по настоящему гибкое приложение

• Громоздко• Не расширяемо• Не поддерживаемо• Не тестируемо

Page 19: как создать по настоящему гибкое приложение

Решение:Использовать MvcExtensions

public class RegisterMetadata : ModelMetadataConfiguration<Register>

{

public RegisterMetadata()

{

Configure(x => x.Login)

.DisplayName("Имя пользователя")

.Required("Необходимо указать имя пользователя");

 

Configure(x => x.Email)

.DisplayName("Адрес электронной почты")

.Required("Необходимоуказать адрес электронной почты")

.AsEmail();

 

Configure(x => x.Password)

.DisplayName("Пароль")

.Required("Необходимо указать пароль")

.MinimumLength(6, "Длина пароля должна быть не меньше 6 символов")

.AsPassword();

 

Configure(x => x.ConfirmPassword)

.DisplayName("Пароль еще раз")

.Required("Необходимо указать подтверждение пароля")

.AsPassword()

.Compare("Password", "Пароль и подтверждение пароля должны совпадать");

}

}

Page 20: как создать по настоящему гибкое приложение

Примеры расширения:Календарик и справочник

public class SetResponsibleMetadata : ModelMetadataConfiguration<SetResponsible>

{

public SetResponsibleMetadata()

{

Configure(x => x.Deputy)

.AsReference("Пользователи",

x => x.Action("ListAllWithoutMe", "AccountsClassifier"))

.DisplayName("Выберите ответсвенного")

.Required("Не выбран ответсвенный");

 

Configure(x => x.FromTime)

.DisplayName("В период с")

.Required("Не выбран период времени")

.AsDatePicker(FutureOrPast.Future);

}

}

Page 21: как создать по настоящему гибкое приложение

Как это работает?public static ValueTypeMetadataItemBuilder<DateTime> AsDatePicker(

this ValueTypeMetadataItemBuilder<DateTime> itemBuilder, FutureOrPast futureOrPast)

{

itemBuilder.Template("DateTime");

 

var setting = itemBuilder.Item.GetAdditionalSettingOrNew<DateTimeSetting>();

setting.FutureOrPast = futureOrPast;

return itemBuilder;

}

public class DateTimeSetting : IModelMetadataAdditionalSetting

{

public FutureOrPast? FutureOrPast;

}

 

public enum FutureOrPast

{

Future,

Past

}

Page 22: как создать по настоящему гибкое приложение

• Расширяемо: возможности не ограничены

• Легко поддерживать• Это можно тестировать!• Минусов нет• Использовать всегда.

Page 23: как создать по настоящему гибкое приложение

Проблема третья:binding

Page 24: как создать по настоящему гибкое приложение

Collections binding

• Collection.cshtml• Добавить скрытое поле для индекса

элементов

Page 25: как создать по настоящему гибкое приложение

Пример@model IEnumerable

@{

if (Model != null) {

string oldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix;

var random = new Random();

ViewData.TemplateInfo.HtmlFieldPrefix = String.Empty;

 

foreach (object item in Model) {

int index = random.Next();

@Html.Hidden(string.Format("{0}.Index", oldPrefix), index)

string fieldName = string.Format("{0}[{1}]", oldPrefix, index);

@Html.EditorFor(_ => item, null, fieldName);

}

 

ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;

}

}

Page 26: как создать по настоящему гибкое приложение

Complex forms

• Object.cshtml• Убрать ограничение вложенности для сложных

объектов

Page 27: как создать по настоящему гибкое приложение

Пример@model IEnumerable

@{

if (Model != null) {

string oldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix;

var random = new Random();

ViewData.TemplateInfo.HtmlFieldPrefix = String.Empty;

 

foreach (object item in Model) {

int index = random.Next();

@Html.Hidden(string.Format("{0}.Index", oldPrefix), index)

string fieldName = string.Format("{0}[{1}]", oldPrefix, index);

@Html.EditorFor(_ => item, null, fieldName);

}

 

ViewData.TemplateInfo.HtmlFieldPrefix = oldPrefix;

}

}

Page 28: как создать по настоящему гибкое приложение

Проблема четвертая:

организация JavaScript

Page 29: как создать по настоящему гибкое приложение

Решение

• Вынести весь JavaScript из view в отдельные .js файлы

• Использовать паттерн модуль• Компоновать все .js файлы в один и

минимизировать его

Page 30: как создать по настоящему гибкое приложение

Пример<script type="text/javascript">

App.views.products.FindProducts.init();

</script>

App.namespace('App.views.products');App.views.products.FindProducts = (function () { //private scope return { init: function () { //public API } };})(jQuery);

Page 31: как создать по настоящему гибкое приложение

Controller

Page 32: как создать по настоящему гибкое приложение

Толстые контроллеры

Page 33: как создать по настоящему гибкое приложение

Обработка форм

Page 34: как создать по настоящему гибкое приложение

Обработка форм

• GET• POST• Redirect• GET

А если ошибка?

Page 35: как создать по настоящему гибкое приложение

Обработка форм[HttpPost]

public ActionResult LogOn(LogOn form, string returnUrl)

{

if (ModelState.IsValid)

{

if (MembershipService.ValidateUser(form.UserName, form.Password))

{

FormsService.SignIn(form.UserName, form.RememberMe);

if (Url.IsLocalUrl(returnUrl))

{

return Redirect(returnUrl);

}

return RedirectToAction("Index", "Home");

}

ModelState.AddModelError("", "Имя пользователя или пароль не верны!");

}

 

// If we got this far, something failed, redisplay form

return View(form);

}

Page 36: как создать по настоящему гибкое приложение

Просто redirect![HttpPost]

public ActionResult LogOn(LogOnModel form, string returnUrl)

{

if (ModelState.IsValid)

{

if (MembershipService.ValidateUser(form.UserName, form.Password))

{

FormsService.SignIn(form.UserName, form.RememberMe);

if (Url.IsLocalUrl(returnUrl))

{

return Redirect(returnUrl);

}

return RedirectToAction("Index", "Home");

}

ModelState.AddModelError("", "The user name or password provided is incorrect.");

}

// If we got this far, something failed, save model state and redirect

TempData[modelStateKey] = ModelState;

return RedirectToAction("LogOn");

}

Page 37: как создать по настоящему гибкое приложение

Как это работает?

protected override void OnActionExecuted(ActionExecutedContext filterContext)

{

if (TempData[modelStateKey] != null &&

ModelState.Equals(TempData[modelStateKey]) == false)

ModelState.Merge((ModelStateDictionary) TempData[modelStateKey]);

 

base.OnActionExecuted(filterContext);

}

Page 38: как создать по настоящему гибкое приложение

Что нам это даст?

• Не нужно думать какие данные и как их отобразить на форме

• Работает даже со сложными формами

• Использовать всегда!

Page 39: как создать по настоящему гибкое приложение

А дальше?[HttpPost]

public ActionResult LogOn(LogOn form, string returnUrl)

{

if (ModelState.IsValid)

{

if (MembershipService.ValidateUser(form.UserName, form.Password))

{

FormsService.SignIn(form.UserName, form.RememberMe);

if (Url.IsLocalUrl(returnUrl))

{

return Redirect(returnUrl);

}

return RedirectToAction("Index", "Home");

}

ModelState.AddModelError("", "The user name or password provided is incorrect.");

}

 

// If we got this far, something failed, redisplay form

return RedirectToAction("LogOn");

}

Page 40: как создать по настоящему гибкое приложение

CQS/CQRS

• Queries: Return a result and do not change the observable state of the system (are free of side effects).

• Commands: Change the state of a system but do not return a value.

Page 41: как создать по настоящему гибкое приложение

Используйте команды!

[HttpPost]

public ActionResult LogOn(LogOn form, string returnUrl)

{

return Handle(form,

successResult: GetRedirectToUrlOrHome(returnUrl),

failResult: RedirectToAction("LogOn"));

}

Page 42: как создать по настоящему гибкое приложение

Как это работает?public IDependencyResolver DependencyResolver { get; set; }

 

private ActionResult Handle<TCommand>(TCommand command,

ActionResult successResult, ActionResult failResult) where TCommand : ICommand

{

if (ModelState.IsValid)

{

try

{

DependencyResolver.GetService<ICommandHandler<TCommand>>().Handle(command);

return successResult;

}

catch (Exception e)

{

ModelState.AddModelError("", e);

}

}

TempData[modelStateKey] = ModelState;

return failResult;

}

Page 43: как создать по настоящему гибкое приложение

Что нам это дает?

+• Легко тестировать• Не нужно

тестировать контроллеры

• Устранение дублирования

• Повторное использование!

-

• Логика на исключениях

• Неявная связь с обработчиком

Page 44: как создать по настоящему гибкое приложение

Отображение данных

Page 45: как создать по настоящему гибкое приложение

Обработка формpublic ActionResult Index()

{

IEnumerable<DepartmentSummaryModel> departments = DepartmentRepository

.FindAll(d => d.Company.Id == CurrentCompany.Id)

.Select(d => new DepartmentSummaryModel

{

Id = d.Id,

Name = d.Name,

NumberOfEmployees = d.Employments.Count(e => !e.Employee.Archived)

})

.OrderBy(d => d.Name)

.ToList();

 

return View(new ListModel<DepartmentSummaryModel>(departments));

}

Page 46: как создать по настоящему гибкое приложение

Используйте запросы!

[HttpGet]

public ActionResult Index()

{

IEnumerable<DepartmentSummaryModel> model = Query.For<DepartmentSummaryModel>()

.With(new CurrentCompany())

.MapTo<ListModel<DepartmentSummaryModel>>();

 

return View(model);

}

Page 47: как создать по настоящему гибкое приложение

Как это работает?public IQueryBuilder Query { get; set; }

public class QueryBuilder : IQueryBuilder

{

public IQueryFor<TResult> For<TResult>()

{

return new QueryFor<TResult>(dependencyResolver);

}

 

private class QueryFor<TResult> : IQueryFor<TResult>

{

public TResult With<TCriterion>(TCriterion criterion)

where TCriterion : ICriterion

{

return dependencyResolver

.GetService<IQuery<TCriterion, TResult>>()

.Ask(criterion);

}

}

}

Page 48: как создать по настоящему гибкое приложение

AutoMapper!

public static class MapperExtensions

{

public static TResult MapTo<TResult>(this object @object)

{

return (TResult) Mapper.Map(@object,

@object.GetType(),

typeof (TResult));

}

}

Page 49: как создать по настоящему гибкое приложение

Что нам это дает?

+• Нет зависимости

от источника данных

• Легко тестировать

• Повторное использование!

-

• Неявная связь с запросом

Page 50: как создать по настоящему гибкое приложение

Итог

• Повторное использование запросов и команд

• Переносимо• Тестируемо• Просто

Page 51: как создать по настоящему гибкое приложение

Model

Page 52: как создать по настоящему гибкое приложение

Complex model binding

Page 53: как создать по настоящему гибкое приложение

Проблемкаpublic class NewProductHandler : ICommandHandler<NewProduct>

{

public void Handle(NewProduct command)

{

var product = new Product();

 

var category = session.Get<Category>(command.CategoryId);

 

product.Category = category;

product.Name = command.Name;

 

 

uow.Save(product);

}

}

Page 54: как создать по настоящему гибкое приложение

Решение:EntityModelBinder

public class EntityModelBinder : IModelBinder

{

public object BindModel(ControllerContext controllerContext,

ModelBindingContext bindingContext)

{

ValueProviderResult value = bindingContext.ValueProvider

.GetValue(bindingContext.ModelName);

 

if (value == null)

return null;

 

if (string.IsNullOrEmpty(value.AttemptedValue))

return null;

 

int entityId = int.Parse(value.AttemptedValue);

IRepository repository = GetRepository(bindingContext);

return repository.GetById(entityId);

}

}

 

Page 55: как создать по настоящему гибкое приложение

Примерpublic class NewProductHandler :

ICommandHandler<NewProduct>

{

public void Handle(NewProduct command)

{

var product = new Product();

 

product.Category = command.Category;

product.Name = command.Name;

 

uow.Save(product);

}

}

Page 56: как создать по настоящему гибкое приложение

Итог

• Инфраструктура выполнит грязную работу за вас.

• Больше сосредоточенности на задаче

Page 57: как создать по настоящему гибкое приложение

Ссылки

• Steven Sanderson • Pro ASP.NET MVC 3• http://blog.stevensanderson.com/

• Jeffrey Palermo, Jimmy Bogard• ASP.NET MVC 2 in Action• http://jeffreypalermo.com/• http://lostechies.com/jimmybogard/

• Kazi Manzur Rashid• http://kazimanzurrashid.com/

Page 58: как создать по настоящему гибкое приложение

Спасибо за внимание

Александр ЗайцевIndyCode

[email protected]/hazzik

Page 59: как создать по настоящему гибкое приложение

PS

• Возьмите себя в рамки• Удивляйтесь• Экспериментируйте