How to Build a Real-World Silverlight 5 Application - Real World .NET, C#, and Silverlight: Indispensible Experiences from 15 MVPs (2012)

Real World .NET, C#, and Silverlight: Indispensible Experiences from 15 MVPs (2012)

Chapter 4

How to Build a Real-World Silverlight 5 Application

by Gill Cleeren

With the introduction of Silverlight, Microsoft made its entry in the Rich Internet Applications (RIA) space. Previously, this market segment was dominated by technologies such as Adobe Flash. After being announced in 2007, Silverlight quickly evolved from a pure JavaScript-oriented development model in version 1, over a .NET-based model since version 2, into a rich platform ready for line-of-business (LOB) application development in versions 3 to 5. Today, Silverlight is installed on more than 70 percent (according to www.riastats.com) of all PCs, and this adoption keeps climbing. Not only is it a framework to build RIAs, it also has become the de facto framework for building applications for Windows Phone 7.

As mentioned, since version 2, Silverlight applications can be built using .NET. Both C# and VB.NET can be used as development language. On the other hand, XAML is used to create (in a declarative manner) the user interface. The XAML language was introduced with WPF at the time of .NET 3.0. Therefore, the learning curve for Silverlight for most .NET developers is not steep — you can leverage a lot of your knowledge.

Although you can fall back on many things you already know from “regular” .NET, Silverlight applications have their peculiarities. This chapter provides an overview of some issues you'll face when you leave the paved road of “demoware” (although this is not a real word, it is used here to reference applications in which you use just the standard things, and nothing more) and get your hands dirty in real-world applications. Of course, each issue is provided with a solution as well.

This chapter starts with the design phase. In this context, “design” refers to creating a prototype of the application. You see how this can be done in a clean way (and in Silverlight!) using SketchFlow.

The next issue addressed is data. Microsoft has several focus domains with Silverlight, and one of them (probably the most important one) is LOB development. An LOB app without data is probably nonexistent, so you need a solid way to get your data into your Silverlight applications. You'll learn about Windows Communication Foundation (WCF) RIA Services for this. When you have the data, Silverlight offers a well-designed data-binding framework. You'll learn about some concepts of the data-binding engine in Silverlight as well.

Just like a solid building needs a good foundation, a good Silverlight application needs a good architecture as well. The community has put its efforts behind the Model-View-ViewModel (MVVM) pattern. You learn how you can easily implement this pattern, and how it can help you to build your application in a more testable and maintainable fashion.

To finish this chapter, you learn how to customize controls to fit the needs of your application.

Setting the Scene for the Application

A chapter on building a real-world Silverlight application requires…a real-world application. Before continuing, explore the scenario for the application you'll build.

Recently, a local cinema chain, “At The Movies,” began looking for a system to enable visitors to book their seats upfront. With this system, they wanted to avoid people rushing into the theater when the doors opened. For people like me, such a system is a blessing because I'm often too late to get one of the good seats.

The system must fulfill the following requirements:

· Enable the users to enter their personal data (first name, last name, and e-mail). No accounts are to be created, but the information must be stored in a database. Therefore, the user does not need a password.

· Enable the user to make a selection out of the currently playing movies and consult the details about the movie (such as duration, rating, and website).

· Enable the user to select a show time for the selected movie, and for this selection, indicate the number of seats.

· Enable the user to enter payment information.

· Show the user the confirmation about the reservation.

For the application, the database schema shown in Figure 4.1 is used throughout this chapter.

note

You can find the application with all the code and a sample database on this book's companion website at www.wrox.com.

Figure 4.1 Application database schema

4.1

Prototype First, Code Later — Using SketchFlow

In my daily life as an application architect, I'm often involved in the startup phase of a project. One of the most crucial tasks in this phase is gathering the requirements of the system to-be, often done by an analyst. This is not an easy task. In many situations, the involved stakeholders have a different vision of the new system.

Nonetheless, based on the feedback and basic information, a rough prototype of the system can be created, mostly in the form of sketches. It's vital for the project that the requirements are captured in the best way possible in the early stages of the project. The later a change must be implemented, the more expensive this change will be.

The analyst often refers to tools such as Visio and PowerPoint to create the sketches of the prototype. Although these are great tools, they aren't perfect for prototyping. For starters, neither Visio nor PowerPoint has the capability to create a prototype that's interactive. You can't create a Visio prototype in which you can click some button and arrive in another sheet, reflecting the real navigation of the final application.

What the analyst delivers in the end is a bundle of printed sheets, containing mockups of screens and a lot of explanation on how the screen works. This makes it difficult for the stakeholder to envision what the final system will look like.

Also, neither of the tools is built to be a prototype designer. They lack the common controls with which an application can be constructed. (Specific stencil sheets can be downloaded for Visio.)

Finally, there's no easy way to give feedback. The development of a prototype should be done iteratively. A version of the prototype is created, new feedback is gathered, and the prototype is updated accordingly — and all this should be possible quite easily.

Introducing SketchFlow

With the introduction of Expression Blend 3 came a new tool called SketchFlow. This new tool enables building interactive prototypes for both Windows Presentation Foundation (WPF) and Silverlight, supporting an iterative design process and containing the most common controls in Silverlight applications.

Looking at the name SketchFlow actually reveals two other words:

· The “Sketch” part refers to the fact that the designs you create with SketchFlow are sketchy, as if they were drawn with a pencil. This emphasizes that the prototype is just a prototype, not a working application.

· The “Flow” part of the name explains that, within the prototypes you create with SketchFlow, a flow can be constructed, enabling you to interact and navigate throughout the application as if it were a finished application.

Come to think of it, it's actually a good thing Microsoft made the applications look sketchy. What would your customer say if he saw your “working prototype” after just 2 days, days after you estimated that the entire project would take about 100 days? When building a prototype with SketchFlow, you are actually building a Silverlight application. The outcome is nothing but a plain Silverlight application. This means that, if desired, you can even write some C# code behind the prototype screens!

The application won't run by itself. It is hosted in the so-called SketchFlow Player you learn about later. This effectively means that the prototype can be uploaded to a server and accessed by all stakeholders so that they get a feeling of the application. Within the SketchFlow Player, you can give feedback, which can then be captured inside SketchFlow again afterward. This enables the iterative design of the prototype.

A common question about SketchFlow prototypes is whether it's a good idea to take the SketchFlow application as the base for the “real” application. The answer is generally “No.” The reason is that, in an iterative design process, you don't want to spend time on designing the UI as it should be done. Think of things such as user controls, reuse of components, and so on. These aren't things you want to be bothered with during a phase in which you want to get the flow of the application finalized. Moreover, if the prototype is created by an analyst, it won't be designed with the final code in mind.

Finding Your Way Around SketchFlow

Learning SketchFlow is easy. The only thing that may take some time, certainly if you aren't experienced with Expression Blend yet, is finding your way around the interface. What better way to learn than trying it out, right?

Start SketchFlow by starting Expression Blend. Within Blend, select File → New Project, and, in the New Project dialog, under the Silverlight node, select Silverlight SketchFlow application. When Blend is ready, you can see that, apart from some SketchFlow-specific panels, the interface is similar to Blend. The following panels, specific for SketchFlow, can be identified:

· SketchFlow Map — In this panel, you can see the screens and their connections. Think of this as a “mind map” for your application.

· SketchFlow Feedback — You can import feedback gathered from stakeholders directly into SketchFlow and see it in the designer. You learn more about this a bit later.

· SketchFlow Animation — Use this panel to create SketchFlow-specific animations. SketchFlow animations are not used in this overview.

Apart from these panels, under the Assets → SketchFlow node, you can find the sketch-version of the controls to be used in the prototype, as shown in Figure 4.2.

Figure 4.2 The SketchFlow controls

4.2

Creating the Application's Prototype

Now that you know about SketchFlow, you can use this knowledge to create a prototype for your own application. Luckily for you, the people of “At The Movies” did their homework and summed up the requirements of the application nicely. You can start creating the prototype based on these requirements.

The Map of the Application

Judging from the requirements, you could implement the application as a wizard, so the navigation would be straightforward, consisting out of five screens, starting with the user data entry and finishing with the confirmation of the reservation. You can build this out in the Map panel inside SketchFlow as follows:

1. When starting a new SketchFlow application, a first screen is created automatically named Screen 1. Start by renaming this to Login by double-clicking the name of the screen. Figure 4.3 shows the screen block item.

2. To add a second, connected screen, you have two options. The easiest one is to place the mouse over the Login screen and wait a few seconds until a menu pops out underneath the screen (as shown in Figure 4.4). The first button generates a connected screen. The second option is to simply right-click the screen and select “Create a Connected Screen.”

3. When created, you can drag around the newly generated screen. Rename this screen to MovieSelection. Figure 4.5 shows the result.

4. Continue to create the entire screen map. The requested, wizard-like application has a flat navigation structure, as shown in Figure 4.6.

Figure 4.3 Screen block item

4.3

Figure 4.4 Menu underneath the screen

4.4

Figure 4.5 The newly created MovieSelection screen

4.5

