目前,我正在构建我的第一个Xamarin MvvmCross应用程序,目前我正在考虑验证视图模型的用户输入。
在所有链接(包括MvvmCross团队)周围进行大量搜索,链接到这个插件:
这个插件使用了MvvmCross v3的一个非常老的版本。我尝试从这个插件中提取代码,并将其直接构建到我的应用程序Core项目中,直到我遇到绑定中断的变化。然后我得出结论,这个插件实际上需要完全重写,因为这样才能使用最新版本的MvvmCross。
所以我现在有点卡住了。
当前推荐的在视图模型中执行输入验证的最佳方法是什么?
发布于 2015-06-11 14:05:10
编辑:在GitHub https://github.com/kiliman/mvx-samples/tree/master/MvxSamples.Validation上添加示例项目
我使用MVVM验证助手http://www.nuget.org/packages/MvvmValidation/。
这是一个简单的验证库,易于使用。它与MvvmCross无关。
例如,在我的SigninViewModel中,我是如何使用它的:
private async void DoSignin()
{
try
{
if (!Validate())
{
return;
}
IsBusy = true;
Result = "";
var success = await SigninService.SigninAsync(Email, Password);
if (success)
{
Result = "";
ShowViewModel<HomeViewModel>();
Close();
return;
}
Result = "Invalid email/password. Please try again.";
}
catch (Exception ex)
{
Result = "Error occured during sign in.";
Mvx.Error(ex.ToString());
}
finally
{
IsBusy = false;
}
}
private bool Validate()
{
var validator = new ValidationHelper();
validator.AddRequiredRule(() => Email, "Email is required.");
validator.AddRequiredRule(() => Password, "Password is required.");
var result = validator.ValidateAll();
Errors = result.AsObservableDictionary();
return result.IsValid;
}它的好处在于,您可以将错误作为集合并在视图中绑定它们。对于Android,我将Error属性设置为键控错误消息。
<EditText
android:minHeight="40dp"
android:layout_margin="4dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:hint="Email"
local:MvxBind="Text Email; Error Errors['Email']"
android:id="@+id/EmailEditText" />
<EditText
android:minHeight="40dp"
android:layout_margin="4dp"
android:inputType="textPassword"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Password"
local:MvxBind="Text Password; Error Errors['Password']"
android:id="@+id/PasswordEditText" />下面是验证的内容:

