Проверка в корпоративных приложениях

Примечание.

Эта электронная книга была опубликована весной 2017 года и с тех пор не была обновлена. Есть много в книге, которая остается ценным, но некоторые из материалов устарели.

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

В контексте шаблона Model-View-ViewModel (MVVM) модель представления или модель часто требуется для выполнения проверки данных и сигнала о любых ошибках проверки в представлении, чтобы пользователь смог исправить их. Мобильное приложение eShopOnContainers выполняет синхронную проверку свойств модели просмотра и уведомляет пользователя о любых ошибках проверки, выделите элемент управления, содержащий недопустимые данные, и отображая сообщения об ошибках, информирующие пользователя о том, почему данные недопустимы. На рисунке 6-1 показаны классы, участвующие в выполнении проверки в мобильном приложении eShopOnContainers.

Validation classes in the eShopOnContainers mobile app

Рис. 6-1. Классы проверки в мобильном приложении eShopOnContainers

Просмотр свойств модели, требующих проверки, имеет тип ValidatableObject<T>, и каждый ValidatableObject<T> экземпляр имеет правила проверки, добавленные в его Validations свойство. Проверка вызывается из модели представления путем вызова Validate метода экземпляра ValidatableObject<T> , который извлекает правила проверки и выполняет их для ValidatableObject<T>Value свойства. Все ошибки проверки помещаются в Errors свойство экземпляра ValidatableObject<T> , а IsValid свойство экземпляра ValidatableObject<T> обновляется, чтобы указать, выполнена ли проверка успешно или не выполнена.

Уведомление об изменении свойств предоставляется классом ExtendedBindableObject , поэтому Entry элемент управления может привязаться к IsValid свойству экземпляра ValidatableObject<T> в классе модели представления, чтобы получать уведомление о том, является ли введенные данные допустимыми.

Указание правил проверки

Правила проверки задаются путем создания класса, наследуемого IValidationRule<T> от интерфейса, который показан в следующем примере кода:

public interface IValidationRule<T>  
{  
    string ValidationMessage { get; set; }  
    bool Check(T value);  
}

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

В следующем примере кода показано IsNotNullOrEmptyRule<T> правило проверки, которое используется для проверки имени пользователя и пароля, введенных пользователем LoginView при использовании макетных служб в мобильном приложении eShopOnContainers:

public class IsNotNullOrEmptyRule<T> : IValidationRule<T>  
{  
    public string ValidationMessage { get; set; }  

    public bool Check(T value)  
    {  
        if (value == null)  
        {  
            return false;  
        }  

        var str = value as string;  
        return !string.IsNullOrWhiteSpace(str);  
    }  
}

Метод Check возвращает boolean значение, указывающее, является nullли аргумент значения пустым или состоит только из символов пробелов.

Хотя и не используется мобильным приложением eShopOnContainers, в следующем примере кода показано правило проверки для проверки адресов электронной почты:

public class EmailRule<T> : IValidationRule<T>  
{  
    public string ValidationMessage { get; set; }  

    public bool Check(T value)  
    {  
        if (value == null)  
        {  
            return false;  
        }  

        var str = value as string;  
        Regex regex = new Regex(@"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$");  
        Match match = regex.Match(str);  

        return match.Success;  
    }  
}

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

Примечание.

Проверка свойств иногда может включать зависимые свойства. Пример зависимых свойств заключается в том, что набор допустимых значений для свойства A зависит от конкретного значения, заданного в свойстве B. Чтобы проверка, что значение свойства A является одним из допустимых значений, потребует получения значения свойства B. Кроме того, при изменении значения свойства B необходимо будет пересмотреть свойство A.

Добавление правил проверки в свойство

В мобильном приложении eShopOnContainers свойства модели просмотра, требующие проверки, объявляются типом ValidatableObject<T>, где T является тип проверяемых данных. В следующем примере кода показан пример двух таких свойств:

public ValidatableObject<string> UserName  
{  
    get  
    {  
        return _userName;  
    }  
    set  
    {  
        _userName = value;  
        RaisePropertyChanged(() => UserName);  
    }  
}  

