Problems understanding use of MVVM when using WCF DTO's in a WPF smart client app
Asked Answered
G

4

14

Having very little experience in the area I am writing a WPF smart client app communicating to a WCF backend using MVVM and am really struggling to make the right decisions out of all the information out there. Which leads me to a set of questions that I hope can be resolved here by people more experienced in this area.

As an example, one of of the screens will allow for entering an order and adding order lines to the order.

What is used as the model?

On the WCF service I have the following simplified DTO:

public OrderDTO
{
   string orderDetails { get; set; }
   List<OrderLineDTO> OrderLines { get; set; }
}

public OrderLineDTO
{
   int customerId { get; set; }
   int productId { get; set; }
   double quantity { get; set; }
}

And a WCF service which has the following method:

public OrderService Order
{
    CreateOrderResponse CreateOrder(OrderDTO order) 
}

In my WPF smart client I then have a reference to the DTO but clearly it does not implement INotifyPropertyChanged as it is purely for transport.

Questions

Would the recommended approach be to convert these DTOs to a model that implements INotifyPropertyChanged using Automapper or similar? Or should the DTO be used as the model directly in the ViewModel?

Communicating between view models

Currently, I have an order view with 2 tabs (Order and OrderLines) with ViewModels OrderViewModel and OrderLineViewModel. On the order tab I have a ComboBox containing customer Ids and names. When I select a customer on the OrderView, I need to advise the OrderLineView that a customer has been selected so that the ComboBox only shows products belonging to that customer.

Questions

How would the OrderViewModel communicate to the OrderLineViewModel in this scenario?

Adding an order line and applying logic / business rules

As the server level application will be used by multiple clients e.g PCs, mobile devices.. I would like to ensure that all the business rules are applied in the server level application. As an example, when an order line is added. if it is of a certain product type it can only be added if the customer has a certain certification.

However, everything I have read about MVVM states that the model is what applies business rules and behaviour - all these examples have implemented the model on the client side. Ideally, I do not want to duplicate the same checks on both the client and the server so I was wondering how one would go about ensuring that this does not happen.

Questions

Do you allow the user to add an invalid line, send the request to the server, let the server apply the relevant rules and return the response? Or do you somehow apply the logic in the smart client app before sending the request to the server?

I really want to get better in all the areas I have outlined here and I thank you in advance for any responses.

Thanks

Alex

Edit: Thanks everyone for your contribution as it has helped me become a little more clear in terms of the best way forward. All the answers were good but I have decided to accept Uri's answer as it fits best with my thoughts at this stage. However, I am still not sure of the best way to handle the conversion from an Id of the DTO to a SelectedItem in the ItemsSource which is a list of ViewModels. I can see that a Converter might work but I am going to try and find another solution. Thanks Alex

Gigolo answered 22/1, 2012 at 22:8 Comment(0)
B
5

Here is my thinking about your questions:

Question: Would the recommended approach be to convert these DTOs to a model which implemented INotifyPropertyChanged using Automapper or similar? Or should the DTO be used as the model directly in the viewmodel?

Answer: My approach I like the most is containment. I agree with you DTO shouldn't have anything but getters and setters. Keep it as clean as possible, hence it shouldn't fire INotifyPropertyChanged. I also don't think the View should have direct access to object model (if for no other reason, you don't have the benefit of property changed). The disadvantage of my approach is some extra code in the ViewModel, but I think it worth it.

public class VmBase : INotifyPropertyChanged {

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void raise( string propName )
    {
        if( PropertyChanged ) {
            PropertyChanged( this, new PropertyChangedEventArgs(propName) );
        }
    }
}

public class OrderLineVm : VmBase {
    private OrderLineDTO orderLine;

    public OrderLineVm( OrderLineDTO ol ) {
        orderLine = ol;
    }

    public OrderLineVm( ) {
        orderLine = new OrderLineDTO();
    }

    int customerId {
        get { return orderLine.customerId; }
        set { orderLine.customerId=value; raise("customerId"); }
    }

    int productId {
        get { return orderLine.productId; }
        set { orderLine.productId=value; raise("productId"); }
    }

    double quantity {
       ...
    }
}

Through the miracle of garbage collection, the OrderLineDTO will be created only once (when it comes from the server) and live as long as it is needed. There are two public constructors: one with the DTO (typically, when objects coming from the server), and one created on the client.

For the OrderVm this is a little bit more complex, since you would like to have a ObservableCollection (vs. List) of OrderLineVm (vs. OrderLineDTO), so containment won't work. Also note that orderLines only has a getter (you add and remove order lines from it, but you don't change the entire list. Allocate it once during construction).

