Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF, MVVM, Business Object with Validation don't match very good

For my WPF-Application I decided for MVVM. Here is my Concept how I will implement this pattern.

  • My Models (Business Objects) are responsible for the validation (that's a must for me).
  • ViewModels are responsible to wrap my Model for a friendly User-Interaction and some security aspects.

My first question was about wrap or not wrap my Model in ViewModel.

  • When I don't wrap my Model in ViewModel and expose the Model directly to the view – then I don't understand why I need a ViewModel (it seems sensless)
  • ViewModel should wrap the Model for various reasons:

    1. I don't like direct binding to the strongly typed properties in Model (DateTime, int, …), because when I do this => WPF takes control over my validation for this types. That's really bad, because when the user write ‘aaaa’ in a Datepicker, my Model is valid (my model never know about that, because WPF takes the control over strongly typed properties) and the Save-Button is enable – that's really wrong.

    2. I don't expose all properties of my Model to the view, my ViewModel should protect my Model (I have some properties, that should have at presentation layer only getter and no setter)

My Decision is that ViewModel should definitely wrap the Model. So the ViewModel implements INotifyPropertyChanged.

But now I have problem with the business validation.

When I take the nice IDataErrorInfo, then I have the whole business rules in the ViewModel, that's breaks my concept. The business rules should definitely be in the model.

Example: When user choose Type A, then Field 1, and Field 2 are mandatory. When user choose Type B, then Field 3 is mandatory – this field should be marked as red and the Save-Button is disable when is it not valid. Also more heavy things like free/occupied DateTime-Ranges.

It's definitely bad, when I do this things in ViewModel, because most things are business part.

So how I can achieve this?

At the Moment I have this workaround:

All ValidationRules are in the Model as simple Methods, e.g.

public string ValidateBirthday(string birthay)
{
    if (...)
    {
        return "Birthday should be…";
    }
    return string.Empty;
}

In my ViewModel I implemented the IDataErrorInfo, and redirect to my Model-Validation like this:

public string this[string columnName]
{
    get
    {
        switch (columnName)
        {
            case "Birthday":
                return Model.ValidateBirthday(Birthday);
            case "XXX":
                return Model.ValidateXXX(XXX);
            case "YYY":
                return Model.ValidateYYY(YYY);
            break;
        }
    }
}

I never see something like this (the redirect to Model) in an example, so I'm very doubtful about my implementation.

Is my workaround OK or do you see any problems about this?

I try to give more information about what I mean…

I know about the implementation INotifyPropertyChanged and IDataErrorInfo in the Model.

This works good with direct Binding from View to Model.

  1. Direct Binding from View to Model:

    public class PersonViewModel : INotifyPropertyChanged { private Person _personModel; public Person PersonModel { get { return _personModel; } set { if (_personModel != value) { _personModel = value; NotifyPropertyChanged(); } } }

    public PersonViewModel(Person person)
    {
        PersonModel = person;
    }
    …
    

    }

View:

<DatePicker Text="{Binding PersonModel.Birthday}"/>

The big disadvantage is: WPF takes the Control over all strong typed Property.

Example: The user typed 07/20/2008 in the datepicker, so the PersonModel will be informed and PersonModel can check this, when OK, then PersonModel is valid => SaveButton is enable.

Now the user typed 'aaa' in the datepicker, WPF takes the control over this validation, because it's a binding to a strongly typed property (DateTime). PersonModel will not be informed about that, so the PersonModel is still valid => SaveButton is enable!

So for that 'problem' I need the ViewModel correctly.

  1. ViewModel wrap the Model like this:

    public class PersonViewModel : INotifyPropertyChanged { private Person _personModel;

    public string Birthday
    {
        get
        {
            if (_personModel. Birthday!= null)
            {
                return ((DateTime) _personModel. Birthday).ToShortDateString();
            }
            else
            {
                return String.Empty;
            }
        }
        set
        {
            if (_personModel. Birthday.ToString() != value)
            {
                DateTime dateValue;
                if (DateTime.TryParse(value, out dateValue))
                {
                    _personModel.Birthday = dateValue;
                    …
                }
                else
                {
                    …
                }
            }
        }    
    }
    
    public PersonViewModel(Person person)
    {
        _personModel = person;
    }
    …
    

    }

Now I don't bind the Model direct from View. I bind the Properties from ViewModel that wrapped the Model.

<DatePicker Text="{Binding Birthday}"/>

The big advantage is: now I have the full control about what the user types in the fields. When the user types strings like 'aaa' in Datepicker I can catch this => set the state to invalid and SaveButton is disabled.

That's one reason, why I don't take the direct binding from View to Model. Other reason are readonly Property. In Model I have get and set on every Property, but for security Issue I won't offer all Properties from Model with get and set. So this can also solved by ViewModel by wrapping this Properties with only get. With direct Binding you can't do all this things.

My point is, I will definitely wrap all Properties from my Model in ViewModel, but how can I use the nice IDataErrorInfo in Model (It works only with direct Binding)?

like image 679
user3607263 Avatar asked Oct 21 '22 09:10

user3607263


1 Answers

You are mixing two concepts here: Bussiness objects and validation.

Almost every system nowadays uses the client-server architecture, even if its an standalone application.

In such scenario you have two validation locations:

  • The client is responsible for ensuring that the data entered is valid before sending anything to the server in order to enhance user experience and avoid server overloads and security issues.

  • The server is responsible for the verification of the incoming data, to avoid malformed, misformatted data and security issues.

Also:

  • The Bussiness Objects (BO) are the classes used by server, tipically represeting the data base.

  • The Data Transfer Objects (DTO) are the classes that the server sends to the client.

  • The ViewModels are both the backend code for the UI and the wrappers for the DTOs.

Your model objects shouldn't have any logic, since you will spoil them with some code that at some point you will need to reuse.

As exposed here, you should separate that validation logic into services that only know about that object and how to validate them. This way, you can use validation services from the UI.

Your Save button should react only on UI changes, and you will only get those from a ViewModel.

Basically, you will be applying SOLID principles here: Each layer has very clear responsibilities (model -> data, services -> validation, dto -> data ready for the client, viewmodels -> UI interaction). All the code will be easy to work with, easy to extend and easy to refactor.

Edit

1st and 2nd questions: UI only validates the input: no random characters in number fields, no sql characters in text fields, Date has correct format, etc.

Thinks like "if this then that" should be handled by the backend, as you describe:

  1. Save is clicked.
  2. UI data is valid.
  3. DTO sent to backend.
  4. Backend analizes DTO and it is not valid.
  5. Backend sends back the errors found.
  6. UI shows the errors found.

3rd question: That looks right to me.

4th question: DTO is just a concept, you can use a real backend server that communicates via WCF, or you can just have a bunch of classes that act as a service but are called in the same application domain (like any other project reference). In either case you can choose what data is being sent and received.

You should start developing in that direction and then see what better fits you.

like image 156
JoanComasFdz Avatar answered Oct 23 '22 03:10

JoanComasFdz