public ValidatableObject<string> Password  
{  
    get  
    {  
        return _password;  
    }  
    set  
    {  
        _password = value;  
        RaisePropertyChanged(() => Password);  
    }  
}

Для выполнения проверки правила проверки должны быть добавлены в Validations коллекцию каждого ValidatableObject<T> экземпляра, как показано в следующем примере кода:

private void AddValidations()  
{  
    _userName.Validations.Add(new IsNotNullOrEmptyRule<string>   
    {   
        ValidationMessage = "A username is required."   
    });  
    _password.Validations.Add(new IsNotNullOrEmptyRule<string>   
    {   
        ValidationMessage = "A password is required."   
    });  
}

Этот метод добавляет IsNotNullOrEmptyRule<T> правило проверки в коллекцию каждого ValidatableObject<T> экземпляра, указывая значения свойства правила ValidationsValidationMessage проверки, которое указывает сообщение об ошибке проверки, которое будет отображаться при сбое проверки.

Активация проверки

Подход проверки, используемый в мобильном приложении eShopOnContainers, может вручную активировать проверку свойства и автоматически активировать проверку при изменении свойства.

Активация проверки вручную

Проверку можно активировать вручную для свойства модели представления. Например, это происходит в мобильном приложении eShopOnContainers, когда пользователь нажимает кнопку "Вход " на сервере LoginView, при использовании макетных служб. Делегат команды вызывает MockSignInAsync метод в LoginViewModelметоде, который вызывает проверку путем выполнения Validate метода, который показан в следующем примере кода:

private bool Validate()  
{  
    bool isValidUser = ValidateUserName();  
    bool isValidPassword = ValidatePassword();  
    return isValidUser && isValidPassword;  
}  

private bool ValidateUserName()  
{  
    return _userName.Validate();  
}  

private bool ValidatePassword()  
{  
    return _password.Validate();  
}

Метод Validate выполняет проверку имени пользователя и пароля, введенных пользователем, LoginViewпутем вызова метода Validate для каждого ValidatableObject<T> экземпляра. В следующем примере кода показан метод Validate из ValidatableObject<T> класса:

public bool Validate()  
{  
    Errors.Clear();  

    IEnumerable<string> errors = _validations  
        .Where(v => !v.Check(Value))  
        .Select(v => v.ValidationMessage);  

    Errors = errors.ToList();  
    IsValid = !Errors.Any();  

    return this.IsValid;  
}

Этот метод очищает Errors коллекцию, а затем извлекает все правила проверки, добавленные в коллекцию объекта Validations . Метод Check для каждого полученного правила проверки выполняется, а ValidationMessage значение свойства для любого правила проверки, которое не может проверить данные, добавляется в Errors коллекцию экземпляра ValidatableObject<T> . Наконец, свойство задано, и его значение возвращается вызывающему методу, указывая, IsValid выполнена ли проверка успешно или завершилась ошибкой.

Активация проверки при изменении свойств

Проверка также может быть активирована всякий раз, когда изменяется связанное свойство. Например, когда двусторонняя привязка в наборах LoginViewUserName или Password свойстве активируется. В следующем примере кода показано, как это происходит:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">  
    <Entry.Behaviors>  
        <behaviors:EventToCommandBehavior  
            EventName="TextChanged"  
            Command="{Binding ValidateUserNameCommand}" />  
    </Entry.Behaviors>  
    ...  
</Entry>

Элемент Entry управления привязывается к UserName.Value свойству ValidatableObject<T> экземпляра, а в коллекцию EventToCommandBehavior элемента управления Behaviors добавлен экземпляр. Это поведение выполняется в ответ на событие [TextChanged], которое Entryвозникает ValidateUserNameCommand при изменении текстаEntry. В свою очередь делегат ValidateUserNameCommand выполняет ValidateUserName метод, который выполняет Validate метод на экземпляре ValidatableObject<T> . Таким образом, каждый раз, когда пользователь вводит символ в Entry элементе управления для имени пользователя, выполняется проверка введенных данных.

Дополнительные сведения о поведении см. в разделе "Реализация поведения".

Отображение ошибок проверки

