Domanda

C'è una buona pratica o un modo ampiamente accettato di strutturazione e di dati di convalida utilizzando MVVM in combinazione con servizi di RIA in Silverlight?

Ecco il nocciolo del mio problema. Diciamo che ho un EmployeeView, EmployeeViewModel e qualche entità dei dipendenti. Nelle applicazioni RIA regolari farò esporre tale entità Impiegato sulla vista e ho ottenere la convalida "gratis", in quanto entità implementano INotifyDataErrorInfo e IDataErrorInfo (giusto?).

Ora, se voglio esporre alcune proprietà dei dipendenti attraverso un ViewModel anziché direttamente tramite un'entità allora diventa più complicato. Ho potuto esporre i bit che devo direttamente e agganciarle nell'entità sull'estremità posteriore, in questo modo:

    private Employee _employee;

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

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

... ma perdo la gustosa "libera" la convalida di entità. In caso contrario, ho potuto esporre il soggetto direttamente nel modello vista, in questo modo

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

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

In questo caso, la vista si legano direttamente all'entità Employee e trovare le sue proprietà in là, in questo modo:

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

Con questo metodo si ottiene la validazione "libero", ma non è esattamente un'implementazione pulita di MVVM.

Una terza opzione sarebbe quella di implementare INotifyDataErrorInfo e IDataErrorInfo me in macchine virtuali, ma questo sembra un sacco di codice di impianto idraulico, considerando quanto sarebbe facile per me di utilizzare la soluzione di cui sopra e hanno qualcosa di un po 'meno "pulita" ma un diavolo di molto più facile, alla fine della giornata.

Quindi credo che la mia domanda è, quale di questi approcci sono appropriati in quale situazione? C'è un approccio migliore che mi manca?

Nel caso in cui sia rilevante Sto guardando il quadro Caliburn.Micro MVVM, ma sarei curioso di vedere le risposte che si applicano genericamente.

È stato utile?

Soluzione

Sto usando RIA con Caliburn.Micro e sono abbastanza soddisfatto della mia soluzione per la validazione lato client.

Quello che ho fatto è mettere un ValidationBaseViewModel tra Screen (fornito da Caliburn.Micro) e la mia effettiva applicazione VM (EmployeeViewModel nel tuo caso). implementa ValidationBaseViewModel INotifyDataErrorInfo modo che il codice impianto idraulico vostro parlare è scritto solo una volta. Ho poi aggiungere / rimuovere / gli errori tramite ValidationBaseViewModel da un override del (Caliburn.Micro) PropertyChangedBase.NotifyOfPropertyChange con il seguente codice:

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

Questo è in realtà in un altro VM (tra ValidationBaseViewModel e EmployeeViewModel) con la seguente definizione:

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

dove Entity è RIA System.ServiceModel.DomainServices.Client.Entity e il membro della classe _editing è un esempio di questo tipo TEdit che viene modificato dalla corrente VM.

In combinazione con Caliburn coroutine questo mi permette di fare un po 'di cose interessanti come il seguente:

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

    ...
}

Altri suggerimenti

Se non si desidera utilizzare risorse esterne o framework, allora si potrebbe avere un ViewModelBase che implementano INotifyDataErrorInfo.

Questa classe avrà ValidateProperty(string propertyName, object value) per convalidare una proprietà speciic, e il metodo Validate() convalidare l'intero oggetto. Internamente utilizzare il Validator classe per restituire i ValidationResults.
Se si utilizza il riflettore, può essere abbastanza facile per ottenere imitando il processo di convalida nel Entity stessa classe al ViewModelBase.

Anche se non è un "libero", è ancora tho relativamente a buon mercato.

Ecco un esempio di implementazione IDataErrorInfo. Anche se non testato, vi darà l'idea.

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

è possibile utilizzare una classe parziale per estendere la vostra entità e aggiungere la convalida dei dati non tramite IDataErrorInfo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top