3
\$\begingroup\$

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.

\$\endgroup\$
6
  • \$\begingroup\$ Some small things: You do your null prevention on properties twice, once in the viewmodel, once in it's model. That is probably not needed. Either way, creating set methods is very odd practice since a property's 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\$
    – Magus
    Commented Oct 1, 2014 at 22:03
  • \$\begingroup\$ As for throwing exceptions in setter, I agree. I just got caught on this. As for pulling all members at the bottom, this comes from a Clean Code class I have attended where Robert C. Martin says one doesn't need the details. The first thing one does when opening a class file, is scrolling down to the meat, where processes occur. Properties, methods, not fields. It is only once one has got the main idea of a property or a method that one needs to dig into the details. So to avoid scrolling down unnecessarily, keep significant stuff at the top, so one has a first look right where it counts. \$\endgroup\$ Commented Oct 6, 2014 at 14:07
  • \$\begingroup\$ As for null checking, I only do it in the Model. In the ViewModel, I check for null when setting the Model property which only exists in hte ViewModel. \$\endgroup\$ Commented Oct 6, 2014 at 14:09
  • \$\begingroup\$ Okay, I can definitely see the Clean Code influences here, but I stand by the extra set methods being useless bloat. If you're just going to set the value and raise property changed, that is expected to be in the property. Commas at the beginnings of new lines in argument lists is also an odd choice. Very SQL. \$\endgroup\$
    – Magus
    Commented Oct 6, 2014 at 14:35
  • \$\begingroup\$ Apart setters are also due to a Clean Code influence. One gains of seeing all properties grouped together as the definition of the object. It is only when one doubts about a property behaviour that one shall dig in the details. Because setting the values and raise the 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\$ Commented Oct 6, 2014 at 15:12

1 Answer 1

2
\$\begingroup\$

A few superficial things:

  1. Your view models should be implementing INotifyPropertyChanged. This will make it more obvious that your models will raise property change events. Also the WPF binding framework checks if a class implements this interface and makes use of it if it does so.

  2. Standard naming convention for C# is PascalCase for method names (private or not usually makes no difference)

\$\endgroup\$
3
  • \$\begingroup\$ +1 I actually implement INotifyPropertyChanged, thanks for pointing this out - please see my update (IViewModel). As for the naming convention, you're totally right. Besides, it is my own preference. =) \$\endgroup\$ Commented Oct 1, 2014 at 20:08
  • 1
    \$\begingroup\$ @WillMarcouiller: Writing code that looks like Java in C# is bound to confuse people. Generally speaking, you should write code that is consistent with the platform, and in C# that means PascalCase methods and properties, and opening braces on new lines. The last thing you want is to annoy other developers. \$\endgroup\$
    – Magus
    Commented Oct 1, 2014 at 21:55
  • \$\begingroup\$ That said, what about my ViewModel design pattern, is it implemented correctly, is there any change to bring forward or the like, any improvement? \$\endgroup\$ Commented Oct 2, 2014 at 13:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.