Note
この電子ブックは 2017 年春に発行されたもので、その後は改訂されていません。 このブックには今なお価値のある内容が多く含まれていますが、一部の記載内容は古くなっています。
ユーザーから入力を受け取るアプリでは、その入力が有効であることを確認する必要があります。 たとえば、アプリでは、入力に特定の範囲の文字のみが含まれているかどうか、入力が一定の長さであるかどうか、または特定の形式に一致しているかどうかを確認します。 検証を行わないと、ユーザーはアプリが失敗する原因となるデータを提供する危険性があります。 検証を行うと、ビジネス ルールが適用され、攻撃者による悪意のあるデータの挿入を防ぎます。
Model-View-ViewModel (MVVM) パターンのコンテキストでは、多くの場合、データの検証を実行して、ユーザーが修正できるように検証エラーをビューに通知するために、ビュー モデルまたはモデルが必要です。 eShopOnContainers モバイル アプリでは、ビュー モデルのプロパティの同期クライアント側検証を実行した後、無効なデータを含むコントロールを強調表示し、データが無効である理由をユーザーに通知するエラー メッセージを表示して、検証エラーをユーザーに通知します。 図 6-1 は、eShopOnContainers モバイル アプリでの検証の実行に関連するクラスを示します。
図 6-1: eShopOnContainers モバイル アプリの検証クラス
検証を必要とするビュー モデル プロパティは型 ValidatableObject<T> であり、各 ValidatableObject<T> インスタンスには、その Validations プロパティに検証規則が追加されています。 検証は、ValidatableObject<T> インスタンスのValidate メソッドを呼び出すことによってビュー モデルから呼び出されます。このメソッドは、検証規則を取得し、ValidatableObject<T> Value プロパティに対して実行します。 検証エラーはすべて ValidatableObject<T> インスタンスの Errors プロパティに配置され、ValidatableObject<T> インスタンスの IsValid プロパティが更新されて、検証が成功または失敗したことを示します。
プロパティ変更通知は ExtendedBindableObject クラスによって提供されるため、Entry コントロールをビュー モデル クラスの ValidatableObject<T> インスタンスの IsValid プロパティにバインドして、入力されたデータが有効であるかどうかを通知できます。
検証規則の指定
検証規則を指定するには、次のコード例に示す IValidationRule<T> インターフェイスから派生するクラスを作成します。
public interface IValidationRule<T>
{
string ValidationMessage { get; set; }
bool Check(T value);
}
このインターフェイスは、検証規則クラスが、必要な検証を実行するために使用する boolean Check メソッドと、検証が失敗した場合に表示される検証エラー メッセージを値とする ValidationMessage プロパティを提供する必要があることを指定します。
次のコード例は、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 メソッドは、値引数が null または空、あるいは空白文字のみで構成されていることを示す boolean を返します。
次のコード例は、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 プロパティの値を調べると確認できます。
Note
プロパティの検証には、依存プロパティが含まれる場合があります。 依存プロパティの一例としては、プロパティ 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);
}
}
検証を行うには、次のコード例に示すように、検証規則を各 ValidatableObject<T> インスタンスの Validations コレクションに追加する必要があります。
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 モバイル アプリで使用される検証アプローチでは、プロパティの検証を手動でトリガーできます。また、プロパティが変更されたときに検証を自動的にトリガーすることもできます。
手動による検証のトリガー
ビュー モデル プロパティについては、検証を手動でトリガーできます。 たとえば、モック サービスを使用している場合、ユーザーが LoginView で [ログイン] ボタンをタップすると、eShopOnContainers モバイル アプリで検証がトリガーされます。 コマンド デリゲートは 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 でユーザーが入力したユーザー名とパスワードの検証を実行します。 次のコード例は、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 インスタンスが追加されています。 このビヘイビアーによって、Entry で発生する [TextChanged] イベント (これは、Entry 内のテキストが変更されたときに発生します) に応答して ValidateUserNameCommand が実行されます。 次に 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>
このスタイルは、Entry コントロールのアタッチされたビヘイビアー LineColorBehavior のアタッチされた ApplyLineColor および LineColor プロパティを設定します。 スタイルの詳細については、 のスタイルに関するページを参照してください。
アタッチされた 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 オーバーライドは、アタッチされた LineColor プロパティが変更されるか、または Entry のHeight プロパティが変更された場合、線の色を更新して、Entryコントロールのバインド可能なプロパティの変更に応答します。 エフェクトの詳細については、エフェクトに関するページを参照してください。
Entry コントロールに有効な値が入力されると、コントロールの下部に黒色の線が適用され、検証エラーがないことを示します。 図 6-3 は、この例を示しています。

図 6-3: 検証エラーがないことを示す黒色の線
Entry コントロールには、Triggers コレクションに追加された DataTrigger もあります。 次のコード例は、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 プロパティに対して実行します。 検証エラーはすべて ValidatableObject<T> インスタンスの Errors プロパティに配置され、ValidatableObject<T> インスタンスの IsValid プロパティが更新されて、検証が成功または失敗したことを示します。