Figure 4.6 Screen map

4.6

Now that the structure of the application is in a format that corresponds to the requirements of “At the Movies,” you can start designing the screens.

Screen Mockup

The application you have at this point contains nothing more than a few empty screens. Showing this to the customer may not yield the expected results — on the contrary! Screen by screen, you can start adding SketchFlow controls to create mockups of the screens. For spacing reasons, now look at how to create only the MovieSelection screen. (The other screens are similar.) Follow these steps:

1. You can find all controls available to use in SketchFlow (in other words, those having a “sketchy” style) under the Assets Panel → Styles → Sketch Styles. Not all controls, including the DataGrid, have a sketchy asset, so if a DataGrid is needed, you must use the default one.

2. Start by adding a TextBlock sketch by dragging it onto the designer. Set the text to Select the movie you want to see!.

3. Select the Button sketch asset, and add it to create the Next button at the bottom.

4. Add a ListBox-sketch and a ComboBox-sketch control.

Figure 4.7 shows how the screen looks at this point.

Figure 4.7 The MovieSelection screen so far

4.7

SketchFlow enables more than just static controls. One of these extras is adding a data source containing sample data. You can customize this data source to mimic the real data. The capability to add a sample data source is actually inherited from Expression Blend. It's helpful when designing an application because you can visualize how the screen will look with data filled in.

Create such a data source by following these steps:

1. In the Data Panel, click the Create Sample Data button, and select New Sample Data. Give the data source a name such as MovieDataSource.

2. The generated data source contains a collection in which each item has two properties (Property1 of type String and Property2 of type Boolean). You can change existing properties or add new ones. To generate the items as shown in Figure 4.6, you need two properties — the first one of type Image, the second one of type String.

3. To generate the Image property, rename Property1 to MovieImage and change its type to Image. By default, Blend adds images of chairs. You can override this by pointing to a folder containing more relevant images (in this case, containing movie posters).

4. Rename the second property, Property2, to MovieName. In the Properties window of the property, you can change what the generated string looks like (for example, the length).

5. To generate the ListBox with the sample data automatically, drag the Collection (not the individual fields) onto the design surface.

6. Because the prototype should resemble (as much as possible) the final application, you must make it possible for the user to click an item and see the details. To generate the detail fields, change the mode to Details Mode. Now, select both the MovieImage and the MovieName fields and drag them onto the design surface. While dragging, you see that the cursor indicates you are about to create a details view.

Figure 4.8 shows the screen with this sample data added.

Figure 4.8 Sample data added

4.8

With the data now in place, you need to enable the navigation. To show in the mockup that the user can navigate from this screen to the ShowTimeSelection screen, right-click the previously created Next button. In the context menu that appears, select Navigate To → ShowTimeSelection, as shown in Figure 4.9. The other screens are similar.

note

You can find the complete mockup on the companion website for this book at www.wrox.com.

Figure 4.9 Enabling navigation

4.9

Testing the Prototype and Gathering Feedback