public class OrderVm : VmBase {
    private string _orderDetails;
    public string orderDetails {
        get { return _orderDetails;
        set { _orderDetails=value; raise("orderDetails"); }
    }

    private ObservableCollection<OrderLineVm> _orderLines;
    public ObservableCollection<OrderLineVm> orderLines { 
        get { return _orderLines; }
    }
}

Question: How would the OrderViewModel communicate to the OrderLineViewModel in this scenario?

Answer: If a communication is required, indeed you should do it the simplest way. Both View Model classes are at the same layers. The OrderVm references a list of OrderLineVm, and if you need a communication from OrderLineVm class to order, just keep a reference.

However, I would strongly argue that a communication isn't needed. Once the View is bound appropriately, I see no reason for such communication. The Mode property of the binding should be "two way", so everything changed in the UI will be changed in the View Model. Addition, Deletion to the list of order lines will reflect automatically on the View thanks to the notifications sent from the ObservableCollection.

Questions: Do you allow the user to add an invalid line send the request to the server let the server apply the relevant rules and return the response? Or do you somehow apply the logic in the smart client app before sending the request to the server?

Answer: There is nothing wrong having data validation on the client, in addition to the server. Avoid duplicate code - have a single assembly (probably the assembly that defines the DTO) that performs the validation, and deploy this assembly in the client. This way your application will be more responsive, and you will reduce workload on the server.

Obviously you need to do data validation on the server (for security reason, and for race conflicts). You must handle situation when the server returns errors even though validation on the client passed.

EDIT: (follow up on comment from Alex):

Showing a dropdown list: I think the source of your confusion is that there are actually two independent ItemsSource (and hence two separate data context): There is one list of order lines, and embedded within each order line is the list of ProductIDs, which are the items populated the combobox. It is only the SelectedItem that is a property of the ProductLine. Typically, the list of possible ProductID should be global to the application (or the order). You'll have the ProductIDs a property of the entire form, and give it a name (e.g. x:Key or x:Name). Then, in the ComboBox element just reference this list:

<ComboBox ItemsSource="{Binding Source={StaticResource ProductIDs}}"
          SelectedItem="{Binding Path=productId}"
          />
Bismarck answered 24/1, 2012 at 13:4 Comment(4)
@Udi, this is pretty much what I have done. However, I do get slightly unsure as to how to deal with something like a dropdown list. For example the OrderLineVM has a ProductId property and on my OrderLine form I want to display the ProductCode of that OrderLine. Does the OrderLineVM contain a list of Products or does that belong elsewhere? Thanks AlexGigolo
@lostinwpf: answer your question in the body of my replyBismarck
I am trying your suggestion. However, my combobox ItemsSource is bound to a property of ObservableCollection<ProductViewModel> on the main form. ProductViewModel contains Id, Code, Description. This means that the SelectedItem also needs to be of type ProductViewModel. However as far as the OrderLineViewModel is concerned I only need an int ProductId. Hence, when I load from disk I need to convert the ProductId into the correct item in the ComboBox and for saving the reverse. Thanks for your help so far. AlexGigolo
In this case, you can simply implement a converter (and object derived from IValueConverter) from int to ProductViewModel. Then, define the converter as a resource, and reference the resource in the SelectedItem attribute: SelectedItem = {Binding Path=productId,Converter={StaticResource cnv}}Bismarck
C
1

To answer your questions in turn ...

1) If you do not need your properties to notify the UI when they change, then there is no need to use INotifyPropertyChanged - and in my opinion you can bind the Model directly to the View. There is no need to add an extra layer if it does not add any extra functionality. However, in most applications you will want to change model object state via the UI. In this case, you will need to add View Model objects that implement INotifyPropertyChanged. You can either make a view model that adapts the model, i.e. delegating properties to the underlying model, or copy the model object state to an equivalent view model.

In order to avoid writing lots of rather similar code, i.e. the same domain object represented as a model object and view model object, I try to use code-generation where possible. I like using XML to describe my model, and T4 templates for codegen.

2) How should OrderViewModel communicate to the OrderLineViewModel? directly! The concepts sound quite closely coupled, I would guess that an order has multiple order lines? In this case just have each view model reference the other. No need for fancy mediators if the two are closely coupled within your domain.

3) Good question! I agree that the server should apply validation. Whether you duplicate some of this validation in the client depends on your requirements. If your communication with the server is fast and frequent, you might be able to provide a good user experience by communicating with the server as the user edits the orders and providing validation as they proceed from field to field. However, in many cases this is not practical. It is quite common to apply simple validation within the client application, but allow the server to do the more complex checks, for example checking for uniqueness etc...

