Обработка ошибок привязки модели

69

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

Для достижения этой цели мы внесли ряд изменений в файл веб-формы Default.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Binding.Default" %>

<!DOCTYPE html>
<html>
<head runat="server">
    <title>Привязка моделей</title>
    <style>
        ...
    </style>
</head>
<body>
    <asp:PlaceHolder ID="ErrorPanel" Visible="false" runat="server">
        <div class="error panel">
            Исправьте следующие ошибки:
                <ul>
                    <asp:Repeater ID="Repeater1" SelectMethod="GetModelValidationErrors"
                        ViewStateMode="Disabled" ItemType="System.String" runat="server">
                        <ItemTemplate>
                            <li><%# Item %></li>
                        </ItemTemplate>
                    </asp:Repeater>
                </ul>
        </div>
    </asp:PlaceHolder>
    <div class="panel">
        ...
    </div>
    <div class="panel">
        ...
    </div>
</body>
</html>

С помощью элемента управления Repeater ошибки отображаются в виде элементов списка; сведения об ошибках получаются из метода GetModelValidationErrors() класса отделенного кода. Мы также добавили литеральный контент, который наряду с Repeater содержится в элементе управления PlaceHolder. Элемент управления Repeater будет генерировать по одному элементу <li> для каждой ошибки, возвращаемой методом GetModelValidationErrors() класса отделенного кода, а контент из Repeater и литеральный контент будут включены в ответ, только если свойство или атрибут Visible для элемента управления PlaceHolder установлен в true.

В примере ниже показаны изменения, внесенные в файл отделенного кода Default.aspx.cs, где производится манипулирование элементом управления PlaceHolder и предоставление данных для Repeater:

// ...
using System.Collections.Generic;

namespace Binding
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (this.IsPostBack)
                DisplayUser(GetUser());

            ErrorPanel.Visible = !ModelState.IsValid;
        }

        protected User GetUser()
        {
            User model = new User();

            IValueProvider provider = new FormValueProvider(ModelBindingExecutionContext);
            TryUpdateModel<User>(model, provider);
            return model;
        }

        protected void DisplayUser(User user)
        {
            // ...
        }

        public IEnumerable<string> GetModelValidationErrors()
        {
            if (!ModelState.IsValid)
                foreach (KeyValuePair<string, ModelState> pair in ModelState)
                    foreach (ModelError error in pair.Value.Errors)
                        if (!String.IsNullOrEmpty(error.ErrorMessage))
                            yield return error.ErrorMessage;
        }
    }
}

Метод GetUser() модифицирован так, чтобы не генерировать исключение, когда метод TryUpdateModel<T>() возвращает false, как мы поступали с ошибками ранее. Мы добавили метод GetModelValidationErrors(), в котором реализован новый прием обработки ошибок. Свойство Page.ModelState возвращает экземпляр класса ModelStateDictionary, содержащего информацию о выполненной привязке модели и проверке достоверности, в котором определены свойства, описанные в таблице ниже:

Свойства, определенные в классе ModelStateDictionary
Имя Описание
IsValid

Возвращает true, если ошибки привязки модели отсутствуют, и false, если они есть

Keys

Возвращает коллекцию ключей, содержащихся в словаре

Values

Возвращает коллекцию значений, содержащихся в словаре

Свойство IsValid используется для контроля видимости элемента управления PlaceHolder, т.е. литеральный контент и результат из элемента управления Repeater будут включаться в ответ только при наличии ошибок. Класс ModelStateDictionary реализует интерфейс IEnumerable<KeyValuePair<string, ModelState>>, который позволяет пройти по очереди по всем свойствам, привязанным к модели. Часть string в KeyValuePair - это имя свойства модели, которое описано посредством части ModelState. В классе ModelState определены свойства, описанные ниже:

Errors

Возвращает коллекцию объектов ModelError, которые описывают ошибки, возникшие во время привязки предоставленного значения к свойству модели.

Value

Возвращает значение, которое использовалось в привязке модели.

Класс ModelError описывает одиночную ошибку привязки модели и определяет свойства, перечисленные ниже:

ErrorMessage

Получает сообщение об ошибке, выдаваемое атрибутом проверки достоверности

Exception

Возвращает исключение, вызванное ошибкой проверки достоверности

