Why it's not good to expose Model through View

2019-04-06 22:04发布

问题:

As far as I develop WPF application with MVVM, I never expose model through viewmodel's public property. Anyway, after I just come to world of Silverlight and WCF RIA I found new method to achieve data validation, that's being said by Required attribute. (there are others attributes as well)

This time instead of create validation logic inside viewmodel, I can do almost of validation logic inside the model itself.

public class TestUserPM {
    [Key]
    public int ID { get; set; }

    [Required]
    public string FirstName { get; set; }

    [Required]
    public string Email { get; set; }
}

After that, all I need in ViewModel is expose public property of TestUserPM's type and let View binding directly to the model.

I think this is not elegant solution but it can work and it has no need to create tedious validation inside viewmodel property.

Are there any down-sides of this method?

Update 1

I just found 1 down-side, may be it has solution out there. I want to binding Button's Command, for example, button save to Command in ViewModel but this button can execute if and only if all informations is valid. From my experience with WPF MVVM which I've helper class I'll call OnCanExecuteChanged() inside public string this[string columnName] of IDataErrorInfo.

How can I handle with this kind of requirement?

回答1:

I expose Model through ViewModel all the time, just to keep things simple and to not repeat myself (DRY).

The only thing to avoid the need to add properties in the model to adapt to the UI (as Benjamin notes), is to keep the model as a propery of the viewModel, so you can add properties to the viewModel, without messing the model.

ie: The ViewModel is the DataContext and it has a Model property returning the model

<TextBlock Text={Binding Path=Model.Name} />
<TextBlock Text={Binding Path=Model.Address} />


回答2:

The major problem I see is that your model (which could be a business object), must adapt to the UI. It could impact a lot other UI or business layer.

You could imagine several UI with different validations level on the same object. This is not possible with your example.



回答3:

The problem is as the others have said that you can't adapt to the view. However I often want to not repeat myself - as Eduardo also said with the exposure of the model to bind to. I find that solution to be a bit non-consistent when you want to alter a value for the view - then some would be binding "Model.Name" and others just "Name" for the altered property - and some scenarios just won't work that way.

My solution is to create a ViewModelProxy class where you can forward properties from another class and get property notification for free. It is done quite easily by deriving DynamicObject (I left out code notification, IDataerror etc). The cool thing is that all properties from Data is forwarded - if you implement/override a property that will be bound to - so in this way you don't have to repeat code and you have a sensible thing to use DynamicObject to.

public class ViewModelProxy<T> : DynamicObject, INotifyPropertyChanged

public T Data { get; set; }

private PropertyInfo[] objectProperties;
private PropertyInfo[] ObjectProperties
{
  get
  {
    if (objectProperties == null)
      objectProperties = typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
    return objectProperties;
  }
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
  var pinfo = ObjectProperties.FirstOrDefault((pi) => pi.Name == binder.Name);

  if (pinfo != null)
  {
    result = Data != null ? pinfo.GetValue(Data, null) : null;
    return true;
  }
  else
    return base.TryGetMember(binder, out result);
}


public override bool TrySetMember(SetMemberBinder binder, object value)
{
  var pinfo = ObjectProperties.FirstOrDefault((pi) => pi.Name == binder.Name);

  if (pinfo != null)
  {
    if (Data != null)
      pinfo.SetValue(Data, value, null);
    RaisePropertyChanged(binder.Name);
    return true;
  }
  else
    return base.TrySetMember(binder, value);
}

}



回答4:

You are correct to use validation via annotation in Silverlight, rather than fill a ViewModel with code.

In the case of any special validation rules you can create custom validators and decorate the members with [CustomValidation...], which again will keep the validation away from the ViewModel.

In any case, the business rules you are describing are generally shared across views. Specific validation, for special case views, can be added in controllers.

As a general point: a ViewModel is a relatively dumb object to hold values for a view. If you start finding you are adding logic, event handlers and other, you should probably look at introducing a controller object... even though there is no C in MVVM :)



回答5:

To expose Model in ViewModel, you need to prepare your Model to Adapt the View, thus you should pollute your Model with view specific code:

  • Notify Property Change support
  • Data Error Info support (Validation inside)
  • Editable Support.

It is necessary to not to pollute your Model with other thing, perfect model should have minimum dependencies with other library, thus it can be shared in the same application with different platform (asp.net, mobile, mono, winform, wpf etc). Or for upgrade/downgrade.

anyway..

I made a small WPF application (not yet completed), I Used uNhAddins, NHibernate, Castle to built it. i don't say it was the best solution but i'm really happy to work with it.. Check out the code first then see the separation of Entity, Validation Logic, Business Logic. The assembly separation was design to minimize dependency between core app, UI, and application logic.