企業應用程式中的驗證
注意
本電子書於 2017 年春季出版,此後尚未更新。 這本書中有很多仍然有價值的,但一些材料已經過時。
任何接受使用者輸入的應用程式都應該確保輸入的內容有效。 例如,應用程式可以檢查輸入內容是否只包含特定範圍的字元、有特定的長度或符合特定的格式。 如不經驗證,使用者可能會提供導致應用程式失敗的資料。 驗證會強制執行商務規則,並防止攻擊者插入惡意數據。
在 Model-View-ViewModel (MVVM) 模式的內容中,通常需要有檢視模型或模型才能執行資料驗證,並向檢視發出任何驗證錯誤訊號,以便使用者更正錯誤。 eShopOnContainers 行動應用程式會執行檢視模型屬性的同步客戶端驗證,並醒目提示包含無效數據的控件,以及顯示錯誤訊息,告知用戶數據無效的原因。 圖 6-1 顯示 eShopOnContainers 行動裝置應用程式中執行驗證所涉及的類別。
圖 6-1:eShopOnContainers 行動應用程式中的驗證類別
需要驗證的檢視模型屬性類型為 ValidatableObject<T>
,而且每個 ValidatableObject<T>
執行個體都已將驗證規則新增至其 Validations
屬性。 藉由呼叫 實例的 ValidatableObject<T>
方法,從檢視模型叫Validate
用驗證,這個方法會擷取驗證規則,並針對 ValidatableObject<T>
Value
屬性執行它們。 任何驗證錯誤都會放入 Errors
實例的 ValidatableObject<T>
屬性中,並 IsValid
更新 實例的 ValidatableObject<T>
屬性,以指出驗證是否成功或失敗。
屬性變更通知是由 ExtendedBindableObject
類別提供,因此 Entry
控制項可以繫結至檢視模型類別之 ValidatableObject<T>
執行個體的 IsValid
屬性,以接獲輸入資料是否有效的通知。
指定驗證規則
驗證規則是透過建立衍生自 IValidationRule<T>
介面的類別所指定,如下列程式碼範例所示:
public interface IValidationRule<T>
{
string ValidationMessage { get; set; }
bool Check(T value);
}
這個介面會指定驗證規則類別必須提供boolean
Check
用來執行必要驗證的方法,以及ValidationMessage
其值為驗證錯誤訊息的屬性,如果驗證失敗,則會顯示該屬性。
下列程式代碼範例顯示 IsNotNullOrEmptyRule<T>
驗證規則,用來在 eShopOnContainers 行動應用程式中使用模擬服務時,對 使用者 LoginView
輸入的使用者名稱和密碼執行驗證:
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 自變數是否為有效的電子郵件位址。 在 value 引數中搜尋第一次出現的規則運算式模式,即可達成此目的,此模式是在 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>
執行個體的 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>
實例上的 Validate 方法,對使用者LoginView
輸入的使用者名稱和密碼執行驗證。 下列程式代碼範例顯示 類別的 ValidatableObject<T>
Validate 方法:
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
執行 ,以回應 上的 Entry
[TextChanged
] 事件引發,而當變更中的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
設定 控件上Entry
附加行為的 和 LineColor
附加屬性LineColorBehavior
。 如需樣式的詳細資訊,請參閱樣式。
設定或變更附加屬性的值 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
舊值和新值。 如果ApplyLineColor
附加屬性為 true
,類別EntryLineColorEffect
就會加入至控件的Effects
集合,否則會從控件的Effects
集合中移除。 如需行為的詳細資訊,請參閱 實作行為。
類別 EntryLineColorEffect
的 RoutingEffect
子類別,如下列程式代碼範例所示:
public class EntryLineColorEffect : RoutingEffect
{
public EntryLineColorEffect() : base("eShopOnContainers.EntryLineColorEffect")
{
}
}
類別 RoutingEffect
代表平台獨立效果,這個效果會包裝平臺特定的內部效果。 這可簡化效果移除程序,因為對於平台特定效果,並不存在對類型資訊的編譯時間資訊存取。 會 EntryLineColorEffect
呼叫基類建構函式,傳入由解析組名串連組成的參數,以及每個平臺特定效果類別上指定的唯一標識符。
下列程式代碼範例顯示 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
也已 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>
屬性,以指出驗證是否成功或失敗。