Based on this answer: MVVM implementation using C# and XAML, I understand that my ViewModel is some kind of a wrapper for my Model.
Being a fan of DI and SOLID principles, I have made my ViewModel Model-dependent.
CustomerDetailViewModel
public class CustomerDetailViewModel : ViewModel<Customer> {
public CustomerDetailViewModel(Customer model) : base(model) { }
public bool AllRequiredInformationHasBeenProvided {
get {
return !string.IsNullOrWhiteSpace(Email)
&& !string.IsNullOrWhiteSpace(GivenName)
&& !string.IsNullOrWhiteSpace(Surname);
}
}
public string Email { get { return Model.Email; } set { setEmail(value); } }
public string GivenName { get { return Model.GivenName; } set { setGivenName(value); } }
public string Surname { get { return Model.Surname; } set { setSurname(value); } }
private void setEmail(string email) {
Model.Email = email;
RaisePropertyChangedFor(m => m.Email);
}
private void setGivenName(string givenName) {
Model.GivenName = givenName;
RaisePropertyChangedFor(m => m.GivenName);
}
private void setSurname(string surname) {
Model.Surname= surname;
RaisePropertyChangedFor(m => m.Surname);
}
}
CustomerManagementViewModel
public class CustomerManagementViewModel : ViewModel<IList<Customer>>, {
public CustomerManagementViewModel(IList<Customer> model) : base(model) { }
public Customer GetCurrent(int index) { return Model.ElementAt(index); }
}
ViewModel
public abstract class ViewModel<M> : IViewModel<M> where M : class {
public ViewModel(M model) { Model = model; }
public virtual M Model { get { return model; } set { setModel(value); }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual RaisePropertyChangedFor(Expression<Func<M, object>> propertyExpression) {
var expression = (MemberExpression)propertyExpression.Body;
if (expressionIsNoMemberExpression(expression)) return;
raisePropertyChangedFor(expression.Member.Name);
}
private void setModel(M value) {
if (value == null) throw new ArgumentNullException("Model");
model = value;
raisePropertyChangedFor("Model");
}
private M model;
}
IViewModel
public interface IViewModel<M> : INotifyPropertyChanged where M : class {
M Model { get; set; }
}
Customer
public class Customer {
public string Email { get; set; }
public string GivenName { get { return givenName; } set { setGivenName(value); } }
public string Surname { get { return surname; } set { setSurname(value); } }
private void setGivenName(string givenName) {
if (givenName == null) throw new ArgumentNullException("GivenName");
if (string.IsNullOrWhiteSpace(givenName)) throw new ArgumentException("GivenName");
this.givenName = givenName;
}
private void setSurname(string surname) {
if (surname == null) throw new ArgumentNullException("Surname");
if (string.IsNullOrWhiteSpace(surname)) throw new ArgumentException("Surname");
this.surname = surname;
}
private string givenName;
private string surname;
}
Obviously, the model might change along the way so that the IViewModel.Model
property is changeable.
Using MVP-VM, it belongs to the Presenter
to change the Model
.
CustomerDetailPresenter
public class CustomerDetailPresenter
: Presenter<ICustomerDetailView<CustomerDetailViewModel>>
, ICustomerDetailUiHandler {
public CustomerDetailPresenter(ICustomerDetailView view)
: base(view) {
View.Handler = this;
}
public void ShowDetailsFor(Customer customer) {
View.ViewModel.Model = customer;
View.Show();
}
}
Then, my CompositionRoot class binds it altogether through convention binding.
CompositionRoot
public class CompositionRoot {
private CompositionRoot() { }
public static ComposeObjectGraph() {
var projectAssemblies =
GetProjectAssemblies(ProjectAssemblyKey, TestProjectAssemblyKey);
BindViewsFor(project, ViewKey, FormKey);
BindViewModelsFor(project, ViewModelKey);
}
private static void BindViewsFor(IEnumerable<Assembly> projectAssemblies
, string viewsKeyword
, string formsKeyword) {
kernel.Bind(services => services
From(projectAssemblies)
.SelectAllClasses()
.EndingWith(formsKeyword)
.BindSelect((type, baseType) => type
.GetInterfaces()
.Where(iface => iface.Name.EndsWith(viewsKeyword)
&& !iface.Name.Equals(string.Concat("I", viewsKeyword)))));
}
private static void BindViewModelsFor(IEnumerable<Assembly> projectAssemblies
, viewModelsKeyword) {
kernel.Bind(services => services
.From(projectAssemblies)
.SelectAllClasses()
.EndingWith(viewMoelsKeyword)
.BindToSelf());
}
private const string FormKey = "Form";
private const string ProjectAssemblyKey = "MyProject";
private const string TestProjectAssemblyKey = "Tests";
private const string ViewKey = "View";
private const string ViewModelKey = "ViewModel";
private static readonly IKernel kernel = new StandardKernel();
}
I'd like to have some feedback on how I can simplify my code, or if I am on the right track.
set
is a method (and it removes the ability to use the[CallerMemberName]
attribute in your property change notification!). It is also bad practice to throw exceptions in setters, and very odd to put all your members at the bottom of your classes. \$\endgroup\$PropertyChanged
event would result in a too long line, and that writing the the full setter inside the property definition would no longer allow me to group the properties together, I prefered this approach. As for the commas on new lines, I agree, this does very SQL, and I find it practical when comes the time to comment out. \$\endgroup\$