Now that the mockup screens have been created, the prototype is ready for prime time! As mentioned earlier, any application you build in SketchFlow this way is nothing more (or less) than a regular Silverlight application. (Hence, you could have added some C#/VB.NET code if you wanted.) You can test the application simply by pressing F5. Blend builds the application as usual.

Upon opening the browser, you can see that the application gets hosted in the Silverlight Player, which is also a Silverlight application. In the Player, you can see the prototype application, the map, and a feedback panel. The application is fully functional. You can click the Next button you created to arrive on the ShowTimeSelection screen.

You can upload the application to any server. To do so, Blend contains a Package SketchFlow function, available via File → Package SketchFlow Project. This opens Windows Explorer, showing the files that need to be copied to a server. Automatically, this also generates a new revision.

You can now allow the customer to test the application and provide feedback on what you have already built. Follow these steps:

1. From the My Feedback panel, select Enable Ink Feedback. The cursor changes into a paintbrush.

2. Draw a circle around the Next button in the Designer, as shown in Figure 4.10.

3. Again, in the feedback panel, type some feedback about the Next button.

4. The feedback can now be saved by clicking the Folder icon and selecting Export Feedback. A *.feedback file can be saved locally containing the entered feedback.

Figure 4.10 Circling the Next button

4.10

You can import the gathered feedback into SketchFlow again. By default, the feedback appears under the revision for which it was made, making it easy to see in which iteration a change or a remark was made.

1. In SketchFlow, open the SketchFlow Feedback panel, and click Add. Select the previously generated *.feedback file.

2. In the Design surface, the ink displays, and the relevant comment is shown in the Feedback panel. This way, the creator of the prototype can easily make changes to incorporate the remarks of the users. This is shown in Figure 4.11.

Figure 4.11 Circling the Next button to show changes

4.11

After several iterations, the prototype you have created will be what the customer is looking for. This agreed-upon prototype makes it easy to know what all involved stakeholders are talking about because all requirements should be possible to map to a function or screen in the prototype.

Now that the prototype is ready, you can (finally) start with the real coder's job.

Data-Binding Primer

In my opinion, one of (if not the) most important concepts to grasp in Silverlight is data binding. In most (perhaps all) LOB applications, data forms the heart of the application. Simplifying tasks such as displaying, editing, sorting, and filtering shortens the development time of applications quite a lot and gives developers more time to focus on the real business problems. Data binding does exactly that — and more!

Later in this chapter, you learn how data binding also forms the foundation on which the MVVM pattern is built. In this part, you learn what you need to know about data binding to understand other, more advanced concepts.

Hello, Data Binding

Before looking at data bindings in action, you need to know what the engine can do for you. The data-binding engine in Silverlight enables you to bind data to controls in the UI. It has nothing to do with data access. The data with which data binding works is data coming from objects — in-memory data, so to speak.

The concept of data binding is nothing new. It already exists in ASP.NET and in Windows Forms. But the binding engine was not as sophisticated as the one included with Silverlight. Silverlight actually inherited the data-binding capabilities from its big brother, WPF. The latter has some extra options in its binding engine, but Silverlight supports most commonly used features, so you are definitely not short of functions in Silverlight!

note

With every new version of Silverlight (including Silverlight 5), more features are added to the data-binding engine, making the difference between WPF and Silverlight in the data-binding area smaller with every release.

Binding Syntax

A data binding is always defined by four items:

· The data object you are binding to — This object is often referred to as the source object for the data binding.

· A property on this object — This property is referred to as the source property.

· A control in the UI — This is referred to as the target control because this is the target of the data. The target object must derive from DependencyObject.

· A property on this control — This property (referred to as the target property) must be a dependency property. Don't worry, though, because most properties (FontSize, Background, and so on) are dependency properties, so you are in no way limited here.

Because these four items make up the binding, it's logical that they appear when you create a data binding via code, both in XAML and C#. Most of the time, a binding will be created using XAML. Bindings from code-behind are mostly used when they are dynamically created.

Assume you want to bind the Text property of a TextBlock to the FirstName property of a Person instance. The XAML syntax looks like the following:

<TextBlock Text="{Binding FirstName}">

If you look at the elements you find in this binding, it seems that one has gone missing. The TextBlock is the target control, and its Text property is the target property. Using a markup extension (which can be recognized by the curly braces), you bind the value of the Text property to the source property, FirstName. So, the source object seems to be missing.

The reason for this is that, most of the time, the DataContext is used. Because of the hierarchical XAML structure (which is XML), you can define the source for the binding on a higher level in the XAML tree so that all controls within that common parent get access to that source. This is exactly what the DataContext enables you to do. You define the DataContext on a common parent (for example, a Grid), and all controls within that Grid, if they don't have a source specified for their data binding, look up in the XAML tree for a non-null DataContext. When found, the control uses the referenced object as its source for the data binding. The DataContext can be set from XAML code, or from code-behind.

The nice thing about the DataContext is that is takes away the need to define on each control what the source for the binding actually is (which would actually make the code much less readable). DataContext will be used quite a bit in this chapter's sample application.

Although the DataContext is most commonly used, you can define the source object for the data binding in other ways:

· Using the Source property of the data binding — You can refer to an object stored in resources (at the page, UserControl, or even application level).

· Using the ElementName property — You can create a binding between two controls. Both the source and the target of the data binding are, in this case, a control. To define such a binding, you can use the ElementName property within the binding declaration, referring to the element name that should be used as the source for the binding.

· Using the RelativeSource property — Although this is not often used, it enables you to create a binding to an element relative to the current element.

Binding Modes

You can use data binding both for data display and data entry. You can indicate how you want the data to flow. The default is from the source to the target, but even there, you have two options. Table 4.1-shows a list of the options you have in Silverlight 4.

Table 4.1 Binding Modes in Silverlight 5

Mode

Action

OneWay

OneWay is the default. Data initially flows from the source to the target. However, if the source object implements the INotifyPropertyChanged interface, and the value of one of its properties changes, this changed value displays in the UI as well. Silverlight's data-binding engine takes care of the automatic synchronization for you.

OneTime

Similar to OneWay, the data flows from the source to the target. However, if the source changes (even if it implements the INotifyPropertyChanged interface), the target will not be updated.

TwoWay

In a TwoWay binding, the data flows in two directions. If the user enters a value (in a TextBox, for example), this data will be pushed back to the original object. This mode enables capturing user input.

INotifyPropertyChanged Interface

The INotifyPropertyChanged interface was mentioned in Table 4.1. This interface makes it possible to synchronize the value of the properties of controls if the source values have changed. The interface itself is simple, as you can see in Listing 4.1.

Listing 4.1: The INotifyPropertyChanged Interface

namespace System.ComponentModel

{

// Summary:

// Notifies clients that a property value has changed.

public interface INotifyPropertyChanged

{

// Summary:

// Occurs when a property value changes.

event PropertyChangedEventHandler PropertyChanged;

}

}

The interface defines just one event, the PropertyChanged event. This event is to be raised whenever the value of a property changes in the source object. When Silverlight notices that you are binding to a source object that implements this interface, it automatically listens for this event being raised from the source object. When it is raised, Silverlight updates the UI to reflect the changes in the property.

This will be the case only if the binding has been defined with either a OneWay or TwoWay mode. For collections, a similar interface exists, namely INotifyCollectionChanged. Because this interface is more complex to implement, a specific collection is available that already implements this interface: theObservableCollection<T>. The ObservableCollection notifies when items are added or removed from the interface. It won't notify if individual items are changing.

Converters

Before discussing how to use data binding to build your application, take a brief look at converters. A converter can be seen as a hook in the data-binding process. It enables you to do an action on the value coming from source or target in a data-binding scenario, such as converting or formatting. Basically, it's nothing more than a class that implements the IValueConverter interface. Listing 4.2 shows the definition of this interface.

Listing 4.2: The IValueConverter Interface

namespace System.Windows.Data

{

public interface IValueConverter

{

object Convert(object value, Type targetType, object parameter,

CultureInfo culture);

object ConvertBack(object value, Type targetType, object parameter,

CultureInfo culture);

}

}

The Convert() method is automatically invoked by Silverlight when a converter is used on a data-binding expression when the data flows from the source to the target. As an example, let's say you are working with an account balance, which obviously can be positive or negative. A converter can be used to “convert” the value to a green color if positive, or red if negative. A converter can (but is not obliged to) return the same data type. (Here, the converter receives a numeric value and returns a SolidColorBrush.)

Converters are handy and, in any application, you'll find them to be useful in several scenarios.

Creating a Data Bound Screen

The application that the people of “At the Movies” asked to create relies on data quite a lot. You'll soon see how to get the data to the application, but first focus first on displaying and capturing the data based on data-binding concepts. If you think back to the prototype you created in SketchFlow, it's obvious that each screen will be binding to data in some way.

For example, consider the user data-entry screen, where the user must enter his or her first name, last name, and e-mail. The to-be-entered data can be captured in a data class called Person. This class implements the INotifyPropertyChanged interface, as shown in Listing 4.3.

Listing 4.3: Person Class

public class Person: INotifyPropertyChanged

{

public event PropertyChangedEventHandler PropertyChanged;

private string _firstName;

private string _lastName;

private string _email;

public int UserId { get; set; }

public string FirstName

{

get

{

return _firstName;

}

set

{

if (_firstName != value)

{

_firstName = value;

RaisePropertyChanged();

}

}

}

private void RaisePropertyChanged(string propertyName)

{

if (PropertyChanged != null)

{

PropertyChanged

(this, new PropertyChangedEventArgs(propertyName));

}

}

//LastName and Email are similar

}

Code file [Snowball.AtTheMovies.Silverlight.UI/Model/Person.cs] available for download at Wrox.com

As shown partially in Listing 4.4, in the XAML code called UserDataEntryView, you can create some TextBox controls. Each of these is bound to a public property of the Person class. The bindings have been marked as a TwoWay binding, so all entered data goes from the target control (the TextBox) to the source object (the Person instance). Note here, though, that no source is defined for the binding.

note

Listing 4.4: XAML Code (Partial) for UserDataEntryView

<TextBox x:Name="FirstNameTextBox"

Text="{Binding FirstName, Mode=TwoWay}"></TextBox>

<TextBox x:Name="LastNameTextBox"

Text="{Binding LastName, Mode=TwoWay}"></TextBox>

<TextBox x:Name="EmailTextBox

Text="{Binding Email, Mode=TwoWay}"></TextBox>

Code file [Snowball.AtTheMovies.Silverlight.UI/View/UserDataEntryView.xaml] available for download at Wrox.com.

In the code-behind for this view shown in Listing 4.5, you can now instantiate the Person class and set this instance as the DataContext for a common parent. This can be a containing Grid or even the UserControl itself.

note

Listing 4.5: Code-Behind for the UserDataEntryView

public partial class UserDataEntryView : Page

{

public UserDataEntryView()

{

InitializeComponent();

Person person = new Person();

this.DataContext = person;

}

}

Code file [Snowball.AtTheMovies.Silverlight.UI/View/UserDataEntryView.xaml.cs] available for download at Wrox.com.

At this point, you should have a deep enough understanding of the concepts of data binding. You'll use data binding a lot more when exploring the following topics. Now focus on getting the real data from the server into the Silverlight application, and vice versa, using WCF RIA Services.

WCF RIA Services in Action

By now, everyone knows that Silverlight is a client-side framework. Being client-side brings some questions to the table about accessing data. Data resides in a database on the server side. How can you access that data from Silverlight applications?

Looking at the assemblies and namespaces available in Silverlight, it lacks all ADO.NET classes, and it has no LINQ-To-SQL or Entity Framework (EF) capabilities. This means that it won't be possible to get a Silverlight application to connect with a server-side database just by using a connection string.

Actually, this would be anything but secure. Silverlight code runs on the user's machine, and you know that all code there is insecure by default. Each user would get access to that connection string in no time. Sadly, not all users have good intentions with your data.

Also, at this point, there's no out-of-the box support for a client-side database in Silverlight. A client-side database would not be a solution for many problems. No developer would write code that downloads an entire product database to the client. (At least I hope not!)

That being said, there's no reason to panic. A solution exists in the form of services. Services can provide an access point to a database, in most cases, not directly, but probably through a business layer and a data access layer (DAL). Microsoft did provide Silverlight with support for many types of services, so accessing data over any kind of service should not be an issue for the business application developer.

Table 4.2 contains an overview of supported service types in Silverlight.

Table 4.2 Supported Service Types in Silverlight 5

Type

Description

ASMX Web services

These are regular web services used mostly to support legacy systems still using this type of services.

WCF Services

These provide the richest support in Silverlight and should be your default for service access. They provide the fastest data transfer with binary encoding, support for security, and duplex communication.

RSS/POX (Plain Old XML)

Silverlight can read out XML returned from a service. LINQ-To-XML, XmlReader/XmlWriter, is supported. In the case of RSS, Syndication classes provide a typed way of reading out the XML.

REST

Representational State Transfer (REST) is a protocol used by many large web applications to expose their functionality (for example, Flickr, Twitter, and so on). Data is exchanged in XML, JavaScript Object Notation (JSON), or ATOM.

Sockets

Although WCF supports duplex communication in Silverlight, sockets provide real duplex communication. Because this is restricted to specific port numbers, it is usable only in an intranet environment.

WCF Data Services

These expose an entity model over a service in XML. Using the WCF Data Services Client Library, an abstraction layer is added that takes away the need to perform XML parsing and URL creation. WCF Data Services can be used from regular .NET and ASP.NET as well.

WCF RIA Services

Specifically designed for Silverlight, WCF RIA Services provides developers with an end-to-end way to handle data inside Silverlight applications.

Choosing the Service-Layer Technology

With the variety of options shown in Table 4.2 available, you may wonder what to use. As with many things, the best answer is, “It depends.”

For example, assume you already have an existing service architecture in place based on ASMX Web Services. It's probably not beneficial (or, perhaps, not even possible) to upgrade the entire service façade just because you're adding a Silverlight interface. ASMX Web Services work just as well.

If, however, you must start from scratch, WCF is a better choice. It's the default framework for building services since .NET 3.0. Plain WCF services have the advantage of being usable from other platforms such as ASP.NET. It should also be your default choice if you must perform duplex communication over the Internet. If you need duplex communication that goes over an intranet environment, sockets (or WCF using net.tcp) might be a better choice.

REST services, on the other hand, are great to use if you need your services to use standards-based methods because it uses XML by default.

As you can see, it does depend on the situation.

With the introduction of WCF RIA Services (often referred to as RIA Services), the choice became even more difficult. Microsoft added a framework specifically for working with data in n-tier Silverlight applications. Although built for Silverlight, RIA Services are, at their core, WCF services, and, therefore, also accessible from other technologies.

Hello to You, WCF RIA Services

Before examining why RIA Services will be used for accessing the data for the “At The Movies” application, let's be clear on what exactly all this actually means.

One of the “issues” when using regular WCF services for accessing data from a Silverlight application is that for all actions you must do, you must write a new method. Reading out all movies requires a new method. Reading out all movies ordered by release date requires another one. Filtering movies by genre requires yet another one. You get the picture. In many cases, although you try to reuse these service methods over different applications, it often is not that simple. You end up with a bunch of methods that are specific for the application at hand.

A “traditional” n-tier Silverlight application architecture has another “issue” in terms of validation. Although data must be validated within the service code on the server-side, to keep the responsiveness on the client as good as possible, you must validate on the client as well. As you may have guessed, this often results in creating duplicate code. And if there's one thing that most developers hate, it's duplicate code — or that they ran out of coffee!

With WCF RIA Services, Microsoft attempts to make working with data in n-tier solutions easier. It's not easy to describe RIA Services in just one definition because it is more than just one thing:

· It's a framework.

· It contains assemblies added both on the server side and on the client side. These assemblies make it easy to have an end-to-end solution to work with data.

· It's also tooling. When RIA Services are installed, code will automatically be generated on the client based on the code from the service, avoiding your having to write the same code twice. This is, among others, the case for validation logic. Validation attributes applied on the entities on the service will be copied to the client side. Also, for the entities you must work with, CRUD (Create, Read, Update and Delete) methods are generated automatically.

· It also integrates with ASP.NET security. If you have an existing ASP.NET membership infrastructure already in place, this can be reused.

Code-generation and many things happening behind the scenes might make some developers scared, thinking that what you can build with RIA Services is nothing more than some demo-ware. This is absolutely not the case.

You can use RIA Services to create scalable and architectural well-designed applications, in which the amount of code that must be written, and the issues of dealing with asynchronous development, are reduced to a minimum. This, however, does not tie you into an architecture you may not be comfortable with. Although it can be used to use data end-to-end, you can bend it just like you want. Indeed, RIA Services makes building n-tier RIA applications a lot easier!

Why WCF RIA Services?

Now that you understand what RIA Services is, you may be thinking why RIA Services is chosen over another technology for the “At The Movies” application (or any other small or medium-size application). The previously mentioned code-generation is handy. For example, when you have annotations on your server-side entities (mostly for validation), the framework automatically copies them to the client, so you must write your code only once — that is, on the server-side.

It can even be said that RIA Services hides that you are sending data back and forth asynchronously. It hides the details of making asynchronous calls. Not having to write code for all CRUD operations is certainly another plus.

RIA Services is optimized for working with EF. However, it is data access layer (DAL) neutral. This means that it can work with others, but it'll cost you some more work. Tying to RIA Services, therefore, doesn't bind you to a specific DAL technology.

Architecture and Concepts of RIA Services

Architecturally, Silverlight applications that use RIA Services are not that different from applications that use other services, as shown in Figure 4.12.

Figure 4.12 Using RIA Services versus other services

4.1

When creating an RIA Service, within the client project, code gets generated. With a WCF or an ASMX service, there's also code generated in the form of a proxy. The proxy generated here, however, is much, let's say, “smarter.” You'll learn shortly how it differs from another proxy. The entities used in the service methods are generated on the client as well, including validation logic, resulting in your not having to create and maintain duplicated code.

Creating the Server-Side

When learning about data binding, you worked with a self-created Person class. In most situations, that's not how you'll work. You won't have your business objects as classes just in the Silverlight application. This section follows the end-to-end path of data, coming from the database all the way to the UI, of course, passing by RIA Services. Before delving into that, take a look at how you can organize the solution.

Setting Up the Solution

Ensuring that the solution structure is well-organized is a first and important step to an understandable development project. Follow these steps to set up the solution structure:

1. The solution for this project is called Snowball.AtTheMovies. (Snowball.be is the name of my blog, hence the prefix in my code.) The solution is a Silverlight navigation application. Visual Studio creates both a web project and a Silverlight application.

2. RIA Services can be placed in a web project. They can be part of the website that gets created with a new Silverlight project. Although this is fine for small projects, perhaps, in a real-world solution, this should not be the place where you put them. Instead, there is a project template available that helps you out here — the WCF RIA Services Class Library. When adding such a project to the solution, a server project for the services and a client project for the client-side generated code are created. Add an instance of this project template and rename the two projects to match the naming convention: Snowball.AtTheMovies.Services.Web for the server-side project and Snowball.AtTheMovies.Services for the Silverlight project.

3. Add a class library (Snowball.AtTheMovies.Model) where you'll add the Entity Model.

4. Create the following references:

· The RIA Services (Snowball.AtTheMovies.Services.Web) project must reference the model project (Snowball.AtTheMovies.Model).

· The web project must reference the services project.

· The Silverlight project must reference the client-side services project (Snowball.AtTheMovies.Services).

5. You must enable RIA Services in the web project because they will be hosted from that URL. To get this to work, add the following references in the web project:

· System.ServiceModel.DomainServices.EntityFramework

· System.ServiceModel.DomainServices.Hosting

· System.ServiceModel.DomainServices.Server

6. In the web.config file of the web project, configuration code must be added to enable RIA Services. You can also find this code, shown in Listing 4.6, on this book's companion website at www.wrox.com.

Listing 4.6: Web.config

<?xml version="1.0"?>

<configuration>

<system.web>

<authentication mode="Forms"/>

<httpModules>

<add name="DomainServiceModule"

type="System.ServiceModel.DomainServices.

Hosting.DomainServiceHttpModule,

System.ServiceModel.DomainServices.Hosting,

Version=4.0.0.0, Culture=neutral,

PublicKeyToken=31bf3856ad364e35" />

</httpModules>

<compilation debug="true" targetFramework="4.0">

<assemblies>

<add assembly="System.Data.Entity, Version=4.0.0.0,

Culture=neutral, PublicKeyToken=b77a5c561934e089" />

</assemblies>

</compilation>

</system.web>

...

<system.webServer>

<modules runAllManagedModulesForAllRequests="true">

<add name="DomainServiceModule"

preCondition="managedHandler"

type="System.ServiceModel.DomainServices.

Hosting.DomainServiceHttpModule,

System.ServiceModel.DomainServices.Hosting,

Version=4.0.0.0, Culture=neutral,

PublicKeyToken=31bf3856ad364e35" />

</modules>

<validation validateIntegratedModeConfiguration="false" />

</system.webServer>

<system.serviceModel>

<serviceHostingEnvironment aspNetCompatibilityEnabled="true"

multipleSiteBindingsEnabled="true" />

</system.serviceModel>

</configuration>

Code file [Snowball.AtTheMovies.Web/Web.config] available for download at Wrox.com.

Figure 4.13 shows the final solution structure. For organizational purposes, a few solution folders (Client, Server and Assemblies) have been added, which will be used later.

Figure 4.13 Final solution architecture

4.13

Data Access Using Entity Framework

RIA Services is optimized to work with Entity Framework (EF), which is why it is recommended that you use EF when choosing RIA Services. If you are forced to use another data access technology such as nHibernate, RIA Services will still be usable. However, it will require more manual work to get things up and running. Because “At The Movies” has not put any specific technology requirements on the project, you're free as a bird to select what you want. Therefore, the choice is made to use EF for this particular project.

In the Snowball.AtTheMovies.Model project, you add the Entity Model, AtTheMovies.edmx, here. Because the application is quite simple still, you can do a one-on-one mapping to the tables in the database. EF is an Object-Relational Mapper (ORM) and supports changing the entities. It can take care of the mappings that must be created to the database tables for you. Figure 4.14 shows the model, which is similar to the database model shown at the beginning of the chapter.

Figure 4.14 The Entity Model

4.14

In the AtTheMoviesModel.Designer.cs file, a context is generated, as well as the entities of the model. This code is generated and regenerated after a change in the model, so it should not be altered by you. In case you must add code to the generated entities, you can do so using partial classes. This even includes adding annotations (for validation purposes) on properties.

Assume you want to add a validation rule that the Amount property on the Payment class should always be smaller than 200. You can apply this using the RangeAttribute. A second rule might be that the CardNumber is required; this can be applied using the RequiredAttribute. However, there's no such thing as a partial property to add this attribute on. Instead, you must add a second class, in this case, called PaymentMetaData, which contains the metadata for the Payment class. This class is linked to the first one using the MetadataTypeAttribute. The attributes on the properties are merged with the attributes on the Paymentclass (generated by EF), based on their names. Listing 4.7 shows the creation of this partial class.

note

Listing 4.7: Payment Class

[MetadataType(typeof(PaymentMetaData))]

public partial class Payment

{

internal sealed class PaymentMetaData

{

[Required]

public string CardNumber { get; set; }

[Range(1, 200)]

public Decimal Amount { get; set; }

}

}

Code file [Snowball.AtTheMovies.Model/Payment.cs] available for download at Wrox.com.

Later, you see that this attribute returns in the generated code on the client.

Creating the Actual Services

With the model and data access ready, you can turn your attention to the services. Let's start building the services for the MovieSelection screen. As explained previously, the tooling that comes with RIA Services takes away some tiresome work. If wanted, you can have it create some default CRUD operations for the desired entities.

To create the services, start by adding a domain service class to the Snowball.AtTheMovies.Services.Web project. A service within RIA services is referred to as a DomainService. It is the access point for your Silverlight application to get data, either coming from a database or another service.

The tooling requires that the build is up-to-date so that it can find the entities in your code. Initiate a build if not performed yet, and then add a domain service class to the project called MovieService. Immediately, a dialog pops up, enabling you to configure the service, as shown in Figure 4.15.

Figure 4.15 Dialog for configuring the service

4.14

The “Enable Client Access” check box indicates whether you want the service code to be generated on the client as well. Selecting this box results in the EnableClientAccess attribute to be applied on the class. The second check box, “Expose OData Endpoint,” enables exposing data from theDomainService as OData, facilitating data sharing. This way, you can access the data from the service (for example, from Excel).

As previously mentioned, the tooling works best with EF, which is clearly visible here. The available DataContexts are listed and the entities for them are shown. You have the option to select which entities are needed for the service you are creating. Optionally, you can also have the tooling to generate the basic CRUD operations.

In version 1 of RIA Services, you could not share an entity over multiple services. This meant that if you had two services (for example PaymentService and MovieService), you could have only the User entity in one of the two. In real-world systems, this caused problems. No real system is designed so that all entities are clearly separated. (It's probably not even a good idea to try doing so!) As of this writing, version 1.0, Service Pack 1 Beta is the latest version, which adds support for shared entities, making it possible to segment the DomainServices more like you want.

In MovieService, all the logic resides that has to do with Movie and ShowTime. Therefore, click both the Movie and the ShowTime entities because these will be needed in the implementation of the service. You won't be adding or updating these entities in this application, so leave the “Enable Editing” check box unchecked. Listing 4.8 shows the code that is generated.

note

Listing 4.8: Generated DomainService Class Code

// [RequiresAuthentication]

[EnableClientAccess()]

public class MovieService : LinqToEntitiesDomainService<AtTheMoviesEntities>

{

public IQueryable<Movie> GetMovies()

{

return this.ObjectContext.Movies;

}

public IQueryable<ShowTime> GetShowTimes()

{

return this.ObjectContext.ShowTimes;

}

}

Code file [Snowball.AtTheMovies.Services.Web/PaymentService.cs] available for download at Wrox.com.

For the movie selection screen (refer to the screen mockup section where the prototype of this screen was shown), you need a list of currently playing movies. (It's not a good idea to allow the user to select a movie that's not playing anymore, is it?) However, that's not what was generated. TheGetMovies() method returns a list of all movies.

Although this code is generated, you can freely change it. It's generated only upon the creation of the service. So, you can either change the GetMovies() method or add a new one. Let's go for the second option, as shown in the Listing 4.9, which uses the GetAllCurrentMovies().

Listing 4.9: GetAllCurrentMovies() Method

public IQueryable<Movie> GetAllCurrentMovies()

{

return this.ObjectContext.Movies.Where(m =>

m.CurrentlyPlaying == true);

}

For now, you have enough logic within the service, so you should now look at what all this work on the server results in on the client.

But wait just a minute. How can the client code know to use this GetAllCurrentMovies() method to retrieve a list of movies?

Convention Is the Rule

As you may have noticed, the generated methods (so-called query methods) start with Get and return an IQueryable<Entity>. For RIA Services, the latter is a sign that this method can be used for retrieving movies. Similarly, starting a method name with Insert and passing an entity as a parameter is enough for RIA Services to know that the specified method does an insert.

The rule here is convention. RIA Services expects this naming pattern, and the generated code on the client will be based on this naming schema. The principle of convention-over-configuration has been known for quite some time but has been somewhat lacking in Microsoft products. Seeing this principle applied here is a sign that you'll see this more and more in Microsoft products in the future as well.

That being said, it's not always possible to stick to these convention rules. I've dealt with customers where the code names need to be translated in the local language. To counter this, it's possible to use attributes, overruling the convention. Table 4.3 shows a summarized list of the conventions, attributes, and their usage.

Table 4.3 Conventions and Attributes Used by RIA Services

Convention Prefix

Configuration Attribute

Usage

Insert, Add, Create

Insert

Accepts an entity as a parameter and inserts this into the data source.

Update, Change, Modify

Update

Accepts an entity as a parameter and updates this entity in the data source.

Remove, Delete

Delete

Accepts an entity as a parameter and removes this entity from the data source.

(Get), (Retrieve)…

Query

Retrieves a single entity T, an IQueryable<T> or an IEnumberable<T>. It's common to prefix with Get or Retrieve; although, you're not obliged to do so. Any method name works just fine.

The IQueryable<T> return value of the retrieval methods is a special case. An IQueryable represents an expression tree, a concept in LINQ that enables evaluating a query against a specific data source. This means that you can build a query on the client, and it will be appended to the query on the server side.

On the data store, an optimal query will be executed, based on the provider. This effectively means that you can build a filtering query on the client. This query can then be combined with the server-side query, resulting in a query being fired on the data store, returning results back to the client.

Meanwhile, in the Silverlight Project

Upon building the solution, RIA Services' tooling works hard to generate the client-side code. In the Snowball.AtTheMovies.Services project (if you followed the same convention as used with this solution, this project is in the Client solution folder), set the Show All Files option to true. You see a folder called Generated_Code, as shown in Figure 4.16. Although not part of your project, this folder contains the proxy code, based on the DomainService you created earlier.

Figure 4.16 Generated_Code folder

4.16

In the generated code, you can find a client-side context of type DomainContext called MovieContext. For each domain service you create, a domain context generates. This context can be seen as the point of contact for the client code. It's a proxy to the service methods, but it's also responsible for things like change tracking for entities on the client. The latter is done using an EntityContainer, which contains an EntitySet for each entity. (In this case, you have an EntitySet for both the Movie and the ShowTime entities.) Listing 4.10 shows part of the generated code of the Snowball.AtTheMovies.Services.Web.g.cs file.

note

Listing 4.10: EntityContainer with EntitySet

internal sealed class MovieContextEntityContainer : EntityContainer

{

public MovieContextEntityContainer()

{

this.CreateEntitySet<Movie>(EntitySetOperations.None);

this.CreateEntitySet<ShowTime>(EntitySetOperations.None);

}

}

Code file [Snowball.AtTheMovies.Services/Generated_code/Snowball.AtTheMovies.Services.Web.g.cs] available for download at Wrox.com.

The domain context supports batching as well. If you change more than one entity on the client, you can save them in a batch to the service.

You can now use the generated code to fill the list of movies. Now take a look at building this screen.

Loading Data in the MovieSelection Screen

To make things easier to follow, Figure 4.17 shows the screen you are working toward. To save some space, the entire XAML is not included. Take a look at the solution on this book's companion website (www.wrox.com) for this.

Figure 4.17 MovieSelection Screen

4.16

The first thing you must be concerned with is filling the list with all current movies. You have built this method within the MovieService already, so it's usable from the Silverlight application. Listing 4.11 shows how to do this.

Listing 4.11: Loading Data from the DomainService

private MovieContext _context;

public MovieSelectionView()

{

InitializeComponent();

_context = new MovieContext();

}

// Executes when the user navigates to this page.

protected override void OnNavigatedTo(NavigationEventArgs e)

{

EntityQuery<Movie> query = _context.GetAllCurrentMoviesQuery();

_context.Load(query);

CurrentMovieListBox.ItemsSource = _context.Movies;

}

Code file [Snowball.AtTheMovies.Silverlight.UI/View/MovieSelectionView.xaml.cs] available for download at Wrox.com.

The code in the OnNavigatedTo() method is what should interest you most. It is executed when using the navigation framework — you have navigated to the page. The context, MovieContext, is instantiated, and you use the GetAllCurrentMoviesQuery() method of it. Indeed, this resembles quite a lot working with a regular service, where you have a normal proxy as well.

You bind the ItemsSource property of the ListBox to a property Movies on the MoviesDataContext. When RIA Services notices that you have one or more methods on your service that return an IQueryable<T>, the previously mentioned EntitySet is generated. You are binding to this exact EntitySet here. However, the data does not arrive in this property automatically. You must call the Load() method explicitly. Although RIA Services hides the asynchronous coding, the data still is loaded in an asynchronous manner.

Polishing the Screen

If you ran the exact code as printed, your result won't be exactly the same. What you see in the ListBox is just the ToString() implementation, not the fancier UI with an image and a TextBlock. This is achieved using a custom DataTemplate for the ItemTemplate property of the ListBox.

This template can be seen as a piece of XAML that is generated for each item within the ListBox. The DataContext for each item is the bound object, so you can use regular data-binding expressions here as well. Listing 4.12 shows the XAML for this DataTemplate. Although you can place this code directly in the App.xaml, it's common to create one or more resource dictionaries and reference these from the App.xaml. That's exactly why this DataTemplate is located in the Assets/CoreStyles.xaml file.

note

Listing 4.12: DataTemplate for the Movie Selection ListBox

<Style x:Key="MovieSelectionListBoxStyle" TargetType="ListBox">

<Setter Property="Margin" Value="5"></Setter>

<Setter Property="Width" Value="290"></Setter>

<Setter Property="ItemTemplate">

<Setter.Value>

<DataTemplate>

<StackPanel Orientation="Horizontal">

<Image Source="{Binding MoviePosterUrl,

Converter={StaticResource

localImageUrlConverter}}"

Margin="3"></Image>

<TextBlock Text="{Binding MovieName}"

TextWrapping="Wrap"

VerticalAlignment="Center"

Style="{StaticResource

MovieSelectionTextBlockStyle}">

