Question

Y at-il une meilleure façon pratique ou largement acceptée de la structuration et la validation des données en utilisant MVVM conjointement avec les services de RIA en Silverlight?

Voici le point crucial de mon problème. Disons que j'ai un EmployeeView, EmployeeViewModel et une entité de l'employé. Dans les applications régulières RIA j'exposerai cette entité des employés sur la vue et j'obtenir la validation « gratuitement », parce que les entités mettent en œuvre INotifyDataErrorInfo et IDataErrorInfo (correct?).

Maintenant, si je veux exposer certaines propriétés des employés par un ViewModel au lieu de directement par une entité, alors il devient plus compliqué. Je pourrais exposer les bits que j'ai besoin directement et les accrocher dans l'entité sur le backend, comme ceci:

    private Employee _employee;

    public EmployeeViewModel()
    {
        _employee = new Employee();
    }

    public string Name
    {
        get { return _employee.Name; }
        set
        {
            _employee.Name = value;
            // fire property change, etc.
        }
    }

... mais je perds le savoureux « libre » validation des entités. Dans le cas contraire, je pourrais exposer l'entité directement dans le modèle de vue, comme si

    private Employee _employee;
    public Employee Employee
    {
        get { return _employee; }
    }

    public EmployeeViewModel()
    {
        _employee = new Employee();
    }

Dans ce cas, la vue se lie directement à l'entité des employés et trouver ses propriétés là-dedans, comme ceci:

<StackPanel DataContext="{Binding Employee}">
    <TextBox Text="{Binding Name}" />
</StackPanel>

En utilisant cette méthode, nous obtenons la validation « libre », mais ce n'est pas exactement une implémentation propre de MVVM.

Une troisième option serait de mettre en œuvre INotifyDataErrorInfo et IDataErrorInfo moi-même dans les machines virtuelles, mais cela semble être un très grand nombre de code de plomberie, considérant combien il serait facile pour moi d'utiliser la solution ci-dessus et avoir quelque chose un peu moins « propre » mais un diable de beaucoup plus facile à la fin de la journée.

Je suppose que ma question est, qui de ces approches sont appropriées dans quelle situation? Y at-il une meilleure approche que je suis absent?

Dans le cas où il est pertinent que je regarde le cadre Caliburn.Micro MVVM, mais je serais désireux de voir les réponses pertinentes génériquement.

Était-ce utile?

La solution

J'utilise RIA avec Caliburn.Micro et je suis assez satisfait de ma solution pour la validation du côté client.

Ce que je l'ai fait est de mettre un ValidationBaseViewModel entre Screen (fourni par Caliburn.Micro) et mon application effective des machines virtuelles (EmployeeViewModel dans votre cas). ValidationBaseViewModel implémente INotifyDataErrorInfo afin que le code de plomberie de votre parler est écrit seulement une fois. Je puis ajouter / supprimer / notify des erreurs par ValidationBaseViewModel d'un remplacement de la (Caliburn.Micro) PropertyChangedBase.NotifyOfPropertyChange avec le code suivant:

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);
}

Il est en fait dans une autre machine virtuelle (entre ValidationBaseViewModel et EmployeeViewModel) avec la définition suivante:

public abstract class BaseEditViewModel<TEdit> :
                                ValidationBaseViewModel where TEdit : Entity

Entity est RIAs System.ServiceModel.DomainServices.Client.Entity et l'élément de classe _editing est une instance de ce type TEdit qui est en cours de modification par la machine virtuelle en cours.

En combinaison avec coroutines Caliburn cela me permet de faire des trucs cool comme suit:

[Rescue]
public IEnumerable<IResult> Save()
{
    if (HasErrors)
    {
        yield return new GiveFocusByName(PropertyInError);
        yield break;
    }

    ...
}

Autres conseils

Si vous ne souhaitez pas utiliser des ressources externes ou des cadres, alors je vous pourriez avoir un ViewModelBase qui mettent en œuvre INotifyDataErrorInfo.

Cette classe aura ValidateProperty(string propertyName, object value) pour valider une propriété speciic et méthode Validate() pour valider l'objet entier. En interne utiliser le Validator classe pour retourner les ValidationResults.
Si vous utilisez le réflecteur, il peut être assez facile pour atteindre en mimant le processus de validation dans le Entity classe lui-même à la ViewModelBase.

Bien qu'il soit pas "libre", est encore relativement tho pas cher.

Voici une implémentation d'IDataErrorInfo. Bien que non testé, vous donnera l'idée.

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));
  }
}

vous pouvez utiliser une classe partielle pour étendre votre entité et y ajouter la validation des données via IDataErrorInfo.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top