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.

War es hilfreich?

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 ValidationResults 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.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top