</TextBlock>

</StackPanel>

</DataTemplate>

</Setter.Value>

</Setter>

</Style>

Code file [Snowball.AtTheMovies.Silverlight.UI/Assets/CoreStyles.xaml] available for download at Wrox.com.

A good coding practice in Silverlight is to place this inside a style, as shown. This style then is applied using the code from Listing 4.13.

note

Listing 4.13: Applying the Style on the ListBox

<ListBox x:Name="CurrentMovieListBox"

Grid.Row="1" Grid.Column="0"

Style="{StaticResource MovieSelectionListBoxStyle}">

</ListBox>

Code file [Snowball.AtTheMovies.Silverlight.UI/View/MovieSelectionView.xaml] available for download at Wrox.com.

Finally, when a selection is made in the ListBox, you must ensure that the Grid containing the details of the movie knows where its children should get their data from. Whenever the selection changes, this must update as well.

This can be achieved by changing the DataContext of this Grid to the selected item in the ListBox (each item is a Movie anyway). Listing 4.14 shows just this.

note

Listing 4.14: XAML Code for the Detail Grid (Partly)

<Grid DataContext="{Binding ElementName=CurrentMovieListBox, Path=SelectedItem}">

...

<Image Source="{Binding MoviePosterUrl,

Converter={StaticResource localImageUrlConverter}}" >