Hope that helps.

Cubical answered 22/1, 2012 at 22:20 Comment(1)
Thanks for your answers, they are extremely useful. I am still not quite sure with regards to 1). Does that mean I would need to have something like public class OrderViewModel{ Model = new OrderModel(new OrderDTO()) } and the OrderModel and in the view you bind to Model.OrderDetails directly? Thanks AlexGigolo
B
0

I have 2 years of experience with building "rich clients" (in WPF).

In my WPF smart client I then have a reference to the DTO but clearly it does not implement INotifyPropertyChanged as it is purely for transport.

Wrong
WCF will automatically implement INPC on every DTO, by default.
Best would be to use the DTOs as ViewModels for simple views, and for more complex views, use the composition "pattern".

Communicating between view models

The "best" practice (read: what pretty much everybody does) is to use a weak event pattern, to keep things loosely coupled. The most renowned one being IEventAggregator from the PRISM library, but there are several implementations out there.

Adding an order line and applying logic / business rules

Think of the client the same as a webpage: do not trust it. It is .NET code, and we all know how easy it is to hack into.
Which is why, you should have the security checks implemented on your WCF service.

HTH,

Bab.

Biconcave answered 24/1, 2012 at 13:38 Comment(0)
C
0

I believe the real question is how true to the MVVM pattern would you like to be?

The idea behind MVVM, as well as similar patterns like MVC and MVP, is separation of concerns. While I, too, have toiled with this topic, I took a closer look at what the pattern is trying to accomplish and the choices became easier.

With MVVM, you have three concerns: the View (V), the Model (M) and the ViewModel (VM). Seems pretty obvious, right? But ask yourself what each is really concerned about and what happens if we start mixing concerns - just as happens when we mix concerns elsewhere. Our code becomes harder to change.

With that in mind, consider the case where you let the UI creep into your ViewModel by exposing a property that uses a UI type. This is common when dealing with dialogs (a major source of headaches in MVVM). Let's say you are developing your application using a 3rd party set of controls and the UI type is one of theirs. Now you have to make multiple changes if you swap control sets instead of just changing the UI markup (or have a designer do it).

(This is fresh in my mind because I just undertook such an endeavor and the true MVVM apps were a snap to reskin while the others took 10-25 times as long to convert!)

This same scenario affects the 'back-end' of the pattern as well.

The purpose of the Model is to transport data to/from whatever persistance mechanism you are using with your application. This could be a web service, database, text file, etc. Just because WCF adds capabilities such as INotifyPropertyChanged doesn't mean that using them is recommended. Remember that Microsoft is in the business of developing tools. In order to sell these tools, they need to work in a variety of situations and levels. RIA Services, for instance, is great for quick-and-dirty applications but breaksdown quickly when applied to real-world solutions (in my experience at least).

So what happens if you do use the containment model and have all of your properties delegate to the Model object held in state within your ViewModel and the nature of the Model changes? Or the Model doesn't do everything you need. Fact is that the ViewModel is supposed to an adapter that gives the UI what it needs to operate. There should rarely be a 1:1 relationship with the Model, but I know it happens.

What happens if in 6 months you decide to go with a REST service instead of WCF? Now you don't have the INPC support in your Model becuase you aren't dealing with auto-generated proxy classes. While not as tangible as the UI changes, the same ideas apply here and it is why the pattern(s) separate the Model.

My advice is to go with your first instinct and use AutoMapper to map the data contained in your Model object into your ViewModel and vice versa. AutoMapper makes it very easy to handle the impedence mismatch problems you may face and give you a single place to make changes should one side or the other of the contract change.

2

What you have is an object model and in that case having events, callbacks, etc. are perfectly legitimate. I'd say that your OrderViewModel contains a collection of OrderLineViewModel objects. The child objects can contain a reference to the parent (OrderViewModel) and pull the selected Customer from there.

3

First, it is the ViewModel, not the Model, that implements business rules and validation. Second, such rules are there to provide the user with an interactive experience. Regardless what rules you put into the ViewModel, you should always perform integrity checks on the server to make sure the user is allowed to perform the requested operation and that the data is valid for persistence.

As for the question of round-tripping business rules, I would say no. I try to enforce as many business rules in the client application as make sense. One, it improves the user's experience, and it reduces the network traffic required by the client. One rule of thumb I follow is that I never allow the user to persist an invalid object. Note: inaccurate or incomplete data is not the same as invalid. Invalid data causes exceptions.

Chase answered 25/1, 2012 at 19:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.