エンタープライズ アプリの検証
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
プロパティが更新されて、検証が成功または失敗したことを示します。