</Image>

<TextBlock Text="Movie details"

Style="{StaticResource SubTitleTextBlockStyle}">

</TextBlock>

<TextBlock x:Name="MovieNameTextBlock"

Text="{Binding MovieName}"

Style="{StaticResource LabelTextBlockStyle}">

</TextBlock>

<TextBlock x:Name="MovieDescriptionTextBlock"

Text="{Binding MovieDescription}"

Style="{StaticResource DefaultTextBlockStyle}">

</TextBlock>

...

</Grid>

Code file [Snowball.AtTheMovies.Silverlight.UI/View/MovieSelectionView.xaml] available for download at Wrox.com.

The other screens can be re-created in a similar fashion. To save space, they aren't presented here.

Applying the MVVM Pattern

When you look back at the code you just wrote, you see that there's nothing actually wrong with it. There isn't, certainly not. If you keep working on the other screens the way you started, things will work out fine, and the application for “At The Movies” will turn out just fine. You wrote some XAML code and most of the code in the Silverlight application arrives in the code-behind. Figure 4.18 shows a graphical overview of the code so far.

Figure 4.18 Application code thus far

4.18

As you can see, the bulk lies in the View, consisting of the XAML and code-behind. The code also directly accesses the model, and services are directly invoked from the View code as well. Although there's not anything wrong with this approach, it's not easy to test. The code in the code-behind is difficult to test in isolation because it's closely linked to the View objects.