Мобильное приложение eShopOnContainers уведомляет пользователя о любых ошибках проверки, выделите элемент управления, содержащий недопустимые данные с красной линией, и отображая сообщение об ошибке, которое сообщает пользователю, почему данные недопустимы под элементом управления, содержащий недопустимые данные. При исправлении недопустимых данных строка изменяется на черное и сообщение об ошибке удаляется. На рисунке 6-2 показан элемент LoginView в мобильном приложении eShopOnContainers при наличии ошибок проверки.

Displaying validation errors during login

Рис. 6-2. Отображение ошибок проверки во время входа

Выделение элемента управления, содержащего недопустимые данные

Присоединенное LineColorBehavior поведение используется для выделения Entry элементов управления, в которых произошли ошибки проверки. В следующем примере кода показано, как присоединенное LineColorBehavior поведение присоединено к элементу Entry управления:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
    <Entry.Style>
        <OnPlatform x:TypeArguments="Style">
            <On Platform="iOS, Android" Value="{StaticResource EntryStyle}" />
            <On Platform="UWP" Value="{StaticResource UwpEntryStyle}" />
        </OnPlatform>
    </Entry.Style>
    ...
</Entry>

Элемент Entry управления использует явный стиль, который показан в следующем примере кода:

<Style x:Key="EntryStyle"  
       TargetType="{x:Type Entry}">  
    ...  
    <Setter Property="behaviors:LineColorBehavior.ApplyLineColor"  
            Value="True" />  
    <Setter Property="behaviors:LineColorBehavior.LineColor"  
            Value="{StaticResource BlackColor}" />  
    ...  
</Style>

Этот стиль задает ApplyLineColor и LineColor присоединенные свойства присоединенного LineColorBehavior поведения в элементе Entry управления. Дополнительные сведения о стилях см. в статье Стили .

Если задано значение присоединенного ApplyLineColor свойства или изменяется, LineColorBehavior присоединенное поведение выполняет OnApplyLineColorChanged метод, который показан в следующем примере кода:

public static class LineColorBehavior  
{  
    ...  
    private static void OnApplyLineColorChanged(  
                BindableObject bindable, object oldValue, object newValue)  
    {  
        var view = bindable as View;  
        if (view == null)  
        {  
            return;  
        }  

        bool hasLine = (bool)newValue;  
        if (hasLine)  
        {  
            view.Effects.Add(new EntryLineColorEffect());  
        }  
        else  
        {  
            var entryLineColorEffectToRemove =   
                    view.Effects.FirstOrDefault(e => e is EntryLineColorEffect);  
            if (entryLineColorEffectToRemove != null)  
            {  
                view.Effects.Remove(entryLineColorEffectToRemove);  
            }  
        }  
    }  
}

Параметры этого метода предоставляют экземпляр элемента управления, к которому присоединено поведение, а также старые и новые значения присоединенного ApplyLineColor свойства. Класс EntryLineColorEffect добавляется в коллекцию элемента управления Effects , если ApplyLineColor присоединенное свойство true, в противном случае оно удаляется из коллекции элемента управления Effects . Дополнительные сведения о поведении см. в разделе "Реализация поведения".

Подклассы EntryLineColorEffectRoutingEffect класса и показаны в следующем примере кода:

public class EntryLineColorEffect : RoutingEffect  
{  
    public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")  
    {  
    }  
}

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

В следующем примере кода показана eShopOnContainers.EntryLineColorEffect реализация для iOS:

[assembly: ResolutionGroupName("eShopOnContainers")]  
[assembly: ExportEffect(typeof(EntryLineColorEffect), "EntryLineColorEffect")]  
namespace eShopOnContainers.iOS.Effects  
{  
    public class EntryLineColorEffect : PlatformEffect  
    {  
        UITextField control;  

        protected override void OnAttached()  
        {  
            try  
            {  
                control = Control as UITextField;  
                UpdateLineColor();  
            }  
            catch (Exception ex)  
            {  
                Console.WriteLine("Can't set property on attached control. Error: ", ex.Message);  
            }  
        }  

        protected override void OnDetached()  
        {  
            control = null;  
        }  

        protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)  
        {  
            base.OnElementPropertyChanged(args);  

            if (args.PropertyName == LineColorBehavior.LineColorProperty.PropertyName ||  
                args.PropertyName == "Height")  
            {  
                Initialize();  
                UpdateLineColor();  
            }  
        }  

