Enterprise Apps での検証
Note
この電子ブックは 2017 年の春に発行され、それ以来更新されていません。 貴重なままの本に多くがありますが、資料の一部は時代遅れです。
ユーザーから入力を受け取るアプリでは、その入力が有効であることを確認する必要があります。 たとえば、アプリでは、入力に特定の範囲の文字のみが含まれているかどうか、入力が一定の長さであるかどうか、または特定の形式に一致しているかどうかを確認します。 検証を行わないと、ユーザーはアプリが失敗する原因となるデータを提供する危険性があります。 検証はビジネス ルールを適用し、攻撃者が悪意のあるデータを挿入するのを防ぎます。
Model-View-ViewModel (MVVM) パターンのコンテキストでは、多くの場合、ユーザーが修正できるように、データ検証を実行し、検証エラーをビューに通知するためにビュー モデルまたはモデルが必要になります。 eShopOnContainers モバイル アプリは、ビュー モデルのプロパティのクライアント側の同期検証を実行し、無効なデータを含むコントロールを強調表示し、データが無効な理由をユーザーに通知するエラー メッセージを表示することで、検証エラーをユーザーに通知します。 図 6-1 は、eShopOnContainers モバイル アプリでの検証の実行に関連するクラスを示しています。
図 6-1: eShopOnContainers モバイル アプリの検証クラス
検証を必要とするビュー モデル プロパティは型 ValidatableObject<T>
であり、各 ValidatableObject<T>
インスタンスには、その Validations
プロパティに検証規則が追加されています。 検証は、検証規則を取得し、 プロパティにValidate
対してValidatableObject<T>
Value
実行する インスタンスの ValidatableObject<T>
メソッドを呼び出すことによって、ビュー モデルから呼び出されます。 検証エラーはインスタンスの プロパティにErrors
配置されIsValid
、検証が成功したか失敗したかをValidatableObject<T>
示すためにインスタンスの プロパティが更新ValidatableObject<T>
されます。
プロパティ変更通知は ExtendedBindableObject
クラスによって提供されるため、Entry
コントロールをビュー モデル クラスの ValidatableObject<T>
インスタンスの IsValid
プロパティにバインドして、入力されたデータが有効であるかどうかを通知できます。
検証規則の指定
検証規則を指定するには、次のコード例に示す IValidationRule<T>
インターフェイスから派生するクラスを作成します。
public interface IValidationRule<T>
{
string ValidationMessage { get; set; }
bool Check(T value);
}
このインターフェイスは、検証ルール クラスが、必要な検証を実行するために使用されるメソッドとValidationMessage
、検証が失敗した場合に表示される検証エラー メッセージを値とするプロパティを提供boolean
Check
する必要があることを指定します。
次のコード例は、eShopOnContainers モバイル アプリでモック サービスを使用するときに、 でLoginView
ユーザーが入力したユーザー名とパスワードの検証を実行するために使用される検証規則を示していますIsNotNullOrEmptyRule<T>
。
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
value 引数が 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
value 引数が有効な電子メール アドレスであるかどうかを示す を返します。 これは、Regex
コンストラクターで指定された正規表現パターンが最初に出現する値引数を検索することによって実現されます。 入力文字列で正規表現パターンが見つかったかどうかは、オブジェクトSuccess
の プロパティの値をMatch
確認することで判断できます。
注意
プロパティの検証には、依存プロパティが含まれる場合があります。 依存プロパティの一例としては、プロパティ A の有効な値のセットが、プロパティ B で設定された特定の値に依存する場合があります。プロパティ A の値が許可された値の 1 つであることを確認するには、プロパティ B の値を取得する必要があります。さらに、プロパティ B の値が変更された場合、プロパティ A を再検証する必要があります。
プロパティへの検証規則の追加
eShopOnContainers モバイル アプリでは、検証を必要とするビュー モデルのプロパティは 型 ValidatableObject<T>
として宣言されます。ここで T
、 は検証するデータの型です。 次のコード例は、このような 2 つのプロパティの例を示します。
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>
インスタンスの Validations
コレクションに追加して、検証規則の ValidationMessage
プロパティの値を指定します。これは、検証が失敗した場合に表示される検証エラー メッセージを指定します。
検証のトリガー
eShopOnContainers モバイル アプリで使用される検証アプローチでは、プロパティの検証を手動でトリガーし、プロパティが変更されたときに自動的に検証をトリガーできます。
検証を手動でトリガーする
ビュー モデル プロパティについては、検証を手動でトリガーできます。 たとえば、ユーザーが モック サービスを使用しているときに、 の [ログイン ] ボタンをタップすると、eShopOnContainers モバイル アプリで LoginView
これが発生します。 コマンド デリゲートは LoginViewModel
で MockSignInAsync
メソッドを呼び出します。これは、次のコード例に示すように、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
、 でユーザーが入力したユーザー名とパスワードの検証を実行します。各ValidatableObject<T>
インスタンスで LoginView
Validate メソッドを呼び出します。 次のコード例は、 クラスの 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
プロパティ値が ValidatableObject<T>
インスタンスの Errors
コレクションに追加されます。 最後に、IsValid
プロパティが設定され、その値が呼び出し元のメソッドに返され、検証が成功したか失敗したかを示します。
プロパティが変更されたときに検証をトリガーする
バインドされたプロパティが変更されるたびに検証をトリガーすることもできます。 たとえば、LoginView
内の両方向のバインドによって UserName
または Password
プロパティが設定されると、検証がトリガーされます。 次のコード例は、これがどのように発生するかを示します。
<Entry Text="{Binding UserName.Value, Mode=TwoWay}">
<Entry.Behaviors>
<behaviors:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding ValidateUserNameCommand}" />
</Entry.Behaviors>
...
</Entry>
Entry
コントロールが ValidatableObject<T>
インスタンスの UserName.Value
プロパティにバインドされ、このコントロールの Behaviors
コレクションには、EventToCommandBehavior
インスタンスが追加されています。 この動作は、 ValidateUserNameCommand
で発生する [TextChanged
] イベントに Entry
応答して を実行します。これは、 内の Entry
テキストが変更されたときに発生します。 次に ValidateUserNameCommand
デリゲートによって ValidateUserName
メソッドが実行され、これは ValidatableObject<T>
インスタンスで Validate
メソッドを実行します。 したがって、ユーザーがユーザー名の Entry
コントロールに文字を入力するたびに、入力されたデータの検証が実行されます。
動作の詳細については、「動作の 実装」を参照してください。
検証エラーの表示
eShopOnContainers モバイル アプリは、無効なデータを含むコントロールを赤い線で強調表示し、無効なデータを含むコントロールの下にデータが無効である理由をユーザーに通知するエラー メッセージを表示することで、検証エラーをユーザーに通知します。 無効なデータが修正されると、行が黒に変わり、エラー メッセージが削除されます。 図 6-2 は、検証エラーが発生した場合の eShopOnContainers モバイル アプリの LoginView を示しています。
図 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
添付動作の LineColorBehavior
プロパティと LineColor
添付プロパティをEntry
設定します。 スタイルについて詳しくは、「Styles」(スタイル) をご覧ください。
添付プロパティの 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
添付プロパティが の場合ApplyLineColor
は、 クラスがtrue
コントロールのEffects
コレクションに追加されます。それ以外の場合は、コントロールのEffects
コレクションから削除されます。 動作の詳細については、「動作の 実装」を参照してください。
は EntryLineColorEffect
クラスを RoutingEffect
サブクラス化し、次のコード例に示します。
public class EntryLineColorEffect : RoutingEffect
{
public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")
{
}
}
クラスは RoutingEffect
、プラットフォーム固有の内部効果をラップするプラットフォームに依存しない効果を表します。 これは、プラットフォーム固有のエフェクトの型情報へのコンパイル時アクセスがないため、エフェクトの削除プロセスを簡略化します。 は EntryLineColorEffect
基底クラス コンストラクターを呼び出し、解決グループ名の連結と、各プラットフォーム固有の効果クラスで指定された一意の ID で構成されるパラメーターを渡します。
次のコード例は、iOS の実装を eShopOnContainers.EntryLineColorEffect
示しています。
[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 に、この例を示します。
図 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
され、アタッチされた動作のLineColorBehavior
添付プロパティが赤に変更LineColor
されます。 この例を図 6-4 に示します。
図 6-4: 検証エラーを示す赤い線
入力されたデータが無効な場合、コントロール内 Entry
の行は赤のままです。それ以外の場合は、入力されたデータが有効であることを示すために黒に変更されます。
トリガーの詳細については、「 トリガー」を参照してください。
エラー メッセージの表示
UI では、データが検証に失敗した各コントロールの下の 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
プロパティに検証規則が追加されています。 検証は、インスタンスの メソッドValidatableObject<T>
をValidate
呼び出すことによってビュー モデルから呼び出されます。これにより、検証規則が取得され、 プロパティにValidatableObject<T>
Value
対して実行されます。 検証エラーはインスタンスの Errors
ValidatableObject<T>
プロパティに配置され、検証が IsValid
成功したか失敗したかを示すためにインスタンスの ValidatableObject<T>
プロパティが更新されます。