Monday, November 22, 2010

ClientUI Sample: MVVM for WPF & Silverlight

We just published a new sample application in partnership with Intersoft Solutions. Here’s what excites me about it:

  • Two solutions, in WPF and Silverlight, share the same source code and XAML
  • Demonstrates MVVM and Repository patterns
  • The views are Blendable and rely on design data delivered by ViewModels
  • It looks great
  • It’s concise and easy to read
  • Includes a fifty page walkthrough that explains how it is put together and why
  • A terrific mash-up of DevForce and some great Intersoft UI controls

Learn more about our evolving series of joint samples.

Download the code

See what Jimmy Petrus of Intersoft says about it

The Intersoft “ClientUI Sample” was the inspiration. It’s a lovely demo of a Forms-Over-Data application – a “Contact Editor” built with Intersoft controls. The UI presents two views: one lists all Contacts; the second is an overlay dialog for editing the currently selected contact and address. A “contact” is a person with an address.

ClientUIMainViewClientUIEditorView

The application appears and behaves the same in both WPF and Silverlight flavors. Intersoft’s APIs are identical for both WPF and Silverlight. They built both versions of the sample using the same code and xaml files, no changes required. They simply compiled separately against the WPF and Silverlight libraries. We thought that was pretty neat (and no mean feat).

Our two companies teamed-up to extend Intersoft’s “UI-only” sample with a DevForce entity model and our distributed persistence components so the editor could retrieve and save data from a database.

The DevForce APIs are the same for all .NET client platforms (ASP.NET, Windows Forms, WPF, Silverlight). As with Intersoft products, you write it once compile for the target libraries.

After integration with DevForce, the ClientUI sample became an end-to-end Contact editor that can save … delivered in WPF and Silverlight, built on a single code base.

Intersoft’s original ClientUI sample followed an MVVM design. That made our DevForce integration job easy. We hardly touched the views. We replaced the original, hand-coded model with a DevForce generated model that can exchange data with a database over the internet. We introduced a Repository and EntityManagerFactory, massaged the ViewModels, added a dash of configuration … and voila!

The conversion process is covered in detail - with digressions on patterns used and design choices - in a fifty page PDF that is part of the downloaded zip file.

We have our ideas for future development. We’ll surely demonstrate how to “sandbox” the editing dialog so that changes propagate to the main view only after they are saved. I’d like to add automated testing and maybe we’ll dare to use an IoC container. I’m sure you’ll tell us what you’d like to see.

Enjoy!

Prerequisites

The sample assumes you’ve installed IdeaBlade’s DevForce (6.0.7 or later, free version is fine) and Intersoft’s “ClientUI 2010” (v3.0.5000.11 or later, trial version is fine).

14 comments:

Anonymous said...

"Download it here" link is broken. Above link is fine.

Ward Bell said...

Thanks! My bad; fixed the link

Thomas said...

Thanks for the great demo. I too have been working on integrating the two products and just completed integrating Devforce 2010 into Intersoft’s Business Application demo. These two products together are unstoppable!

João (John) said...

Ward,
awesome sample code!
Simple, objective, direct to the point.
Thank you for sharing it!

Anonymous said...

Ward, I'm running into a problem. My db uses identities. But at design time, I try to add my dummy objects, and I get an error because of a missing ID Generator. Can I add an ID Generator at design time, or is there another way around this?

Ward Bell said...

@anon - I'll bet you ADD to manager instead of ATTACH.

Short answer: specify the ids for design purposes when you create the design entities and then call
manager.AttachEntity(newEntity) [or
manager.AttachEntities(newEntities) if you have a collection of them.]

I get the same reaction you describe if I ADD to manager because DevForce, thinking that this is a new entity, wants to create the temporary ids for you and it needs to find an IdGenerator for that purpose.

These are design entities. You generally don't care whether they appear to be new or pre-existing entities while laying out the screen. Using AttachEntity to simulate entities that already exist in the database is fine.

The entities in ClientUI use Guids so I didn't have to think about Add versus Attach. However, in other samples, such a the BookShelf, the entities us autoincrement Ids and we must deal with this issue.

I don't think you can replace the autoincrement IdGenerator with your own (although maybe you can). I never bothered to try.

If you ever DID have to design for an added entity, you could set the state of an entity to ADDED after you've attached it:
WellKnownDesignContact.EntityAspect.SetAdded();

This by-passes the IdGeneration business. Obviously it would fail in production.

But, seriously, use AttachEntity.

Ward Bell said...

@anon - I just updated the sample (ContactFactoryFromXML and EntityManagerFactory) to demonstrate the distinction between Add and Attach to manager.