        private void Initialize()  
        {  
            var entry = Element as Entry;  
            if (entry != null)  
            {  
                Control.Bounds = new CGRect(0, 0, entry.Width, entry.Height);  
            }  
        }  

        private void UpdateLineColor()  
        {  
            BorderLineLayer lineLayer = control.Layer.Sublayers.OfType<BorderLineLayer>()  
                                                             .FirstOrDefault();  

            if (lineLayer == null)  
            {  
                lineLayer = new BorderLineLayer();  
                lineLayer.MasksToBounds = true;  
                lineLayer.BorderWidth = 1.0f;  
                control.Layer.AddSublayer(lineLayer);  
                control.BorderStyle = UITextBorderStyle.None;  
            }  

            lineLayer.Frame = new CGRect(0f, Control.Frame.Height-1f, Control.Bounds.Width, 1f);  
            lineLayer.BorderColor = LineColorBehavior.GetLineColor(Element).ToCGColor();  
            control.TintColor = control.TextColor;  
        }  

        private class BorderLineLayer : CALayer  
        {  
        }  
    }  
}

Метод OnAttached получает собственный элемент управления для Xamarin.FormsEntry элемента управления и обновляет цвет линии путем вызова UpdateLineColor метода. Переопределение OnElementPropertyChanged реагирует на изменения привязываемого свойства элемента Entry управления путем обновления цвета линии, если присоединенное LineColor свойство изменяется или Height свойство Entry изменений. Дополнительные сведения об эффектах см. в статье Эффекты.

Если допустимые данные вводятся в Entry элемент управления, он применит черную линию к нижней части элемента управления, чтобы указать, что ошибка проверки отсутствует. На рисунке 6-3 показан пример этого.

Black line indicating no validation error

Рис. 6-3. Черная строка, указывающая на ошибку проверки

Элемент Entry управления также добавляется DataTrigger в свою Triggers коллекцию. В следующем примере кода показано DataTrigger:

<Entry Text="{Binding UserName.Value, Mode=TwoWay}">  
    ...  
    <Entry.Triggers>  
        <DataTrigger   
            TargetType="Entry"  
            Binding="{Binding UserName.IsValid}"  
            Value="False">  
            <Setter Property="behaviors:LineColorBehavior.LineColor"   
                    Value="{StaticResource ErrorColor}" />  
        </DataTrigger>  
    </Entry.Triggers>  
</Entry>

Это DataTrigger отслеживает UserName.IsValid свойство и, если оно становится falseзначением, выполняется Setter, который изменяет LineColor присоединенное свойство присоединенного LineColorBehavior поведения на красный. На рисунке 6-4 показан пример этого.

Red line indicating validation error

Рис. 6-4. Красная строка, указывающая на ошибку проверки

Строка в элементе Entry управления останется красной, пока введенные данные недопустимы, в противном случае она изменится на черную, чтобы указать, что введенные данные допустимы.

Дополнительные сведения о триггерах см. в разделе "Триггеры".

Отображение сообщений об ошибках

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

<Label Text="{Binding UserName.Errors, Converter={StaticResource FirstValidationErrorConverter}}"  
       Style="{StaticResource ValidationErrorLabelStyle}" />

Каждый Label из них привязывается к Errors свойству проверяемого объекта модели представления. Свойство Errors предоставляется классом ValidatableObject<T> и имеет тип List<string>. Errors Так как свойство может содержать несколько ошибок проверки, FirstValidationErrorConverter экземпляр используется для получения первой ошибки из коллекции для отображения.

Итоги

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

Просмотр свойств модели, требующих проверки, имеет тип ValidatableObject<T>, и каждый ValidatableObject<T> экземпляр имеет правила проверки, добавленные в его Validations свойство. Проверка вызывается из модели представления путем вызова Validate метода экземпляра ValidatableObject<T> , который извлекает правила проверки и выполняет их для ValidatableObject<T>Value свойства. Все ошибки проверки помещаются в Errors свойство экземпляра ValidatableObject<T>, а IsValid свойство экземпляра ValidatableObject<T> обновляется, чтобы указать, выполнена ли проверка успешно или не выполнена.