Validieren Daten über die UI mit WCF RIA und dem Muster MVVM
-
25-09-2019 - |
Frage
Gibt es eine bewährte Methode oder allgemein akzeptierte Art und Weise der Strukturierung und Validierung von Daten unter Verwendung von MVVM in Verbindung mit RIA Service in Silverlight?
Hier ist der Kern meines Problems. Sagen wir, ich habe eine EmployeeView, EmployeeViewModel und einige Mitarbeiter Einheit. In regelmäßigen RIA-Anwendungen will ich, dass Employee-Entität auf der Ansicht aussetzen und ich Validierung erhalten „kostenlos“, weil Entities INotifyDataErrorInfo und IDataErrorInfo implementieren (richtig?).
Nun, wenn ich einige Mitarbeiter Eigenschaften durch ein Ansichtsmodell verfügbar machen möge, anstatt direkt durch eine Entity dann wird es kompliziert. Ich konnte die Bits aussetzen, die ich brauche und sie direkt auf das Backend in die Einheit Haken, wie folgt aus:
private Employee _employee;
public EmployeeViewModel()
{
_employee = new Employee();
}
public string Name
{
get { return _employee.Name; }
set
{
_employee.Name = value;
// fire property change, etc.
}
}
... aber ich verliere die leckere „frei“ Validierung von Entitäten. Sonst könnte ich die Einheit direkt in dem View-Modell aussetzen, wie so
private Employee _employee;
public Employee Employee
{
get { return _employee; }
}
public EmployeeViewModel()
{
_employee = new Employee();
}
In diesem Fall wird die Ansicht direkt an die Mitarbeiter Einheit binden und seine Eigenschaften in dort finden, etwa so:
<StackPanel DataContext="{Binding Employee}">
<TextBox Text="{Binding Name}" />
</StackPanel>
Mit Hilfe dieser Methode, die wir „frei“ Validierung erhalten, aber es ist nicht gerade eine saubere Implementierung von MVVM.
Eine dritte Option wäre INotifyDataErrorInfo und IDataErrorInfo mich in der VMs zu implementieren, aber dies scheint wie eine Menge von Sanitär-Code, wenn man bedenkt, wie einfach es wäre für mich die obige Lösung zu verwenden und haben etwas etwas weniger „sauber“ aber ein verdammt viel leichter am Ende des Tages.
Also ich meine Frage erraten ist, sind, welche diese Ansätze angemessen in welcher Situation? Gibt es einen besseren Ansatz, den ich fehle?
Falls es relevant Ich freue mich auf den Caliburn.Micro MVVM Rahmen, aber ich wäre daran interessiert, Antworten zu sehen, die allgemein gelten.
Lösung
Ich bin mit RIA mit Caliburn.Micro und bin ziemlich zufrieden mit meiner Lösung für Client-seitige Validierung.
Was ich getan habe ist ein ValidationBaseViewModel
zwischen Screen
setzen (zur Verfügung gestellt von Caliburn.Micro) und meinem tatsächlichen Anwendungs-VMs (EmployeeViewModel
in Ihrem Fall). ValidationBaseViewModel
Geräte INotifyDataErrorInfo
so dass Sanitär-Code Ihr über nur einmal geschrieben spricht. Ich habe dann auf Hinzufügen / Entfernen / notify von Fehlern über ValidationBaseViewModel
von einer Überschreibung der (Caliburn.Micro) PropertyChangedBase.NotifyOfPropertyChange
mit dem folgenden Code:
public override void NotifyOfPropertyChange(string property)
{
if (_editing == null)
return;
if (HasErrors)
RemoveErrorFromPropertyAndNotifyErrorChanges(property, 100);
if (_editing.HasValidationErrors)
{
foreach (var validationError in
_editing.ValidationErrors
.Where(error => error.MemberNames.Contains(property)))
{
AddErrorToPropertyAndNotifyErrorChanges(property, new ValidationErrorInfo() { ErrorCode = 100, ErrorMessage = validationError.ErrorMessage });
}
}
base.NotifyOfPropertyChange(property);
}
Dies ist eigentlich in einer anderen VM (zwischen ValidationBaseViewModel und EmployeeViewModel) mit folgenden Definition:
public abstract class BaseEditViewModel<TEdit> :
ValidationBaseViewModel where TEdit : Entity
Dabei ist Entity
RIAs System.ServiceModel.DomainServices.Client.Entity
und das _editing
Klassenglied ist eine Instanz dieses Typs TEdit
, die von der aktuellen VM bearbeitet wird.
In Kombination mit Caliburn Koroutinen dies ermöglicht es mir, ein paar coolen Sachen wie die folgenden zu tun:
[Rescue]
public IEnumerable<IResult> Save()
{
if (HasErrors)
{
yield return new GiveFocusByName(PropertyInError);
yield break;
}
...
}
Andere Tipps
Wenn Sie nicht möchten, dass externe Ressourcen oder Frameworks verwenden, dann könnte ich Ihnen eine ViewModelBase
haben, die INotifyDataErrorInfo
implementieren.
Diese Klasse wird ValidateProperty(string propertyName, object value)
eine speciic Eigenschaft und Validate()
Methode zur Validierung der das gesamte Objekt zu validieren. Intern verwenden die Validator
Klasse die ValidationResult
s zurückzukehren.
Wenn Sie Reflektor verwenden, kann es sein ziemlich einfach durch die Nachahmung des Validierungsprozesses in der Entity
Klasse selbst auf den ViewModelBase
.
Obwohl es nicht "frei" ist, ist immer noch relativ billig tho.
Hier ist eine Beispielimplementierung von IDataErrorInfo
. Obwohl nicht getestet, werden Sie die Idee.
public class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
/*
* InotifyPropertyChanged implementation
* Consider using Linq expressions instead of string names
*/
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
if (implValidationErrors == null) return null;
return ImplValidationErros.Where(ve =>
ve.MemberNames.Any(mn => mn == propertyName));
}
public bool HasErrors
{
get
{
return implValidationErrors == null || ImplValidationErros.Any();
}
}
private List<ValidationResult> implValidationErrors;
private List<ValidationResult> ImplValidationErros
{
get
{
return implValidationErrors ??
(implValidationErrors = new List<ValidationResult>());
}
}
private ReadOnlyCollection<ValidationResult> validationErrors;
[Display(AutoGenerateField = false)]
protected ICollection<ValidationResult> ValidationErrors
{
get
{
return validationErrors ??
(validationErrors =
new ReadOnlyCollection<ValidationResult>(ImplValidationErros));
}
}
protected void ValidateProperty(string propertyName, object value)
{
ValidationContext validationContext =
new ValidationContext(this, null, null);
validationContext.MemberName = propertyName;
List<ValidationResult> validationResults =
new List<ValidationResult>();
Validator.TryValidateProperty(
value,
validationContext,
validationResults);
if (!validationResults.Any()) return;
validationResults
.AddRange(ValidationErrors
.Where(ve =>
!ve.MemberNames.All(mn =>
mn == propertyName)));
implValidationErrors = validationResults;
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
können Sie eine partielle Klasse verwenden, um Ihre Einheit zu verlängern und Datenvalidierung fügen dort über IDataErrorInfo.