A better approach exists in the Model-View-ViewModel (MVVM) approach. Take a look at what the schema for my application evolves into, as shown in Figure 4.19.

Figure 4.19 Evolution of the application

4.19

The View has been on a serious diet! Although the amount of XAML is still roughly the same, the code-behind part is a lot smaller. There's almost no more code left in the code-behind (more on that later). The bulk of the code has now moved into the ViewModel, an abstraction of the View. This ViewModel exposes state and operations. State means properties, to be used by the View. Operations are commands. The ViewModel is set as the DataContext for the View, so in the View, bindings can be created to properties and commands of the ViewModel.

No direct link exists between the View and the Model anymore. The ViewModel sits in between.

Different Parts, Different Roles

Before you start refactoring your code to include the MVVM pattern, take a look in a bit more detail at the different building blocks.

The View

The View should contain mostly XAML. Although there is some code in the code-behind, it should be minimal. All code that should be tested should not be in the code-behind. The View binds to the ViewModel, which exposes data in the form of properties. Event handlers should not be used to handle events (such as clicking on a button). Instead, commands can be used, which are exposed from the ViewModel as well.

Silverlight 4 contains limited support for commanding, in that it's supported only on the ButtonBase class. Using a simple workaround with a behavior, other controls can bind any event to a command as well. In the end, the View is the area of the designer!

The ViewModel

The ViewModel contains the state and the operations available for the View — in other words, the View can bind to them. The ViewModel can be seen as an abstraction of the View. Properties represent the state. For example, a list of movies can be a property of the ViewModel, to which the View can bind.

Commands represent the operations available to the View. All the code to be tested should live in the ViewModel. It can be tested in isolation, without interference of, for example, a UI element. The ViewModel does not know in which View it's used. Sometimes, different views are bound to one ViewModel.

The Model

The Model is any data model you want to use in the application. This can be a proxy, generated from adding a service reference. There should be no direct access from the View to the Model. Instead, the ViewModel gets the data from the Model and prepares it for the UI to use. The Model does not know the ViewModels using it.

Choosing the MVVM Approach

Whether to build your application based on an MVVM architecture may be a question that pops into your mind. One of the main advantages is, of course, the better separation of concerns (SOC) within your code. (And everyone knows this is a good thing.)

Making your View thin so that it becomes entirely the area of the designer on your team is a good principle. Your logic can automatically become more testable and maintainable. As mentioned, testing View code is difficult. On the other hand, testing the ViewModel is much easier. Also, through the better SOC, for example, when the View changes, there's a big chance that no change is needed to the ViewModel, thus improving the maintainability.

On the other hand, as you'll soon see, there's still more manual work to be done (that is, more code to be written) when using MVVM. Several great frameworks are out there that can take on part of that work for you. This can be a bit confusing because there are quite a few implementations available. Of course, MVVM remains a pattern, and everyone gives his or her own implementation to the pattern.

Picking a Little Helper — MVVM Light

As mentioned, using MVVM can be a bit more work because there's more manual code to be written. One of the frameworks that can help with this is MVVM Light, written by Laurent Bugnion. As the name implies, it is a lightweight framework, available as Open Source, so you can make changes where you want.

The code for the project can be downloaded from the CodePlex site via http://mvvmlight.codeplex.com. For refactoring purposes here, use just the binaries, which can also be downloaded from the CodePlex site.

In the solution, add a Solution folder and place in it the GalaSoft.MvvmLight.SL4.dll assembly. Create a reference to the assembly from the Silverlight project. You're now ready to start refactoring to MVVM.

Refactoring to MVVM

In this section, you refactor your code to use the MVVM pattern. First prepare the project to accommodate the changes you want to make.

Add in the Silverlight project View, ViewModel, and Model folders. In these folders, you can place the classes of the respective roles.

The Model

The code that communicates with the DomainService no longer can be in the code-behind; place it in a Model class. For the MovieSelectionView discussed earlier, you must work with Movie entities. You create a class MovieModel in the Model folder. In this class, you need a method that retrieves all currently playing movies. Listing 4.15 shows the code for this class.

note

Listing 4.15: MovieModel Class

public class MovieModel

{

private MovieContext _context;

private LoadOperation<Movie> _movieLoadOperation;

private Action<ObservableCollection<Movie>> getAllCurrentMoviesCallback;

public MovieModel()

{

_context = new MovieContext();

}

public void GetAllCurrentMovies

(Action<ObservableCollection<Movie>> GetAllCurrentMoviesCallback)

{

_getAllCurrentMoviesCallback = GetAllCurrentMoviesCallback;

var query = _context.GetAllCurrentMoviesQuery();

_movieLoadOperation = _context.Load(query);

_movieLoadOperation.Completed +=

new EventHandler(OnGetAllCurrentMoviesCompleted);

}

private void OnGetAllCurrentMoviesCompleted(object sender, EventArgs e)

{

_movieLoadOperation.Completed -= OnGetAllCurrentMoviesCompleted;

var movies = new EntityList<Movie>

(_context.Movies, _movieLoadOperation.Entities);

_getAllCurrentMoviesCallback(movies);

}

}

Code file [Snowball.AtTheMovies.Silverlight.UI/Model/MovieModel.cs] available for download at Wrox.com.

