• Tutorial

Цель урока : Изучить способ авторизации через Cookie, использование стандартных атрибутов доступа к контроллеру и методу контроллера. Использование IPrincipal. Создание собственного модуля (IHttpModule) и собственного фильтра IActionFilter.

Небольшое отступление: На самом деле в asp.net mvc все учебники рекомендуют пользоваться уже придуманной системой авторизации, которая называется AspNetMembershipProvider, она была описана в статье http://habrahabr.ru/post/142711/ (сейчас доступ уже закрыт), но обьяснено это с точки зрения «нажимай и не понимай, что там внутри». При первом знакомстве с asp.net mvc меня это смутило. Далее, в этой статье http://habrahabr.ru/post/143024/ - сказано, что пользоваться этим провайдером – нельзя. И я согласен с этим. Здесь же, мы достаточно глубоко изучаем всякие хитрые asp.net mvc стандартные приемы, так что это один из основных уроков.

Кукисы
Кукисы – это часть информации, отсылаемая сервером браузеру, которую браузер возвращает обратно серверу вместе с каждым (почти каждым) запросом.

Сервер в заголовок ответа пишет:
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]
Например:

HTTP/1.1 200 OK Content-type: text/html Set-Cookie: name=value Set-Cookie: name2=value2; Expires=Wed, 09-Jun-2021 10:18:14 GMT
Браузер (если не истекло время действия кукиса) при каждом запросе:
GET /spec.html HTTP/1.1 Host: www.example.org Cookie: name=value; name2=value2 Accept: */*

Устанавливаем cookie (/Areas/Default/Controllers/HomeController.cs):
public ActionResult Index() { var cookie = new HttpCookie() { Name ="test_cookie", Value = DateTime.Now.ToString("dd.MM.yyyy"), Expires = DateTime.Now.AddMinutes(10), }; Response.SetCookie(cookie); return View(); }

В Chrome проверяем установку:

Для получения кукисов:
var cookie = Request.Cookies["test_cookie"];

Делаем точку остановки и проверяем:

Примечание: подробнее можно изучить кукисы по следующей ссылке:
http://www.nczonline.net/blog/2009/05/05/http-cookies-explained/

Авторизация
В нашем случае авторизация будет основана на использовании кукисов. Для этого изучим следующие положения:
  • FormsAuthenticationTicket – мы воспользуемся этим классом, чтобы хранить данные авторизации в зашифрованном виде
  • Нужно реализовать интерфейс IPrincipal и установить в HttpContext.User для проверки ролей и IIdentity интерфейса.
  • Для интерфейса IIdentity сделать реализацию
  • Вывести в BaseController в свойство CurrentUser значение пользователя, который сейчас залогинен.

Приступим.
Создадим интерфейс IAuthentication и его реализацию CustomAuthentication (/Global/Auth/IAuthentication.cs):

Public interface IAuthentication { ///

/// Конекст (тут мы получаем доступ к запросу и кукисам) /// HttpContext HttpContext { get; set; } User Login(string login, string password, bool isPersistent); User Login(string login); void LogOut(); IPrincipal CurrentUser { get; } }

Реализация (/Global/Auth/CustomAuthentication.cs):
public class CustomAuthentication: IAuthentication { private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); private const string cookieName = "__AUTH_COOKIE"; public HttpContext HttpContext { get; set; } public IRepository Repository { get; set; } #region IAuthentication Members public User Login(string userName, string Password, bool isPersistent) { User retUser = Repository.Login(userName, Password); if (retUser != null) { CreateCookie(userName, isPersistent); } return retUser; } public User Login(string userName) { User retUser = Repository.Users.FirstOrDefault(p => string.Compare(p.Email, userName, true) == 0); if (retUser != null) { CreateCookie(userName); } return retUser; } private void CreateCookie(string userName, bool isPersistent = false) { var ticket = new FormsAuthenticationTicket(1, userName, DateTime.Now, DateTime.Now.Add(FormsAuthentication.Timeout), isPersistent, string.Empty, FormsAuthentication.FormsCookiePath); // Encrypt the ticket. var encTicket = FormsAuthentication.Encrypt(ticket); // Create the cookie. var AuthCookie = new HttpCookie(cookieName) { Value = encTicket, Expires = DateTime.Now.Add(FormsAuthentication.Timeout) }; HttpContext.Response.Cookies.Set(AuthCookie); } public void LogOut() { var httpCookie = HttpContext.Response.Cookies; if (httpCookie != null) { httpCookie.Value = string.Empty; } } private IPrincipal _currentUser; public IPrincipal CurrentUser { get { if (_currentUser == null) { try { HttpCookie authCookie = HttpContext.Request.Cookies.Get(cookieName); if (authCookie != null && !string.IsNullOrEmpty(authCookie.Value)) { var ticket = FormsAuthentication.Decrypt(authCookie.Value); _currentUser = new UserProvider(ticket.Name, Repository); } else { _currentUser = new UserProvider(null, null); } } catch (Exception ex) { logger.Error("Failed authentication: " + ex.Message); _currentUser = new UserProvider(null, null); } } return _currentUser; } } #endregion }

Суть сводится к следующему, мы, при инициализации запроса, получаем доступ к HttpContext.Request.Cookies и инициализируем UserProvider:

Var ticket = FormsAuthentication.Decrypt(authCookie.Value); _currentUser = new UserProvider(ticket.Name, Repository);

Для авторизации в IRepository добавлен новый метод IRepository.Login. Реализация в SqlRepository:
public User Login(string email, string password) { return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0 && p.Password == password); }

UserProvider, собственно, реализует интерфейс IPrincipal (в котором есть проверка ролей и доступ к IIdentity).
Рассмотрим класс UserProvider (/Global/Auth/UserProvider.cs):

Public class UserProvider: IPrincipal { private UserIndentity userIdentity { get; set; } #region IPrincipal Members public IIdentity Identity { get { return userIdentity; } } public bool IsInRole(string role) { if (userIdentity.User == null) { return false; } return userIdentity.User.InRoles(role); } #endregion public UserProvider(string name, IRepository repository) { userIdentity = new UserIndentity(); userIdentity.Init(name, repository); } public override string ToString() { return userIdentity.Name; }

Наш UserProvider знает про то, что его IIdentity классом есть UserIdentity , а поэтому знает про класс User , внутри которого мы реализуем метод InRoles(role) :

Public bool InRoles(string roles) { if (string.IsNullOrWhiteSpace(roles)) { return false; } var rolesArray = roles.Split(new { "," }, StringSplitOptions.RemoveEmptyEntries); foreach (var role in rolesArray) { var hasRole = UserRoles.Any(p => string.Compare(p.Role.Code, role, true) == 0); if (hasRole) { return true; } } return false; }

В метод InRoles мы ожидаем, что придет запрос о ролях, которые допущены к ресурсу, разделенные запятой. Т.е., например, “admin,moderator,editor”, если хотя бы одна из ролей есть у нашего User – то возвращаем зачение «истина» (доступ есть). Сравниваем по полю Role.Code, а не Role.Name.
Рассмотрим класс UserIdentity (/Global/Auth/UserIdentity.cs):
public class UserIndentity: IIdentity { public User User { get; set; } public string AuthenticationType { get { return typeof(User).ToString(); } } public bool IsAuthenticated { get { return User != null; } } public string Name { get { if (User != null) { return User.Email; } //иначе аноним return "anonym"; } } public void Init(string email, IRepository repository) { if (!string.IsNullOrEmpty(email)) { User = repository.GetUser(email); } } }
В IRepository добавляем новый метод GetUser(email) . Реализация для SqlRepository.GetUser() (LessonProject.Model:/SqlRepository/User.cs):

Public User GetUser(string email) { return Db.Users.FirstOrDefault(p => string.Compare(p.Email, email, true) == 0); }

Почти все готово. Выведем CurrentUser в BaseController:
public IAuthentication Auth { get; set; } public User CurrentUser { get { return ((UserIndentity)Auth.CurrentUser.Identity).User; } }

Да, это не очень правильно, так как здесь присутствует сильное связывание. Поэтому сделаем так, введем еще один интерфейс IUserProvider , из которого мы будем требовать вернуть нам авторизованного User:
public interface IUserProvider { User User { get; set; } } … public class UserIndentity: IIdentity, IUserProvider { ///

/// Текщий пользователь /// public User User { get; set; } … public IAuthentication Auth { get; set; } public User CurrentUser { get { return ((IUserProvider)Auth.CurrentUser.Identity).User; } }
А теперь попробуем инициализировать это всё.
Вначале добавим наш IAuthentication + CustomAuthentication в регистрацию к Ninject (/App_Start/NinjectWebCommon.cs):

Kernel.Bind().To().InRequestScope();

Потом создадим модуль, который будет на событие AuthenticateRequest совершать действие авторизации:
public class AuthHttpModule: IHttpModule { public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(this.Authenticate); } private void Authenticate(Object source, EventArgs e) { HttpApplication app = (HttpApplication)source; HttpContext context = app.Context; var auth = DependencyResolver.Current.GetService(); auth.HttpContext = context; context.User = auth.CurrentUser; } public void Dispose() { } }

Вся соль в строках: auth.HttpContext = context и context.User = auth.CurrentUser . Как только наш модуль авторизации узнает о контексте и содержащихся в нем кукисах, ту же моментально получает доступ к имени, по нему он в репозитории получает данныепользователя и возвращает в BaseController. Но не сразу всё, а по требованию.
Подключаем модуль в Web.config:

План таков:

  • Наверху показываем, авторизован пользователь или нет. Если авторизован, то его email и ссылка на выход, если нет, то ссылки на вход и регистрацию
  • Создаем форму для входа
  • Если пользователь правильно ввел данные – то авторизуем его и отправляем на главную страницу
  • Если пользователь выходит – то убиваем его авторизацию

Поехали. Добавляем Html.Action(“UserLogin”, “Home”) – это partial view (т.е. кусок кода, который не имеет Layout) – т.е. выводится где прописан, а не в RenderBody().
_Layout.cshtml (/Areas/Default/Views/Shared/_Layout.cshtml):

@RenderBody() HomeController.cs: public ActionResult UserLogin() { return View(CurrentUser); }

UserLogin.cshtml (/Areas/Default/Views/Home/UserLogin.cshtml):

@model LessonProject.Model.User @if (Model != null) {

  • @Model.Email
  • @Html.ActionLink("Выход", "Logout", "Login")
  • } else {
  • @Html.ActionLink("Вход", "Index", "Login")
  • @Html.ActionLink("Регистрация", "Register", "User")
  • }

    Контроллер входа выхода LoginController (/Areas/Default/Controllers/LoginController.cs):

    Public class LoginController: DefaultController { public ActionResult Index() { return View(new LoginView()); } public ActionResult Index(LoginView loginView) { if (ModelState.IsValid) { var user = Auth.Login(loginView.Email, loginView.Password, loginView.IsPersistent); if (user != null) { return RedirectToAction("Index", "Home"); } ModelState["Password"].Errors.Add("Пароли не совпадают"); } return View(loginView); } public ActionResult Logout() { Auth.LogOut(); return RedirectToAction("Index", "Home"); } }

    LoginView.cs (/Models/ViewModels/LoginView.cs):
    public class LoginView { public string Email { get; set; } public string Password { get; set; } public bool IsPersistent { get; set; } }

    Страница для входа Index.cshtml (/Areas/Default/Views/Index.cshtml):

    @model LessonProject.Models.ViewModels.LoginView @{ ViewBag.Title = "Вход"; Layout = "~/Areas/Default/Views/Shared/_Layout.cshtml"; }

    Вход

    @using (Html.BeginForm("Index", "Login", FormMethod.Post, new { @class = "form-horizontal" })) {
    Вход
    @Html.TextBox("Email", Model.Email, new { @class = "input-xlarge" })

    Введите Email

    @Html.ValidationMessage("Email")
    @Html.Password("Password", Model.Password, new { @class = "input-xlarge" }) @Html.ValidationMessage("Password")
    }

    Запускаем и проверяем:

    Все исходники находятся по адресу

    The ASP.NET login controls provide a robust login solution for ASP.NET Web applications without requiring programming. By default, login controls integrate with ASP.NET membership and forms authentication to help automate user authentication for a Web site. For information about how to use ASP.NET membership with forms authentication, see Introduction to Membership .

    By default, the ASP.NET login controls work in plain text over HTTP. If you are concerned about security, use HTTPS with SSL encryption. For more information about SSL, see Configuring SSL on a Web Server or a Web Site in the IIS documentation.

    Login controls might not function correctly if the of the ASP.NET Web page is changed from POST (the default) to GET.

    This topic describes each ASP.NET login control and provides links to the control"s reference documentation.

    The Login Control

    The LoginName Control

    Password information sent in an e-mail message is sent as clear text.

    Теперь перейдем к созданию проекта. Создавать мы будет базовое приложение.

    Запустите Visual Studio, в меню выберите "File"->"New"->"Project..."

    В открытом окне выбираем из списка Installed Templates "Web" (1). Дальше выбираем "ASP.NET MVC 3 Web Application" (2). Пишем название проекта и путь к каталогу, где оно будет находиться (3)

    Жмем ОК. В следующем окне оставляем выбранным "Internet Application" и жмем ОК

    У нас создаться базовый проект asp.net mvc. После нажатия клавиши F5 (Start Debugging), мы увидим наш сайт (запуститься виртуальный IIS и на панели Пуск появится значок, которые отображает его работу). Каждый сайт запущенный с помощью Visaul Studio работает на каком-то порту (например, localhost:29663) по этому не волнуйтесь, если цифры у Вас будут отличатся от моих.

    Что же нам насоздавала студия, и как работает asp.net mvc приложение.

    Для начала нужно понять простую логику работы всех сайтов и то чем они отличаются от дескоп/windows приложений.

    Когда мы открываем какой-то сайт (например www..aspx), то отправляется запрос на сервер (это равносильно событию нажатия какой-то кнопки в дескоп приложений), и говорим серверу отдать нам какую-то информацию (в нашем примере отдать инфу о "категории 3" сайта сайт).

    Сервер, в свою очередь, либо знает эту команду (этот url) и отдает нам нужную инфу, либо возвращает ошибку (например страничку 404). После того, как сервер команду выполнил, он про нас запрос забывает.

    Когда мы на сайте жмем какую-то кнопку, то в целом это равносильно открытию новой странички сайта новым пользователем. Сервер совершено не помнит, что он нам перед этим отдавал и кто мы. Т.е. сервер работает по принципу: отдал-забыл.

    При дальнейшем развитии интернета начали придумывать всякие хитрости, что бы сервер как-то запоминал с кем он работает и что бы нам было легче напоминать серверу, какую инфу мы до этого получали (это я говорю про Cookie, Session и прочие вещи).

    На заметку. Технология asp.net forms стремилась к такому принципу: легко напоминать серверу, какие действия применялись до этого клиентом и какую информацию клиент уже получил. Так появилось понятия ViewState - это сохраненная информация состоянии странички. Но эта технология была не удачной

    Перейдем теперь к тому, как работает asp.net mvc технология. (Сейчас я опишу совсем базовые вещи, многие шаги упущены для простоты).

    Для начала откройте окно "Solutin Explorer" в VS (если его нет, то его можно найти в меню "View"-> "Solutin Explorer"). В нем мы увидим все файлы проекта. (это окно удобно разместить справа, так как мы постоянно будем ним пользоваться).

    Нас сейчас интересует папка Controllers - в ней мы создаем файлики, которые будут обрабатывать наши действия (обрабатывать наши url). Так же нас интересует файл Global.asax, в нем мы будет задавать какой файл из папки Controllers какой url будет обрабатывать. Откроем файл Global.asax и найдем вот такой код:

    public static void RegisterRoutes(RouteCollection routes)
    {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    Routes.MapRoute(
    "Default", // Route name
    "{controller}/{action}/{id}", // URL with parameters
    new { controller = "Home", action = "Index", id = UrlParameter.Optional }
    );
    }

    Это правило привязки url к Controllers. Удалим строку с "routes.MapRoute..." по ".....UrlParameter.Optional });" Вместо нее мы напишем свои три правила:

    routes.MapRoute(
    "Root", // название правила
    "", // какой URL
    new { controller = "Home", action = "Index" } //какой файл controller
    );

    routes.MapRoute(
    "Home-About", // название правила
    "About.aspx", // какой URL
    new { controller = "Home", action = "About" } //какой файл controller
    );

    routes.MapRoute(
    "Account-LogOn", // название правила
    "Account/LogOn.aspx", // какой URL
    new { controller = "Account", action = "LogOn" } //какой файл controller
    );

    Каждое правило имеет свое название, которое не должно повторятся ("Root" "Home-About" "Account-LogOn"). Так же каждое правило должно указывать URL и controller, который будет это действие обрабатывать.

    Сейчас в моем сайте есть три странички/три правила:

    - Account/LogOn.aspx - ее будет обрабатывать контроллер AccountController и метод этого контроллера LogOn

    - About.aspx - эту страничку будет обрабатывать контроллер HomeController и метод этого контроллера About

    - корневая стр - ее будет обрабатывать контроллер HomeController и метод этого контроллера Index

    Теперь откроем файл HomeController (для открытия файлов пользуйтесь "Solutin Explorer") в этом файле вы увидите класс HomeController, которые наследуется от класса Controller и два метода этого класса Index и About. Эти методы и будут обрабатывать наши url.

    Основная логика работы приложения пишется в этих методах (например считывания или внесение информации в базу данных). Давайте мы в методе Index считаем серверное время, а вот в методе About посчитаем сколько будет 345 умножить на 23.

    Смотрим на код:

    public class HomeController: Controller
    {
    public ActionResult Index()
    {
    var date = DateTime.Now;

    Return View();
    }

    Public ActionResult About()
    {
    var result = 345 * 23;
    return View();
    }
    }

    Теперь нам нужно результат свои действий отобразить пользователю. В web приложениях это делается с помощью html страничек. Эти странички, как правило, находятся в папке View (пользуйтесь "Solutin Explorer"). В папке View для каждого контролера создается своя папка с названием этого контроллера (так легче ориентироваться). Создадим несколько страничек.

    Правой клавишей нажимаем на папке "Home" в контекстном меню выбираем "Add"->"View..."

    Перед нами откроется окно, в котором мы укажем имя нашей страничке "ViewDateTime". Так же нужно убрать галочку "Use layout or master page", о ней мы поговорим позже. Итак, окно должно иметь вид:

    Жмем Add. Студия создаст нам файл ViewDateTime.cshtml и создаст в нем базовую структуру html. Таким же образом добавим еще файл ViewResult

    Вернемся теперь к нашим методам контролера. Изменим строку "return View();" в методе Index на "return View("~/Views/Home/ViewDateTime.cshtml", date);" а в методе About на "return View("~/Views/Home/ViewResult.cshtml", result);"

    Это означает, что результат своих действий мы будем отображать на представлениях (View) ViewDateTime и ViewResult соответственно, так же мы в эти представления передали date и result.

    Что бы упростить жизнь программистам и не писать всегда длинный путь ("~/Views/Home/.....cshtml) к файлам отображения принято использовать такие правила:

    • return View(); // означает, что файл отображения находиться в папке с именем контролера, а файл имеет тоже название что и метод. Пример, для метода Index контролера HomeController представление будет находиться "~/Views/Home/Index.cshtml"
    • return View("MyView"); // означает, что файл отображения находиться в папке с именем контролера, а файл имеет название MyView. Пример, для метода Index контролера HomeController представление будет находиться ~/Views/Home/MyView.cshtml

    Исходя из выше сказанного еще раз изменим строки кода: метод Index будет возвращать return View("ViewDateTime", date); а метод About вернет return View("ViewResult", result);

    Теперь обратите внимания, что кроме указания View-файла мы еще передаем данные для отображения (date и result). Сейчас нам нужно настроить их корректное отображение.

    Откроем файл ViewDateTime.cshtml и вначале добавим код "@model DateTime". Он означает, что файл ViewDateTime будет отображать переменную типа DateTime. Если бы мы не задавали, какой тип переменой будет отображать данное представление, то код был бы рабочим, но тогда на этой страничке у нас не было подсказки. Между тегами добавим код @Model.ToShortDateString().

    Используя код @Model - мы обращаемся к объекту, который передали в View. Полный код файла ViewDateTime.cshtml

    @model DateTime
    @{
    Layout = null;
    }



    ViewDateTime



    @Model.ToShortDateString()


    Теперь откроем файл ViewResult.cshtml. В начале файла добавьте" @model int "

    Между тегами напишите код "345 * 23 = @Model" Полный текст выглядит так:

    @model int
    @{
    Layout = null;
    }



    ViewResult



    345 * 23 = @Model



    Теперь запустите свое приложение (клавиша F5). Вы увидите текущую дату

    Если перейдете на url http://localhost:29663/About.aspx Вы увидите результат операции 345 * 23

    Итак, что Вы должны освоит: в asp.net mvc приложении есть папка Controllers - в которой мы пишем все действия/url адреса сайта; так же есть папка Views - в которой мы пишем представление наших действий (html странички); так же есть файл Global.asax - который связывает url сайта из методами контролера.

    Недавно познакомился с инструментом для валидации данных Validation Application Block (VAB) из Enterprise Library, но вот беда: на российских сайтах с трудом можно найти информацию о том, как им пользоваться.

    Напомню, что уже c 2005 года существует проект под название Enterprise Library от Microsoft Patterns & Practics, который предлагает множество готовых решений для часто реализуемых задач (кэширование, логирования, валидация, безопасность и т.д.), в том числе и Unity Framework, который получил наибольшее распространение среди разработчиков.

    А столкнулся я с ним, желая подобрать для Web-проекта наиболее гибкое решение. В итоге остановил свой выбор на VAB, и остался довольный результатом.

    Приведу простейший пример использования для ASP.NET .
    Наш сценарий будет таков: нужно сделать валидацию при регистрации пользователя:
    а) длина логина от 4 до 8 символов;
    б) длина пароля от 5 до 10 символов:
    в) подтверждение пароля = паролю;
    г) логин не должен повторяться с уже существующими;

    0. Создадим Web-приложение или Web-сайт ASP.NET, и создадим класс User.

    public class User

    public Hash Id { get ; set ; }

    public string Login { get ; set ; }

    public string Password { get ; set ; }

    public string ConfirmPassword { get ; set ; }


    1. Теперь нужно скачать Enterprise Library отсюда , установить, и подключить следующие файлы в проект.
    Первая сборка нужна, для реализации базовых возможностей EnterpriseLibrary, они нам понядобятся при написании собственного валидатора. Вторая сборка - без комментариев, и третья - содержит контрол-валидатор для asp.net приложений.

    Все необходимое для работы с VAB у нас теперь есть, и теперь можно переходить к следующему шагу.

    2. Нужно реализовать 4 правила валидации с "А" по "Г"
    VAB уже содержит стандартные валидаторы, которыми можно релализовать правила с "А" по "В". А вот проверку того, что данный логин уже используется, нужно написать самим. Создаем следующий класс-валидатор.

    public class LoginExistsValidator : Validator

    public LoginExistsValidator(NameValueCollection attributes)

    : base (String .Empty, String .Empty)

    protected override string DefaultMessageTemplate

    get { return "Логин {0} уже используется" ; }

    protected override void DoValidate(string objectToValidate,

    object currentTarget,

    string key,

    ValidationResults validationResults)

    if (objectToValidate.ToLower() == "ivan" )

    validationResults.AddResult(new ValidationResult (base .GetMessage(objectToValidate, key),

    objectToValidate, key, this .Tag, this ));

    В validationResults записывается информация о том, что данные не валидны. Приятный момент: в свойстве "DefaultMessageTemplate" используется форматирование, и в {0} попадет имя логина, который не прошел валидацию.

    Ну вот теперь у нас есть все необходимые валидаторы, можно наложить эти правила на класс.

    3. Запишем правила валидации в терминологии VAB, как показано ниже. И поместим их в web.config.

    < validation >

    < type name = "VALExampleWeb.User "assemblyName = "VALExampleWeb, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null ">

    < ruleset name = "register ">

    < properties >

    < property name = "Login ">

    < validator type = "VALExampleWeb.LoginExistsValidator, VALExampleWeb, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null "

    messageTemplate = ""name = "LoginExistsValidator " />

    < validator type = ""

    upperBound = "8 "lowerBound = "4 "lowerBoundType = "Inclusive "messageTemplate = "Длинна логина должна быть от 4 до 8 символов "

    tag = ""name = "String Length Validator " />

    < property name = "Password ">

    < validator type = "Microsoft.Practices.EnterpriseLibrary.Validation.Validators.StringLengthValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 "

    upperBound = "10 "lowerBound = "5 "lowerBoundType = "Inclusive "messageTemplate = "Длинна пароля должна быть от 5 до 10 "

    name = "String Length Validator " />

    < property name = "ConfirmPassword ">

    < validator type = "Microsoft.Practices.EnterpriseLibrary.Validation.Validators.PropertyComparisonValidator, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 "

    propertyToCompare = "Password "messageTemplate = "Подтверждение пароля должно совпадать с паролем "

    name = "Property Comparison Validator " />


    С виду может показаться слишком сложно, но это не так, структура здесь простая.

    < validation > - содержит все правила валидации
    < type > - содержит правила валидации для указанного типа
    < ruleset name = " register " > - нужен, чтобы разделять группы правил валидации для одного типа
    < properties > - содержит правила валидации для свойств объекта
    < property name = " Login " > - содержит правила валидации для конкретного свойства
    < validator > - объект валидации

    Да, не забудем добавить в web.config, в блок < configSections >, следующий код, который будет обрабатывать секцию < validation >.

    < section name ="validation" type ="Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.ValidationSettings, Microsoft.Practices.EnterpriseLibrary.Validation, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission ="true" / >

    4. Остался последний шаг: включить валидацию на форму ASP.NET

    Зарегистрируем на форме валидатор "PropertyProxyValidator" от Enterprise Library, который будет использовать выше написанные правила валидации.

    <%@ Register Assembly="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet"
    Namespace="Microsoft.Practices.EnterpriseLibrary.Validation.Integration.AspNet" TagPrefix="vab" %>

    И поместим на форме следущий код, с соответствующими валидаторами.






















    PropertyName="Login" RulesetName="register" SourceTypeName="VALExampleWeb.User" />





    PropertyName="Password" RulesetName="register" SourceTypeName="VALExampleWeb.User" />





    PropertyName="ConfirmPassword" RulesetName="register" SourceTypeName="VALExampleWeb.User" />


    Можно заменить, что в валидаторах Enterprise Library указывается тип, свойство и правило, которое нужно использовать для валидации. Таким образом, добавив еще один валидатор в конфигурацию, vab:PropertyProxyValidator автоматически будет его использовать.

    5. Вот и все!
    В результате, нажав на кнопку "Зарегистрировать" мы получим следующую картину.




    Еще немного о VAB


    Некоторые возможности:
    1. Есть удобный визуальный редактор правил валидации, который интегрируется в Visual Studio (в данном примере я использовал именно его );
    2. Есть валидаторы также для Windows Forms и WCF;
    3. Для Windows Forms используется специальный механизм, который отслеживает изменения правил валидации в файле, и подгружает новые;
    4. Правила валидации можно накладывать атрибутами или создавать динамически;
    5. Удобный механизм для локализации сообщений о результатах валидации.

    Небольшие, очевидные минусы:
    1. При первой валидации, уходит время на загрузку правил валидации и их кэширование, для последующего использования.
    2. VAB - это сложный механизм, который требует грамотного обращения.

    Например, ошибка допущенная в файле правил валидации, можно будет заменить только на стадии валидации данных. Для решения этой проблемы стоит применять модульные unit-тесты.

    В заключении
    Validation Application Block - это большой зверь или пушка, из которой не стоит палить по воробьям:-)