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.

  • 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")
    }

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

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

    Элемент управления Login

    Элемент управления Login упрощает создание страницы входа для аутентификации с помощью форм в сочетании с Membership API. Он предоставляет готовый к применению пользовательский интерфейс, запрашивающий имя и пароль пользователя и предлагающий кнопку для входа пользователя. "За кулисами" он инкапсулирует функциональность, которая была описана в предыдущей статье: проверку удостоверений пользователей через Membership API и инкапсуляцию базовой функциональности аутентификации с помощью форм, такой как перенаправление к изначально запрошенной странице в защищенной области приложения после успешного входа.

    Это значит, что Login инкапсулирует такие вещи, как Membership.ValidateUser() или FormsAuthentication.RedirectFromLoginPage(), поэтому писать этот код самостоятельно не придется. На рисунке ниже показан элемент управления Login в действии:

    Всякий раз, когда пользователь щелкает на кнопке Log In (Войти), элемент управления автоматически проверяет имя и пароль, применяя для этого функцию Membership.ValidateUser(), а затем вызывает FormsAuthenication.RedirectFromLoginPage(), если проверка прошла успешно. Все опции элемента управления Login влияют на ввод, доставляемый им в эти метода. Например, если отметить флажок Remember me next time (Запомнить учетные данные), он передаст значение true в параметре createPersistentCookie метода RedirectFromLoginPage(). Поэтому FormsAuthenticationModule создает постоянный cookie-набор.

    "За кулисами" Login представляет собой составной элемент управления ASP.NET. Он полностью расширяем - в том смысле, что позволяет переопределять любые стили компоновки и свойства, а также перехватывать генерируемые события для переопределения его поведения по умолчанию. Если оставить элемент управления без изменений, как он есть, и не перехватывать никаких событий, то он автоматически будет использовать поставщик членства, сконфигурированный для приложения.

    Простейшая форма элемента управления Login на странице выглядит следующим образом:

    Для изменения внешнего вида элемента управления Login предназначено несколько свойств. Можно применять разные установки стилей, как показано ниже:

    Кроме того, для настройки внешнего вида Login можно использовать классы CSS. Каждое свойство стиля, поддерживаемое элементом управления Login, включает свойство CssClass. Как и в случае любого другого элемента управления ASP.NET, это свойство позволяет указать имя класса CSS, который был добавлен ранее к веб-сайту. Предположим, что в проект добавлена следующая таблица стилей CSS с именем файла MyStyles.css:

    MyLoginTextBoxStyle { cursor: pointer; background-color: yellow; text-align: center; padding:6px; border: dotted black; font-family: Verdana; vertical-align: middle; } .Login { display:inline-block; } .Title { padding: 6px; }

    Этот файл стиля можно включить в страницу входа, чтобы иметь возможность использовать стиль для элемента Login:

    В таблице ниже перечислены стили, поддерживаемые элементом управления Login. Каждый стиль работает одним и тем же способом. Свойства шрифта и цвета можно устанавливать непосредственно или применять свойство CssClass для указания нужного класса CSS:

    Стили, поддерживаемые элементом управления Login
    Стиль Описание
    CheckBoxStyle

    Определяет свойства стиля для флажка Remember me next time (Запомнить учетные данные)

    FailureStyle

    Определяет стиль для текста, который отображается а случае неудачного входа

    HyperLinkStyle

    Элемент управления Login позволяет определить несколько типов гиперссылок, например, на страницу начальной регистрации. Этот стиль задает внешний вид таких гиперссылок

    InstructionTextStyle

    Элемент управления Login позволяет указать текст справки, отображаемый непосредственно в нем самом. Этот стиль задает внешний вид этого текста

    LabelStyle

    Определяет стиль для меток User Name (Имя пользователя) и Password (Пароль)

    LoginButtonStyle

    Определяет стиль кнопки входа

    TextBoxStyle

    Определяет стиль для текстовых полей User Name (Имя пользователя) и Password (Пароль)

    TitleTextStyle

    Определяет стиль текста заголовка для элемента управления Login

    ValidatorTextStyle

    Определяет стили для элементов управления, используемых для проверки имени и пароля пользователя

    Пользовательский интерфейс элемента Login настраивается не только через эти стили; другие дополнительные свойства предназначены для определенных частей содержимого элемента управления, таких как кнопка Log In, которые также позволяют настроить графический интерфейс.

    Например, можно выбрать текст, отображаемый на кнопке входа, либо вообще отображать вместо этой кнопки (как установлено по умолчанию) гиперссылку. Более того, к элементу управления Login можно добавить несколько гиперссылок, таких как ссылка на страницу справки или на страницу регистрации. Обе страницы должны быть открыты для анонимного доступа, поскольку справка должна предлагаться также анонимным пользователям (вспомните, что если кто-то видит элемент управления Login, то он - потенциально анонимный пользователь). Чтобы включить дополнительные ссылки в Login, модифицируйте ранее показанное определение следующим образом:

    ...

    Этот код приводит к отображению двух дополнительных ссылок - на страницу справки и на страницу первоначальной регистрации, а также добавляет текст краткой инструкции под заголовком элемента Login:

    Стили, описанные ранее, применимы и к этим свойствам. В таблице ниже описаны важные свойства для настройки элемента управления Login:

    Важные свойства для настройки элемента управления Login
    Свойство Описание
    Текст сообщений
    TitleText

    Текст, отображаемый в заголовке элемента управления

    InstructionText

    Это свойство уже использовалось в предыдущем фрагменте кода. Содержит текст, отображаемый ниже заголовка элемента управления

    FailureText

    Текст, отображаемый элементом управления Login, если попытка входа не удалась

    UserNameLabelText

    Текст, отображаемый в виде метки перед текстовым полем имени пользователя

    PasswordLabelText

    Текст, отображаемый в виде метки перед текстовым полем пароля пользователя

    UserName

    Начальное значение, заполняющее текстовое поле имени пользователя

    UsernameRequiredErrorMessage

    Сообщение об ошибке, отображаемое, если пользователь не ввел имя

    PasswordRequiredErrorMessage

    Сообщение об ошибке, отображаемое, если пользователь не ввел пароль

    Кнопка входа
    LoginButtonText

    Текст, отображаемый на кнопке входа

    LoginButtonType
    LoginButtonImageUrl

    Если кнопка входа представлена как графическое изображение, необходимо указать URL, где находится это изображение

    Страница входа
    DestinationPageUrl

    Если попытка входа была успешной, элемент управления Login перенаправляет пользователя на эту страницу. По умолчанию это свойство пусто. При пустом значении используется инфраструктура аутентификации с помощью форм для перенаправления либо на исходную запрошенную страницу, либо на defaultUrl, сконфигурированный в web.config для аутентификации с помощью форм

    FailureAction

    Определяет действие, которое элемент управления выполняет после неудачной попытки входа. Два допустимых варианта - Refresh и RedirectToLoginPage. Первое значение приводит к обновлению только текущей страницы, а второе - к перенаправлению на сконфигурированную страницу входа. Второй вариант полезен, если элемент управления Login используется где-то в другом месте, а не на странице входа

    VisibleWhenLoggedIn

    Если установлено в false, то элемент управления автоматически скрывает себя, если пользователь уже вошел. Если установлено в true (по умолчанию), то элемент Login отображается, даже если пользователь совершил вход

    Настройка метки "Запомнить меня"
    DisplayRememberMe

    Позволяет показывать или скрывать флажок Remember me next time (Запомнить меня). По умолчанию это свойство установлено в true

    RememberMeSet

    Определяет значение по умолчанию флажка Remember me next time. По умолчанию это свойство установлено в false, т.е. флажок не отмечен

    Страница регистрации
    CreateUserUrl

    Определяет гиперссылку на страницу веб-сайта, которая позволяет создавать (регистрировать) пользователя. Таким образом, это обычно используется для открытия пользователю доступа к странице первичной регистрации. Обычно при этом отображается элемент управления CreateUserWizard

    CreateUserText
    CreateUserIconUrl

    URL-адрес графического изображения, выводимого вместе с текстом гиперссылки CreateUserUrl

    Страница помощи
    HelpPageUrl

    URL-адрес для перенаправления пользователя на страницу справки

    HelpPageText
    HelpPageIconUrl

    URL-адрес значка, отображаемого вместе с текстом гиперссылки HelpPageUrl

    Страница восстановления пароля
    PasswordRecoveryUrl

    URL-адрес для перенаправления пользователя на страницу восстановления пароля. Эта страница применяется, когда пользователь забыл пароль. Обычно она отображает элемент управления PasswordRecovery

    PasswordRecoveryText
    PasswordRecoveryIconUrl

    URL-адрес значка, отображаемого вместе с текстом гиперссылки PasswordRecoveryUrl

    Шаблоны и элемент управления Login

    Как видите, благодаря всем этим свойствам элемент управления Login настраивается очень гибко. Но как вы, скорее всего, обратили внимание, определить какое-нибудь выражение для проверки достоверности ввода невозможно. Конечно, можно реализовать проверку достоверности на стороне сервера внутри процедур событий, предлагаемых элементом управления Login. Когда требуется добавить какие-то элементы к составному элементу управления Login, это не получится сделать через представленные выше свойства. Например, что если понадобится второе текстовое поле для надежной аутентификации со вторым паролем или пользовательским ключом доступа, как это делается на некоторых правительственных сайтах?

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

    Войти в систему

    Имя пользователя:
    Пароль:


    Если посмотреть на приведенный код, то возникает один вопрос: если при настройке шаблона приходится писать настолько много кода пользовательского интерфейса (или проектировать его в визуальном конструкторе), так почему бы ни написать собственную страницу входа, не применяя элемента управления Login?

    Это правильный вопрос. Однако, как объяснялось ранее, интерфейсная часть - это только одна часть элемента Login. Например, на случай, когда пользователь щелкает на кнопке входа, элемент управления Login уже имеет весь необходимый код для автоматической проверки пользователя по хранилищу членства и перенаправляет его на исходную запрошенную им страницу через инфраструктуру аутентификации с помощью форм. Так что вы определенно избавлены от необходимости написания этого кода.

    С правильными элементами управления и корректными значениями идентификаторов для этих элементов управления не понадобится писать код обработки событий. Код работает обычным образом, за исключением того, что вы определяете набор элементов управления и их компоновку. В действительности элемент управления Login требует как минимум двух текстовых полей с идентификаторами UserName и Password. Если эти два текстовых поля отсутствуют (или имеют другие значения идентификатора), то Login сгенерирует исключение. Все остальные элементы управления не обязательны, но если вы указываете соответствующее значение идентификатора (такое как Login для кнопки входа), то Login автоматически обработает их события, и будет вести себя так, как тогда, когда применяется компоновка по умолчанию.

    В таблице ниже перечислены специальные значения идентификаторов, требуемые для них типы элементов и признак обязательности:

    Элементом управления с идентификатором Login может быть любой, поддерживающий пузырьковое распространение событий и свойство CommandName. Важно установить свойство CommandName этого элемента в Login, т.к. в противном случае элемент управления Login не будет распознавать его в процессе обработки событий. Если не добавить элемент управления со свойством CommandName, установленным в Login, придется обрабатывать события самостоятельно и писать соответствующий код для проверки имени пользователя и пароля, а также перенаправления на исходную запрошенную страницу.

    Можно также добавлять элементы управления с другими идентификаторами, которые вообще не имеют отношения к Login. В показанном выше коде применялись элементы RequiredFieldValidator и RegularExpressionValidator для проверки полей имени пользователя и пароля.

    При использовании LayoutTemplate многие свойства, изначально присущие элементу управления, становятся недоступными. Когда применяется шаблон, остаются доступными только следующие свойства:

      DestinationPageUrl

      VisibleWhenLoggedIn

    • MembershipProvider

    Все свойства стиля и несколько свойств настройки текстового содержимого элементов по умолчанию больше не доступны в редакторе свойств Visual Studio, поскольку их можно добавить вручную как отдельные элементы управления или статический текст к шаблону элемента Login. Если вы будете добавлять их к элементу Login в режиме шаблона, они просто будут игнорироваться, потому что шаблон переопределяет интерфейс по умолчанию элемента Login, который пользуется этими свойствами.

    Программирование элемента управления Login

    Элемент управления Login поддерживает несколько событий и свойств, которые можно применять для настройки его поведения. Они предоставляют полный контроль над тонкой настройкой элемента управления Login (наряду с другими средствами настройки, такими как шаблоны и свойства стиля). Элемент управления Login поддерживает события, перечисленные в таблице ниже:

    События элемента управления Login
    Событие Описание
    LoggingIn

    Инициируется непосредственно перед аутентификацией пользователя элементом управления

    LoggedIn

    Инициируется после того, как пользователь аутентифицирован элементом управления

    LoginError

    Инициируется при неудачной попытке входа пользователя по какой-либо причине (например, из-за неправильного пароля или имени пользователя)

    Authenticate

    Инициируется для аутентификации пользователя. Если вы обрабатываете это событие, то должны аутентифицировать пользователя самостоятельно, и элемент управления Login будет полностью полагаться на ваш код аутентификации

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

    Protected void Page_Load(object sender, EventArgs e) { if (!this.IsPostBack) ViewState["LoginErrors"] = 0; } protected void Login1_LoginError(object sender, EventArgs e) { // Если состояние LoginErrors не существует, создать его if (ViewState["LoginErrors"] == null) ViewState["LoginErrors"] = 0; // Увеличить счетчик неудачных попыток входа int ErrorCount = (int)ViewState["LoginErrors"] + 1; ViewState["LoginErrors"] = ErrorCount; // Проверить количество неудачных попыток if ((ErrorCount > 3) && (Login1.PasswordRecoveryUrl != string.Empty)) Response.Redirect(Login1.PasswordRecoveryUrl); }

    Элемент управления Login генерирует события в порядке, представленном на рисунке ниже:

    Как упоминалось ранее, если вы перехватываете событие Authenticate, то должны добавить собственный код проверки имени пользователя и пароля. Свойство Authenticate поддерживает экземпляр AuthenticateEventArgs списка параметров. Этот класс аргумента события поддерживает одно свойство по имени Authenticated. Если установить его в true, то элемент управления Login предполагает, что аутентификация прошла успешно, и инициирует событие LoggedIn. Если же установить это свойство в false, будет отображен FailureText и инициировано событие LoginError:

    Protected void Login1_Authenticate(object sender, AuthenticateEventArgs e) { if (Membership.ValidateUser(Login1.UserName, Login1.Password)) { e.Authenticated = true; } else { e.Authenticated = false; } }

    Как видите, имеется прямой доступ к введенным значениям через свойства UserName и Password, которые содержат текст, введенный в соответствующие текстовые поля. Если вы используете шаблонные элементы управления и хотите получить значение из другого элемента, в дополнение к элементам с идентификаторами UserName и Password, то для этого можете использовать метод FindControl() для получения этого дополнительного элемента. Этот метод принимает идентификатор нужного элемента и возвращает экземпляр System.Web.UI.Control. Затем полученный объект просто приводится к типу требуемого элемента управления и читается значение, необходимое специальному методу проверки удостоверения пользователя.

    Недавно познакомился с инструментом для валидации данных 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 - это большой зверь или пушка, из которой не стоит палить по воробьям:-)

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

    Запустите 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 сайта из методами контролера.