编辑:显示助手代码
public static class ValidationResultExtension
{
public static ObservableDictionary<string, string> AsObservableDictionary(this ValidationResult result)
{
var dictionary = new ObservableDictionary<string, string>();
foreach (var item in result.ErrorList)
{
var key = item.Target.ToString();
var text = item.ErrorText;
if (dictionary.ContainsKey(key))
{
dictionary[key] = dictionary.Keys + Environment.NewLine + text;
}
else
{
dictionary[key] = text;
}
}
return dictionary;
}
}
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private IDictionary<TKey, TValue> _dictionary;
protected IDictionary<TKey, TValue> Dictionary
{
get { return _dictionary; }
}
public ObservableDictionary()
{
_dictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_dictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_dictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(int capacity)
{
_dictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
Insert(key, value, true);
}
public bool ContainsKey(TKey key)
{
return Dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return Dictionary.Keys; }
}
public bool Remove(TKey key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
TValue value;
Dictionary.TryGetValue(key, out value);
var removed = Dictionary.Remove(key);
if (removed)
{
OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
}
return removed;
}
public bool TryGetValue(TKey key, out TValue value)
{
return Dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return Dictionary.Values; }
}
public TValue this[TKey key]
{
get
{
return Dictionary.ContainsKey(key) ? Dictionary[key] : default(TValue);
}
set
{
Insert(key, value, false);
}
}
#endregion IDictionary<TKey,TValue> Members
public void Add(KeyValuePair<TKey, TValue> item)
{
Insert(item.Key, item.Value, true);
}
public void Clear()
{
if (Dictionary.Count > 0)
{
Dictionary.Clear();
OnCollectionChanged();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Dictionary.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Dictionary.Count; }
}
public bool IsReadOnly
{
get { return Dictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Dictionary.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)Dictionary).GetEnumerator();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public event PropertyChangedEventHandler PropertyChanged;
public void AddRange(IDictionary<TKey, TValue> items)
{
if (items == null)
{
throw new ArgumentNullException("items");
}
if (items.Count > 0)
{
if (Dictionary.Count > 0)
{
if (items.Keys.Any((k) => Dictionary.ContainsKey(k)))
{
throw new ArgumentException("An item with the same key has already been added.");
}
else
{
foreach (var item in items)
{
Dictionary.Add(item);
}
}
}
else
{
_dictionary = new Dictionary<TKey, TValue>(items);
}
OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray());
}
}
private void Insert(TKey key, TValue value, bool add)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
TValue item;
if (Dictionary.TryGetValue(key, out item))
{
if (add)
{
throw new ArgumentException("An item with the same key has already been added.");
}
if (Equals(item, value))
{
return;
}
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
}
else
{
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
}
}
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void OnCollectionChanged()
{
OnPropertyChanged();
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
{
OnPropertyChanged();
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));
}
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
OnPropertyChanged();
if (CollectionChanged != null)
{
CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));
}
}
}发布于 2019-08-26 07:56:59
没有既定的推荐,这是你最满意的。我发现很多选项特别冗长(即需要大量的锅炉板代码,即使是一些帮助程序库)。
我访问的库是用于编写规则的FluentValidation (它们有许多内置的通用库和用于重用/自定义的很好的方法,包括特定于上下文的规则),并且为了减少所需的大量复杂和代码行,编写了一个我自己的小助手库,在这里可以看到(用示例完成):
这里的示例使用Prism,但它根本不依赖于任何MVVM框架。
下面是一个例子:
构建/验证的类:
public class Email
{
public string RecipientEmailAddress { get; set; }
public string RecipientName { get; set; }
}使用我的库中提供的Validatable对象和 Fody.PropertyChanged (这也将为INPC节省大量锅炉板代码)在您的ViewModel中的属性
public Validatable<string> RecipientName { get; set; } = new Validatable<string>(nameof(Email.RecipientName));
public Validatable<string> EmailAddress { get; set; } = new Validatable<string>(nameof(Email.RecipientEmailAddress));为类创建一个AbstractValidator FluentValidation:
public class EmailValidator : AbstractValidator<Email>
{
public EmailValidator()
{
RuleFor(e => e.RecipientEmailAddress)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty()
.EmailAddress();
RuleFor(e => e.RecipientName)
.NotEmpty();
When(e => e.RecipientName != null, () =>
{
RuleFor(e => e.RecipientName)
.MinimumLength(3).WithMessage("How you bout to enter a FULL 'name' with less than 3 chars!?")
.Must(name => name.Contains(" ")).WithMessage("Expecting at least first and last name separated by a space!");
});
}
}在ViewModel中实现IValidate:
public void SetupForValidation() // to be called from your ViewModel's constructor
{
// set validators and prop groups
_emailValidator = new EmailValidator();
_emailValidatables = new Validatables(RecipientName, EmailAddress);
// maybe even set some defaults
RecipientName.Value = "Fred Fredovich";
}
public OverallValidationResult Validate(Email email)
{
return _emailValidator.Validate(email).ApplyResultsTo(_emailValidatables);
}
public void ClearValidation(string clearOptions = "")
{
_emailValidatables.Clear(clearOptions);
}实现命令(下面的示例使用Prism的DelegateCommand,但显然这不是必需的)来使用这些方法:
private DelegateCommand<string> _clearValidationCommand;
private DelegateCommand _validateEmailCommand;
public DelegateCommand<string> ClearValidationCommand =>
_clearValidationCommand ?? (_clearValidationCommand = new DelegateCommand<string>(ClearValidation)); // already defined above in step 4 as part of the interface requirements
public DelegateCommand ValidateEmailCommand =>
_validateEmailCommand ?? (_validateEmailCommand = new DelegateCommand(ExecuteValidateEmailCommand));
public void ExecuteValidateEmailCommand()
{
var email = _emailValidatables.Populate<Email>(); // this conveniently creates a new Email instance with the values from our Validatable objects (populated by the user via the View)
var overallValidationResult = Validate(email); // remember, this will also populate each individual Validatable's IsValid status and Errors list.
if (overallValidationResult.IsValidOverall)
{
// do something with the validated email instance
}
else
{
// do something else
}
if (overallValidationResult.NonSplitErrors.Any())
{
// do something with errors that don't pertain to any of our Validatables (which is not possible in our little example here)
}
}最后,视图(在本例中XAML中):
<Entry
Placeholder="Email"
Text="{Binding EmailAddress.Value}">
<Entry.Behaviors>
<!-- Note this behavior is included in the Prism Library -->
<behaviors:EventToCommandBehavior
Command="{Binding ClearValidationCommand}"
CommandParameter="RecipientEmailAddress"
EventName="Focused" />
</Entry.Behaviors>
</Entry>
<Label
Style="{StaticResource ErrorLabelStyle}"
Text="{Binding EmailAddress.FirstError}" />
<Button
Command="{Binding ValidateEmailCommand}"
Text="Validate" />这可能是最常见的用例-我们有:
但是,您也可以使用一个按钮立即清除所有验证,甚至与实际值(清除整个表单)一起清除,等等--只需在回购链接中阅读完整的示例,以及使用它的完全工作的Xamarin示例项目(其中包括一些更高级的示例,例如使用基于上下文的规则)。
希望这能帮上忙。
https://stackoverflow.com/questions/30778812
复制相似问题