Pregunta

¿Hay una mejor práctica o forma ampliamente aceptada de la estructuración y validación de los datos usando MVVM en conjunto con los servicios de RIA en Silverlight?

Aquí está el quid de mi problema. Digamos que tengo una EmployeeView, EmployeeViewModel y alguna entidad del empleado. En aplicaciones RIA regulares voy a exponer esa entidad Empleado en la vista y obtener la validación "de forma gratuita", dado que las entidades implementen INotifyDataErrorInfo y IDataErrorInfo (correcto?).

Ahora si quiero exponer algunas propiedades de los empleados a través de un modelo de vista en lugar de directamente a través de una Entidad entonces se vuelve más complicado. Podría exponer a los bits que necesito directamente y engancharlos a la entidad en el backend, como esto:

    private Employee _employee;

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

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

... pero pierdo la sabrosa validación "libre" de las entidades. De lo contrario, podría exponer a la entidad directamente en el modelo de vista, al igual que

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

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

En este caso, la vista se unirá directamente a la entidad Employee y encontrar sus propiedades allí, así:

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

El uso de este método obtenemos la validación "libre", pero no es exactamente una aplicación limpia de MVVM.

Una tercera opción sería implementar INotifyDataErrorInfo y IDataErrorInfo mí mismo en las máquinas virtuales, pero esto parece una gran cantidad de código de plomería, teniendo en cuenta lo fácil que sería para mí utilizar la solución anterior y tener algo un poco menos "limpia" pero una diablos de mucho más fácil al final del día.

Así que supongo que mi pregunta es, ¿cuál de estos enfoques son apropiados en cada situación? ¿Hay un mejor enfoque que me falta?

En caso de que sea relevante estoy mirando el marco Caliburn.Micro MVVM, pero yo estaría interesado en ver las respuestas que se aplican de forma genérica.

¿Fue útil?

Solución

Estoy utilizando RIA con Caliburn.Micro y estoy bastante contento con mi solución para la validación del lado del cliente.

Lo que he hecho es poner un ValidationBaseViewModel entre Screen (proporcionado por Caliburn.Micro) y mi aplicación real VM (EmployeeViewModel en su caso). implementos ValidationBaseViewModel INotifyDataErrorInfo de manera que su código de plomería hablando solamente se escribe una vez. entonces añadir / quitar / notificarle de errores a través de ValidationBaseViewModel de una anulación de la PropertyChangedBase.NotifyOfPropertyChange (Caliburn.Micro) con el siguiente 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);
}

Esto es en realidad en otro VM (entre ValidationBaseViewModel y EmployeeViewModel) con la siguiente definición:

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

donde Entity es RIAs System.ServiceModel.DomainServices.Client.Entity y el miembro de la clase _editing es una instancia de este tipo TEdit que está siendo editado por la corriente VM.

En combinación con Caliburn corrutinas esto me permite hacer algunas cosas interesantes como la siguiente:

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

    ...
}

Otros consejos

Si no desea utilizar los recursos externos o marcos, entonces yo podría tener un ViewModelBase que implementan INotifyDataErrorInfo.

Esa clase tendrá ValidateProperty(string propertyName, object value) para validar una propiedad especíica, y el método Validate() para validar todo el objeto. utilizar Internamente, la Validator clase para devolver los ValidationResults.
Si utiliza reflector, puede ser bastante fácil para lograr imitando el proceso de validación en el Entity clase en sí a la ViewModelBase.

A pesar de que no es "libre", aunque todavía es relativamente barato.

Aquí hay un ejemplo de implementación de IDataErrorInfo. Aunque no se ha probado, le dará la 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));
  }
}

Puede utilizar una clase parcial para extender su entidad y añadir la validación de datos allí a través de IDataErrorInfo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top