To repeat, the distinction was irrelevant in the Contact application sample which used Guid ids. This change provides better guidance for the great number of you who will use autoincrement ids.

Jignesh said...

Hi Ward & Jimmy,

Thanks for the great demo. It helps understanding MVVM as well as DevForce and ClientUI technologies. Minor observation though , If you open and edit contact details , changes are getting reflected back in related contact entry in Conatact list as you modify the field and tab though next field in contact detail page even before user saves data.

I understand its because databinding and both views are referencing the same entity.

Is it acceptable behavior from end-user perspective ? if not ( which I believe is the case ) what is the work around ?

- Jignesh

Ward Bell said...

@Jignesh - Excellent question. I've seen strong feelings on both sides.

Some people really like the simultaneous update; you see it in the BookShelf / BookClub example seen at PDC 2010 and the recent Silverlight Firestarter.

Others feel as you do that changes should not propagate to the rest of the UI until they are committed by a save. They want isolation of the entity/aggregate while it is being edited.

I call an editor that isolates pending changes a "Sandbox Editor". In such an editor, the entities involved are held in a separate a separate repository with its own DevForce EntityManager.

That's easy to do in DevForce. In fact, very soon we will release a version of this ClientUI sample that uses a sandbox editor.

We're releasing DevForce 6.0.7 this week. The ClientUI sample you downloaded will be reissued for DF 607 and, if you look closely, you may notice that the ContactEditor has been refactored slightly to prepare for an easy switch to the forthcoming sandbox editor.

That refactoring is actually closer to what ClientUI would have looked like the first time ... had I been sufficiently farsighted and architecturally strenuous.

So ... pick up a fresh copy of ClientUI at the end of this week ... and stay tuned for the sandbox editor that will follow shortly thereafter.

Jimmy said...

@Jignesh: Glad to hear our sample worked out for you, particularly on the architectural bits like MVVM.

I agree with you that editing should be isolated from the "connected" views, this is an expected behavior in line-of-business applications.

In fact, we already discussed about this in the earlier phase of our joint sample development. Ward agrees that this should be supported in the DF bits to make the process straightforward and easy.

As Ward suggested, DF 6.0.7 includes the "sandbox editing" support. We will surely blog about the changes when the updated sample arrives.

mark88 said...

Looking forward to the updated sample, Ward.

It's nice to see so many good examples these days. The attached PDF is a very welcome addition. Many times we get samples and it can be difficult to understand why you would do something a certain way.

I have been working on a sample Northwind application using Caliburn.Micro and Autofac. Coupled with IoC, DevForce makes it very easy to implement sandbox editing.

In my application, the flow is like this:

View <-> ViewModel <-> Repository <-> PersistenceContext <-> EntityManager

When the PersistenceContext saves/deletes, it broadcasts an event to other instances of PersistenceContext that choose to receive it. The entities then get imported or removed from each EntityManager.

Is this a good way of doing this, or do you have a better method?

Ward Bell said...

@jignesh The "sandbox editor" version of the sample has been published. I blogged briefly about it here where the links will lead you to it.

A small clarification: we've been building sandbox editors in DevForce for years. The version of DevForce was never an issue.

That said, the latest release of the Contacts application does depend in a few ways on some small changes released in DF 2010, v.5.0.7 so please do upgrade to that version.

Ward Bell said...

@mark88 I believe you are on a path to success. What you call the "PersistenceContext" is what we tend to call the "EntityManagerProvider" but a rose by any name ...

In this sample I have not (yet) shown the post-save "eventing" approach to which you allude because I haven't wanted to introduce that wrinkle so early. There is enough to learn already and the Editor callback is both clear and sufficient in this two-repository world.

But I too would have my repository raise an event after save under the circumstances you describe. More precisely, it would send a message upon successful save. A .NET event would do if I thought all interested parties could find the repository event and wire handlers to it. That is unlikely in most apps; the EventAggregator pattern is appropriate here.

The message payload could carry the "Entities" from the EntitySavedEventArgs. In a multi-repository application, the listeners to import those entities into their own repositories rather than use them directly. You don't want to mixing entities that belong to different EntityManagers!

Another point worth noting: the saved event args "Entities" collection holds entities that were saved. Deleted entities may be among them; you will know they were deleted if their EntityStates are now Detached. That's the clue listeners need to know so they can remove (not delete!) corresponding entities from their own caches.

Such cross-EntityManager transfers of entities is out-of-scope for this post; it needs a post of its own.

Meanwhile, you are on the right track. Happy coding!

Jignesh said...

Thanks Bell and Jimmy. I will try the new sample :) U guys are doing awesome job. Thanks a ton.