Pergunta

Existe uma prática recomendada ou uma maneira amplamente aceita de estruturar e validar dados usando MVVM em conjunto com os serviços da RIA no Silverlight?

Aqui está o ponto crucial do meu problema. Digamos que eu tenha um Funcionário, Funcionário ViewModel e alguma entidade dos funcionários. Em aplicativos RIA regulares, exporei essa entidade dos funcionários na visualização e recebo validação "gratuitamente", porque as entidades implementam o InotifyDataerrorinfo e o IDataerrorinfo (correto?).

Agora, se eu quiser expor algumas propriedades dos funcionários através de um viewmodel em vez de diretamente através de uma entidade, ela se torna mais complicada. Eu poderia expor os bits de que preciso diretamente e conectá -los à entidade no back -end, assim:

    private Employee _employee;

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

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

... Mas eu perco a saborosa validação "gratuita" de entidades. Caso contrário, eu poderia expor a entidade diretamente no modelo de vista, como assim

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

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

Nesse caso, a visão se vinculará diretamente à entidade dos funcionários e encontrará suas propriedades lá, como assim:

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

Usando esse método, obtemos validação "gratuita", mas não é exatamente uma implementação limpa do MVVM.

Uma terceira opção seria implementar InotifyDataerrorinfo e Idataerrorinfo eu nas VMs, mas isso parece ser um monte de código de encanamento, considerando o quão fácil seria para mim usar a solução acima e ter algo um pouco menos "limpo", mas um diabos de muito mais fácil no final do dia.

Então, acho que minha pergunta é: quais dessas abordagens são apropriadas em qual situação? Existe uma abordagem melhor que estou perdendo?

Caso seja relevante, estou olhando para a estrutura de Caliburn.micro MVVM, mas gostaria de ver as respostas que se aplicam genericamente.

Foi útil?

Solução

Estou usando o RIA com Caliburn.micro e estou muito feliz com minha solução para a validação do lado do cliente.

O que eu fiz é colocar um ValidationBaseViewModel entre Screen (fornecido por Caliburn.micro) e minhas VMs reais de aplicativos (EmployeeViewModel no seu caso). ValidationBaseViewModel implementos INotifyDataErrorInfo para que o código de encanamento de que você fale seja escrito apenas uma vez. Eu então adiciono/removo/notificar os erros via ValidationBaseViewModel De uma substituição do (Caliburn.micro) PropertyChangedBase.NotifyOfPropertyChange com o seguinte código:

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

Na verdade, isso está em outra VM (entre a ValidationBaseViewModel e o FylementViewModel) com a seguinte definição:

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

Onde Entity é rias System.ServiceModel.DomainServices.Client.Entity e a _editing Membro da classe é uma instância deste tipo TEdit que está sendo editado pela VM atual.

Em combinação com a Caliburn Coroutines, isso me permite fazer algumas coisas legais, como as seguintes:

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

    ...
}

Outras dicas

Se você não quiser usar recursos ou estruturas externas, então eu poderia ter um ViewModelBase esse implemento INotifyDataErrorInfo.

Essa aula terá ValidateProperty(string propertyName, object value) para validar uma propriedade específica e Validate() Método para validar todo o objeto. Use internamente o Validator classe para devolver o ValidationResults.
Se você usa o refletor, pode ser Bem fácil alcançar imitando o processo de validação no Entity classe em si para o ViewModelBase.

Embora não seja "grátis", ainda é relativamente barato.

Aqui está uma amostra de implementação de IDataErrorInfo. Embora não seja testado, dará a você a ideia.

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

Você pode usar uma classe parcial para estender sua entretenimento e adicionar validação de dados lá via idataerrorinfo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top