The GetAllCurrentMovies() method takes Action<ObservableCollection<Movie>> as a parameter. This shows once again that the data retrieval from the DomainService is asynchronous. This Action points to the method (part of the ViewModel, as you'll see soon) that you'll call from the callback (OnGetAllCurrentMoviesCompleted) when the data has been received. Accessing the service is similar to what you saw earlier.

The ViewModel

The ViewModel for the movie selection screen is a class located in the ViewModel folder called MovieSelectionViewModel. This class must notify the View about changes happening to its properties to ensure that the data-binding engine keeps the View in sync. Therefore, this class should implement theINotifyPropertyChanged interface. However, you can benefit from MVVM Light here and have the class inherit from ViewModelBase. The latter already implements INotifyPropertyChanged and has a method, RaisePropertyChanged, that you can use to raise the event of a changing property.

Looking back at what the screen needs, you see that it needs a list of movies. When clicking a movie, you display the details of the movie. Previously, you did the latter by setting the DataContext of the Grid to the SelectedItem within the ListBox. Although that's fine, in the MVVM approach, you must know which movie was selected by the user (to save it later back to the service). Therefore, you also expose a SelectedMovie property on the ViewModel, to which the View can bind. Listing 4.16 shows the code for the ViewModel. Only the relevant parts are shown here for spacing reasons.

note

Listing 4.16: MovieSelectionViewModel Class (Partial)

public class MovieSelectionViewModel : ViewModelBase

{

private MovieModel _movieModel;

private Movie _selectedMovie;

private ObservableCollection<Movie> _currentMovies;

public MovieSelectionViewModel()

{

_movieModel = new MovieModel();

LoadAllCurrentMovies();

}

private void LoadAllCurrentMovies()

{

_movieModel.GetAllCurrentMovies(GetAllCurrentMoviesCallback);

}

public void GetAllCurrentMoviesCallback

(ObservableCollection<Movie> movies)

{

if (movies != null)

{

CurrentMovies = movies;

}

}

public ObservableCollection<Movie> CurrentMovies

{

get

{

return _currentMovies;

}

set

{

_currentMovies = value;

RaisePropertyChanged("CurrentMovies");

}

}

public Movie SelectedMovie

{

get

{

return _selectedMovie;

}

set

{

_selectedMovie = value;

MovieSelectedCommand.RaiseCanExecuteChanged();

RaisePropertyChanged("SelectedMovie");

}

}

}

Code file [Snowball.AtTheMovies.Silverlight.UI/ViewModel/MovieSelectionViewModel.cs] available for download at Wrox.com.

As you can see, the ViewModel knows the Model and asks it to load the Movies in the LoadCurrentMovies() method. The GetAllCurrentMoviesCallback() gets called from the mode when the asynchronous loading of the data is complete.

The View

The View is a Page or a UserControl, located in the View folder. The View for the movie selection is MovieSelectionView.xaml. The code-behind should be as clean as possible. By all means, it should not contain any code that is to be tested. The code in Listing 4.17 is all there is in the code-behind.

note

Listing 4.17: Code-Behind for the MovieSelectionView

public partial class MovieSelectionView : Page

{

public MovieSelectionView()

{

InitializeComponent();

}

}

Code file [Snowball.AtTheMovies.Silverlight.UI/View/MovieSelectionView.xaml.cs] available for download at Wrox.com.

That seems clean, doesn't it? The XAML is bound to an instance of the ViewModel. The latter exposes a CurrentMovies and a SelectedMovie property, as shown in Listing 4.18.

note

Listing 4.18: MovieSelectionView.xaml Bound to the ViewModel

<ListBox x:Name="CurrentMovieListBox"

ItemsSource="{Binding CurrentMovies}"

SelectedItem="{Binding SelectedMovie, Mode=TwoWay}">

</ListBox>

...

<TextBlock x:Name="MovieNameTextBlock"

Text="{Binding SelectedMovie.MovieName}" >

</TextBlock>

<TextBlock x:Name="MovieDescriptionTextBlock"

Text="{Binding SelectedMovie.MovieDescription}">

</TextBlock>

...

Code file [Snowball.AtTheMovies.Silverlight.UI/View/MovieSelectionView.xaml] available for download at Wrox.com.

Hold on. How does the View know which ViewModel to take? There are different answers to this question. A possible solution is to use Managed Extensibility Framework (MEF). However, you won't use MEF here. Another solution is to use a locator. A locator is a class that contains a property for each ViewModel. The code in Listing 4.19 shows the relevant part of the ViewModelLocator class, part of the solution.

note

Listing 4.19: ViewModelLocator.cs (Partial)

public class ViewModelLocator

{

private MovieSelectionViewModel _movieSelectionViewModel;

private UserDataEntryViewModel _userDataEntryViewModel;

public ViewModelLocator()

{

_userDataEntryViewModel = new UserDataEntryViewModel();

_movieSelectionViewModel = new MovieSelectionViewModel();

}

public MovieSelectionViewModel MovieSelectionViewModel

{

get

{

return _movieSelectionViewModel;

}

}

public UserDataEntryViewModel UserDataEntryViewModel

{

get

{

return _userDataEntryViewModel;

}

}

}

Code file [Snowball.AtTheMovies.Silverlight.UI/ViewModelLocator.cs] available for download at Wrox.com.

Once the locator code has been written, we need to instantiate it. A specific resource dictionary, MVVMDictionary.xaml, was created to place this instance in. Listing 4.20 shows the code for this file.

note

Listing 4.20: Setting the DataContext to a ViewModelLocator Property

<ResourceDictionary

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:vm="clr-namespace:Snowball.AtTheMovies.Silverlight.UI">

<vm:ViewModelLocator x:Key="Locator" />

</ResourceDictionary>

Code file [Snowball.AtTheMovies.Silverlight.UI/Assets/MVVMDictionary.xaml] available for download at Wrox.com.

In the XAML for the UI, you can now set the DataContext of the Page (or UserControl, depending on what you use) to the public property of the related ViewModel, as shown in Listing 4.21.

note

Listing 4.21: Setting the DataContext to a ViewModelLocator Property

<navigation:Page x:Class=

"Snowball.AtTheMovies.Silverlight.UI.View.MovieSelectionView"

...

DataContext="{Binding MovieSelectionViewModel,

Source={StaticResource Locator}}">

Code file [Snowball.AtTheMovies.Silverlight.UI/View/MovieSelectionView.xaml] available for download at Wrox.com.

You have now successfully refactored to MVVM. Now take a look at what must happen when the user interacts with a control.

At Your Command

Because you must keep the code-behind as clean as possible, you can't start adding event handlers to it. Instead, you can use commands. Using a command, you can link an action in the ViewModel to an event in the View, such as clicking a button. Since version 4, Silverlight has support for theICommand interface, which is shown in Listing 4.22.

Listing 4.22: The ICommand Interface

public interface ICommand

{

event EventHandler CanExecuteChanged;

bool CanExecute(object parameter);

void Execute(object parameter);

}

To use a command, you must write a class that implements the ICommand interface, expose an instance of this class on the ViewModel, and finally bind the Command property of a control to this property.

Although that works fine, you can fall back on MVVM Light again, which provides the RelayCommand. This class already implements the ICommand interface, so you can skip the step of writing your own implementation. In the MovieSelection screen, you must perform an action when the user clicks the Next button. You can do so by creating a RelayCommand instance in the ViewModel called MovieSelectedCommand, as shown in Listing 4.23.

note

Listing 4.23: Initializing the MovieSelectedCommand

public MovieSelectionViewModel()

{

_movieModel = new MovieModel();

LoadCommands();

LoadAllCurrentMovies();

}

public RelayCommand MovieSelectedCommand { get; set; }

private void LoadCommands()

{

MovieSelectedCommand = new RelayCommand(OnMovieSelected,

OnCanMovieSelected);

}

public void OnMovieSelected()

{

//do some action

}

public bool OnCanMovieSelected()

{

return _selectedMovie != null;

}

Code file [Snowball.AtTheMovies.Silverlight.UI/ViewModel/MovieSelectionViewModel.cs] available for download at Wrox.com.

In the LoadCommands() method, you initialize the RelayCommand. The first parameter is an Action, which is executed when the command fires. The second parameter is a Func<bool>, which returns whether the command can execute. Based on this Boolean value, a command can be enabled or disabled. Whenfalse is returned, if bound to a button, the button will be disabled.

You now need to link the command on the ViewModel with the Command property of the button. This can be done using the code shown in Listing 4.24.

note

Listing 4.24: Binding the Command Property

<Button Content="Next" Command="{Binding MovieSelectedCommand}">

</Button>

Code file [Snowball.AtTheMovies.Silverlight.UI/View/MovieSelectionView.xaml] available for download at Wrox.com.

When the button is clicked, the code in the Command (in the Execute() of the ICommand interface) is executed.

Messaging

Using the MVVM pattern, you achieve loose coupling. However, between the different ViewModels, communication must take place.

For example, in the “At The Movies” application, when going from one screen to the next, you must pass on the entered data. (The show time screen must know what movie was selected to search for the show times for the selected movie.) Solving this by creating references between ViewModels may end up in spaghetti code of references between ViewModels, and this would take away the advantage of the ease of testability.

To solve this, a mediator/messenger can be used. Using a pub/sub model, a ViewModel registers with the messenger, saying that it will publish messages of a certain type. Another ViewModel can register with that same messenger to receive messages of a certain type. When the messenger receives a message, it sends that message to all registered classes. This way, the two ViewModel classes can communicate without a hard reference between them.

MVVM Light has a Messenger class on board that you can use for this purpose. You can create instances of this Messenger, or you can use the default Messenger instance, available in the ViewModelBase. Use the latter option here. To send data from one ViewModel to another, a base or a custom type can be used. Let's use UserDataModelMessage. The definition for this class is shown in Listing 4.25. The UserDataPresentationModel type used here is just a data transfer object (DTO) class containing the user's selections to pass between screens.