Может показаться, что в описание ошибок вовлечено слишком много объектов, но на самом деле с ними легко работать, и они позволяют системе привязки моделей сообщать об ошибках для множества свойств. Это значит, что мы можем предоставлять пользователю единый список всех ошибок проверки достоверности, которые возникли.

В методе GetModelValidationErrors() используется тот факт, что объект ModelStateDictionary выдает объекты KeyValuePair<string, ModelState>, когда применяется в цикле foreach для обработки всей информации, доступной о процессе привязки модели. Для каждой пары "ключ/значение" мы используем объект Value, чтобы получить ModelState и пройти по содержащейся в ней коллекции объектов ModelError, выдавая последовательность значений ErrorMessage.

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

Сообщение пользователю об ошибках проверки достоверности

Это определенное улучшение по сравнению с предыдущим подходом, но для выполнения проверки достоверности данных пользователь по-прежнему должен отправлять форму серверу.

Использование сводки по проверке достоверности

Мы продемонстрировали способ получения и отображения ошибок проверки достоверности вручную, поскольку это помогает понять взаимодействие между разными частями ASP.NET Framework. В состав ASP.NET входит встроенный элемент управления под названием ValidationSummary, который отображает ошибки, но не генерирует вывод, если ошибки отсутствуют.

В примере ниже приведено содержимое файла Default.aspx с добавленным элементом управления ValidationSummary:

...
<body>
    <form id="form1" runat="server">
        <asp:ValidationSummary HeaderText="Исправьте следующие ошибки:" CssClass="error" runat="server" />
        <div class="panel">
            ...
        </div>
        <div class="panel">
            ...
        </div>
    </form>
</body>
</html>

Обратите внимание на небольшую реструктуризацию веб-формы - причина в том, что элемент управления ValidationSummary должен использоваться внутри элемента <form> серверной стороны. (Это еще одна причина, по которой мы объясняем ручной подход: элемент управления ValidationSummary неприменим в случае работы с несколькими формами.) В элементе управления ValidationSummary определены следующие свойства:

DisplayMode

Указывает, каким образом элементы отображаются. Допустимыми значениями являются BulletList (стандартное значение, обеспечивающее генерацию элементов <ul> и <li>), List (разделяет ошибки с помощью элементов <br>) и SingleParagraph (помещает все сообщения об ошибках в единственный литеральный текстовый блок).

HeaderText

Указывает строку, отображаемую перед сообщениями об ошибках

В примере выше было принято стандартное значение для атрибута DisplayMode (т.е. ошибки будут отображаться в виде элементов списка) и указана строка в атрибуте HeaderText, которая отобразится перед списком ошибок. Мы также установили свойство CssClass, которое назначает HTML-элементу верхнего уровня, сгенерированному элементом управления ValidationSummary, класс error, в результате чего сообщение об ошибке отображается с использованием того же стиля, что и у списка, построенного вручную.

В элементе управления ValidationSummary определены и другие свойства, поддерживающие подход к проверке достоверности, который применялся до выхода версии ASP.NET 4.5 и требовал помещения специальных элементов управления проверкой рядом с каждым полем формы. Мы не описывали эти свойства, поскольку появившаяся в ASP.NET 4.5 поддержка привязки моделей и проверки достоверности проще как в работе, так и в сопровождении.

Элемент управления ValidationSummary обнаруживает ошибки привязки модели и проверки достоверности автоматически и отображается только при наличии ошибок, которые должны быть показаны пользователю. И это означает, что мы можем упростить класс отделенного кода, т.к. больше не придется предоставлять элементу управления Repeater сообщения об ошибках и контролировать видимость этого элемента управления.

Упрощенный класс отделенного кода представлен в примере ниже:

using System;
using Binding.Models;
using System.Text.RegularExpressions;
using System.Web.ModelBinding;
using System.Collections.Generic;

namespace Binding
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (this.IsPostBack)
                DisplayUser(GetUser());
        }

        protected User GetUser()
        {
            User model = new User();

            IValueProvider provider = new FormValueProvider(ModelBindingExecutionContext);
            TryUpdateModel<User>(model, provider);
            return model;
        }

        protected void DisplayUser(User user)
        {
            sname.InnerText = user.Name;
            sage.InnerText = user.Age.ToString();
            scell.InnerText = user.Cell;
            szip.InnerText = user.Zip;
        }
    }
}

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

Использование элемента управления ValidationSummary для отображения ошибок привязки модели и проверки достоверности
Пройди тесты
Лучший чат для C# программистов