MVVM & business logic Layer

432 Views Asked by At

I have a problem with MVVM pattern and binding collection. My ViewModel provides a collection to the View but to get this collection I use this:

public BindingList<Car> BindingListCars { get; set; }

public CarsVm()
{
    BindingListVoiture = carServices.ListCars;
}

When I bind my View on this List it's as if I bind directly my View on the Model because they use the same reference. So when I edit one property of a Car, the model is directly edited without using carServices validation method.

What is the best solution to correct this problem ?

Do I have to expose a copy of my Model to my View to not edit directly my Model from the View?

Do I have to use BindingList in my Model and subsribe to ListChanged in my carServices to validate each change?

2

There are 2 best solutions below

0
mm8 On BEST ANSWER

You should either perform the validation directly in the Car class itself or expose wrapper objects instead of exposing the "real" Car objects to the view.

The following sample code should give you the idea about what I mean:

//the "pure" model class:
public class Car
{
    public string Model { get; set; }
}


public class CarService
{
    public List<CarWrapper> ListCar()
    {
        List<Car> cars = new List<Car>(); //get your Car objects...

        return cars.Select(c => new CarWrapper(c, this)).ToList();
    }

    public bool Validate()
    {
        //
        return true;
    }
}

public class CarWrapper
{
    private readonly Car _model;
    CarService _service;
    public CarWrapper(Car model, CarService service)
    {
        _model = model;
        _service = service;
    }

    //create a wrapper property for each property of the  Car model:
    public string Model
    {
        get { return _model.Model; }
        set
        {
            if(_service.Validate())
                _model.Model = value;
        }
    }
}

Obviously if you expose an IEnumerable<Car> from your view model for the view to bind, you are effectively bypassing any validation that is dedined outside of the Car class if the view is able to set any properties of the Car class.

1
Kris On

Thanks for your answer mm8,

With this solution I have to create one wrapper per class which need outside validation. It add work and during refactoring we have to edit the class and the Wrapper.

What do you think about this solution :

  1. I put my list of vehicle in a binding list
  2. My service subscribe to ListChanged event of this list
  3. My service implement INotifyDataErrorInfo
  4. For each modification in this list validation is executed
  5. If there is an error ErrorsChanged event is raised The view model subsribe to this event and retrieve error Data.
  6. The view model subsribe to this event and retrieve error Data.

For example :

My services implementation :

public class VehicleServices : INotifyDataErrorInfo
{

     private BindingList<Vehicle> _bindingListCar
     public BindingList<Vehicle> BindingListCar 
     { 
         get return _bindingListCar;
     }

     private readonly Dictionary<string, ICollection<string>>
        _validationErrors = new Dictionary<string, ICollection<string>>();

     //INotifyDataErrorInfo implementation

     public IEnumerable GetErrors(string propertyName)
     public bool HasErrors
     private void RaiseErrorsChanged(string propertyName)

     public VehicleServices()
     {
         _bindingListCar = GetVehicles();
         _bindingListCar.ListChanged += BindingListVehicleChanged;
     }

     private void BindingListVehicleChanged(object sender, ListChangedEventArgs e)
     {
         //Only modification is managed
         if (e.ListChangedType != ListChangedType.ItemChanged) return;
         switch(e.PropertyDescriptor.Name)

         //Validate each property

         //if there is ErrorsChanged is raised
     }
 }

And my ViewModel

 public class CarVm : BindableBase
 {

      private ICollection<string> _errors;

      public ICollection<string> Error
      {
         get
         {
             return _errors;
         }
         set
         {
             SetProperty(ref _errors, value);
         }
      }
      private VehicleServices _carServices;

      public BindingList<Vehicle> BindingListCar { get; set; }

      public CarVm(VehicleServices carServices)
      {
           _carServices = carServices;
           BindingListCar = new BindingList<Vehicle>(_carServices.BindingListCar);
           _carServices.ErrorsChanged += _carServices_ErrorsChanged;
       }

       private void _carServices_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
       {
           Error = _carServices.ValidationErrors[e.PropertyName];
       }
 }

Do you think this is a good practice ?