note

Listing 4.25: UserDataModelMessage Class

public class UserDataModelMessage: MessageBase

{

public UserDataPresentationModel CurrentUserDataModel { get; set; }

}

Code file [Snowball.AtTheMovies.Silverlight.UI/Messages/UserDataModelMessage.cs] available for download at Wrox.com.

The code in Listing 4.26 contains the completed OnMovieSelected, part of the MovieSelectionViewModel. In this method, you register with the Messenger to send a message of the type shown in Listing 4.25. The ShowTimeSelectionViewModel parameter points to the target type. You can indicate which type must receive the message in order not to send the message to too many recipients, as shown in Listing 4.26.

note

Listing 4.26: Registering to Send a Message to the Default Messenger

public void OnMovieSelected()

{

Messenger.Default.Send<UserDataModelMessage, ShowTimeSelectionViewModel>

(new UserDataModelMessage()

{ CurrentUserDataModel = _currentUserDataModel });

}

Code file [Snowball.AtTheMovies.Silverlight.UI/ViewModel/MovieSelectionViewModel.cs] available for download at Wrox.com.

In the ShowTimeSelectionViewModel, you can register to accept this message, as shown in Listing 4.27.

note

Listing 4.27: Registering to Receive a Message from the Default Messenger

private void InitializeMessenger()

{

Messenger.Default.Register<UserDataModelMessage>

(this, OnUserDataModelMessageReceived);

}

Code file [Snowball.AtTheMovies.Silverlight.UI/ViewModel/MovieSelectionViewModel.cs] available for download at Wrox.com.

The ViewModels can now communicate without a reference between them.

Creating Customized Controls

So far, the controls you have been using are nothing more than the standard controls such as a TextBox or a button. Silverlight wouldn't be Silverlight if it could not support a way to customize these controls. To finish the sample application, let's incorporate some customized controls.

Control Templates

With standard styling, you can change only values of properties such as FontSize, Width, or HorizontalAlignment. In many cases, this is more than enough. When you want to completely redesign the control's looks, however, you won't get there. In this case, changing the control's template can help out.

Templating is the technique in which you remove the standard look for the control and replace it with a new look. The behavior is still the same. If you re-template a button, you remove the gray rectangle, including gradients, and things like the hover effect, but it still works like a button. When you click it, it still triggers an event that you can catch (either with an event handler or a Command).

To create a new template for an existing control, you can do everything manually. This includes creating a new template from scratch, linking template values with values of the control instantiation using a TemplateBinding, specifying where the content goes using a ContentPresenter, and so on. In the code in Listing 4.28, a control template is defined inside a style to create a red circular button.

note

Listing 4.28: Control Template Code

<Style x:Key="ButtonStyle" TargetType="Button">

<Setter Property="Template">

<Setter.Value>

<ControlTemplate>

<Grid>

<Ellipse Stroke="Gray" StrokeThickness="5"

Fill="Red"

Height="{TemplateBinding Height}"

Width="{TemplateBinding Width}" >

</Ellipse>

<ContentPresenter HorizontalAlignment="Center"

VerticalAlignment="Center">

</ContentPresenter>

</Grid>

</ControlTemplate>

</Setter.Value>

</Setter>

</Style>

Code file [Snowball.AtTheMovies.Silverlight.UI/Assets/SDKStyles.xaml] available for download at Wrox.com.

Although this creates a new, round button, some issues exist with this approach. For example, there's no feedback on when you are clicking or hovering. This can all be re-created, but it takes quite some work.

A better approach is to use the Visual State Manager (VSM) and the Parts & States model. This model specifies that a control can be in a specific state and can have one or more parts with which the user can interact. The states define in what situations the control can be. For a button, this can bePressed, MouseOver, and so on. The VSM takes care of bringing the control from one state to the other when needed, and also manages the transitions that must happen between the states. With the VSM, templating a control merely comes down to defining how the control should look in the various states.

This model dramatically improves how easy it is to create custom controls. If you want to do all this manually, it's possible. However, Expression Blend supports the VSM as well, making it much easier to create custom control templates.

Now look at how you can create a more movie-themed button by changing the standard gray Next button into a movie clapper with some text on it. Follow these steps:

1. As mentioned, to make things easier, work from Expression Blend. You can open the solution you already have entirely in Blend without there being any need to change anything.

2. In the MovieSelectionView.xaml, right-click the Next button, and, under Edit Template, select Create Empty. In the Resource creation dialog, set the name to MovieClapperButtonTemplate and select Application as the scope for the template.

3. In the “Objects and Timeline” panel, the ControlTemplate displays, containing just an empty Grid as its root container.

4. From the files provided on this book's companion website (www.wrox.com), download and add the action.png Movie Clapper icon to the project (preferably under the Assets folder).

5. Drag the image onto the Designer, and position it in the center of the Grid.

6. Add a ContentPresenter and center this horizontally as well. The ContentPresenter represents a placeholder for the actual content of the control. In this case, the button contained just some text (Next), so when dragged on, the Designer displays the text as well. If your control contains other content (such as nested controls), these would become the new content.

7. To ensure that the image sizes along with the size of the control, you can use a TemplateBinding. Such a binding links a value within the control template with a value on the control initialization. You must create such a binding between the Width and Height of the image and Width and Height of the control. In the Properties window, navigate to the property (Width and Height) and click the options rectangle at the far right. From the menu, select Template Binding → Width (or Height).

At this point, the control looks like Figure 4.20.

Figure 4.20 The new button

4.20

If you run the application now, the button looks as expected but doesn't give feedback on events such as MouseOver or being Pressed. To add these, open the States panel in Blend. In this panel, the states supported by the control are shown in two groups, the VisualStateGroups. A control can't be in more than one state from a group at the same time. However, it can be in a combination of states belonging to several groups.

You can now define some visuals for the states. Follow these steps:

1. Select the MouseOver state. Automatically, the Designer starts recording the state changes. Make a small change, such as rotating the image slightly.

2. Now, select the Pressed state. In the Designer, apply another change, such as resizing the image slightly to create the effect of clicking the button.

3. If you move from state X to state Y, by default, the change happens immediately, causing the transition to be unsmooth. To solve this, you can use a VisualTransition. Using VisualTransition, you can specify how long the state change should take. You can use the easy route by letting Silverlight do the interpolation, or define a custom animation for this. For most occasions, letting Silverlight perform the transition is just fine. To add it, you change the Default Transition for the entire state group, or apply a more specific transition when going or moving away from a specific state. For example, set the Default Transition to 0.2 seconds. Then, select in the MouseOver state the Add Transition button, and select the * → MouseOver. Set this to 0.5 seconds. All transitions, except the last added one, take 0.2 seconds.

With the state and transition information ready, you can test your control again. In Figure 4.21, it's slightly rotated when the mouse hovers over.

Figure 4.21 Slightly rotated button

4.21

Summary

There's no doubt that building LOB applications with Silverlight is easy. You can fully leverage your knowledge of .NET. Most concepts apply in Silverlight in the exact same way. On the other hand, some specifics require some more investigation, and after reading this chapter, you have learned about quite a few of them.

By taking a step back before starting the actual development and using a tool such as SketchFlow, you can create prototypes of your application with ease. Because it's based entirely on Silverlight, the prototypes you create this way reflect the real situation, and can help in building what the customer needs.

The data platform in Silverlight is extremely well thought-out. You learned about the data-binding capabilities of Silverlight, which also form the foundation for the MVVM pattern. This rich engine dramatically brings down the amount of code that must be written for a data-driven application. Getting in the data is no problem either. You used RIA Services to get to the data. Other types of services are available as well, so that won't hold you back in getting data into your apps.

The MVVM pattern makes the applications you build in Silverlight more testable and more maintainable. You used MVVM Light here to help you out in some areas.

About the Author

Gill Cleeren is Microsoft Regional Director (www.theregion.com), Silverlight Most Valuable Professional (MVP) (former ASP.NET MVP), and Telerik MVP. He lives in Belgium, where he works as a .NET architect at Ordina (www.ordina.be/). Passionate about .NET, he's always playing with the newest bits. In his role as Regional Director, Cleeren has given many sessions, webcasts, and trainings on new as well as existing technologies (such as Silverlight, ASP.NET, and WPF) at conferences including TechEd Berlin 2010, TechDays Belgium — Switzerland — Sweden, DevDays NL, NDC Oslo Norway, SQL Server Saturday Switzerland, Spring Conference UK, Silverlight Roadshow in Sweden, Telerik RoadShow UK, and so on. He's also the author of many articles in various developer magazines and for www.silverlightshow.net. He organizes the yearly Community Day event in Belgium. Cleeren also leads Visug (www.visug.be), the largest .NET user group in Belgium. He is also the co-author of Microsoft Silverlight 4 Data and Services Cookbook (Birmingham, United Kingdom: Packt Publishing, 2010). You can reach Gill via his blog (www.snowball.be) and via his Twitter account (@gillcleeren).