Looking at Silverlight and MVVM for the first time

As an exercise, I’ve decided to start looking at building Silverlight RIA’s as concepts that can be quickly delivered to clients. However, with any new technology, learning some of the patterns can be the most difficult part of the entire process.

One of the most agreed upon patterns for Silverlight is called Model View View Model (MVVM) and is focused on separation of concerns for each class. The basic concept is as follows:

  1. Model

    This is a representation of the data. This can be generated for you by any number of frameworks or via WCF contracts. You can also generate your own POCO models as well.

  2. View

    The view is concerned with presenting information and allowing the end-user to interact with that data. The view in Silverlight is always your XAML.

  3. ViewModel

    The ViewModel is concerned with keeping things synchronized between the view and the model. It’s an in-between layer that intercepts events and relays those events to the appropriate class. The ViewModel will implement the interface INotifyPropertyChanged which declares a single event called PropertyChanged. This notifies the View that the underlying data has changed and automatically synchronizes the data between the view and the model.

Project

For this exercise, I also decided that I would start looking into the new Windows Phone 7 development. I’m taking a project from Scott Guthrie and converting it to MVVM. I’m also using information found in this article, which got me started but needed some additional changes for my liking. The final project will appear as follows:

The basic idea is the user will provide the name of a twitter user and click the search button. The application will call out to Twitter and attempt to get the user’s public timeline. Once the timeline has been retrieved, the results will be displayed in a list box below.

The Model

The model for this application only contains the information necessary for the list box.

    public class TwitterItem
    {
        public string UserName { get; set; }
        public string Message { get; set; }
        public string ImageSource { get; set; }

        public static List GetTweetsFromResponse(string Response)
        {
            List lsttweets = new List();

            XElement xmlTweets = XElement.Parse(Response);

            lsttweets = (from tweet in xmlTweets.Descendants("status")
                         select new TwitterItem
                         {
                             ImageSource = tweet.Element("user").Element("profile_image_url").Value,
                             Message = tweet.Element("text").Value,
                             UserName = tweet.Element("user").Element("screen_name").Value
                         }).ToList();

            return lsttweets;
        }
    }

The model also contains a single static method to parse a result from the twitter API.

The View

The view for this application starts with a simple windows phone page. I’ve included some additional namespaces to the GalaSoft MVVM Light library because it gives a great way to wire up events from the View into the ViewModel.

<phone:PhoneApplicationPage 
    x:Class="TwitterMiniClient.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
        <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Twitter Mini Client" Style="{StaticResource PhoneTextNormalStyle}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBox Name="Search" VerticalAlignment="Top" Margin="0,0,130,0"></TextBox>
            <ProgressBar Name="Loading" IsIndeterminate="True" VerticalAlignment="Top" Margin="0,62,0,0"></ProgressBar>
            <Button Name="StartSearch" VerticalAlignment="Top" HorizontalAlignment="Right" Content="Search" Width="140">
            </Button>
            <ListBox Name="Results" Margin="15,70,15,15">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="5,5,5,20">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="64"/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Image Grid.Column="0" Height="64" Width="64" VerticalAlignment="Top" Margin="0,10,8,0" />
                            <Grid Grid.Column="1">
                                <Grid.RowDefinitions>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <TextBlock Grid.Row="0" Style="{StaticResource PhoneTextGroupHeaderStyle}" />
                                <TextBlock Grid.Row="1" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" />
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>


</phone:PhoneApplicationPage>

Now, there are some changes that will be required in the view after the ViewModel is created.

ViewModels

In order to provide common functionality to multiple view models, it is a good practice to create a base view model that implements the INotifyPropertyChanged interface. This base class also has a method to show when it is in a designer tool so sample data can be generated

    public class ViewModelBase : INotifyPropertyChanged
    {
        public bool IsInDesignMode { get { return DesignerProperties.IsInDesignTool; } }

        protected void OnNotifyPropertyChanged(string p)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(p));
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }

Now, I inherit from the base view model (ViewModelBase) and create a view model that corresponds with the main page

    public class MainViewModel : ViewModelBase
    {
        public MainViewModel()
        {
            IsLoading = false;
            if (!this.IsInDesignMode)
            {
                GetTweetsCommand = new RelayCommand<string>((s) =>
                {
                    Title = s + " Tweets";
                    GetTweetsCall(s);
                });
            }
            else
            {
                ObservableCollection<TwitterItem> SampleData = new ObservableCollection<TwitterItem>();
                for (int idx = 0; idx < 10; idx++)
                {
                    SampleData.Add(new TwitterItem()
                    {
                        UserName = "SampleUser",
                        Message = "Sample Message #" + idx + ". Lorem ipsum dolor color ebit alslit laughit.",
                        ImageSource = ""
                    });
                }
                Tweets = SampleData;
                Title = "SampleUser Tweets";
            }
        }
        public string UserName { get; set; }
        public RelayCommand<string> GetTweetsCommand { get; private set; }

        private ObservableCollection<TwitterItem> _Tweets;
        public ObservableCollection<TwitterItem> Tweets
        {
            get
            {
                return _Tweets;
            }
            set
            {
                if (_Tweets != value)
                {
                    _Tweets = value;
                    OnNotifyPropertyChanged("Tweets");
                }
            }
        }

        private string _Title;
        public string Title
        {
            get
            {
                return _Title;
            }
            set
            {
                if (_Title != value)
                {
                    _Title = value;
                    OnNotifyPropertyChanged("Title");
                }
            }
        }

        private bool _loading;
        public bool IsLoading
        {
            get
            {
                return _loading;
            }
            set
            {
                if (_loading != value)
                {
                    _loading = value;
                    OnNotifyPropertyChanged("IsLoading");
                }
            }
        }

        private void GetTweetsCall(string UserName)
        {
            WebClient client = new WebClient();
            IsLoading = true;
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(twitter_Response);
            client.DownloadStringAsync(new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=" + UserName));
        }

        private void twitter_Response(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                Tweets = TwitterItem.GetTweetsFromResponse(e.Result).ToObservableCollection();
                IsLoading = false;
            }
        }
    }

There is a lot of code in the view model, so let’s break it down a little to explain what’s going on in each step.

Handling Events from the View

In order to handle events from the view, you must use a class that implements the ICommand interface. Thankfully, the GalaSoft MVVM Toolkit provides a really good implementation of this, so I just simply add a reference and then create a property to hold the relay command. The application will be taking in a string variable to determine the name of the user to retrieve, so it is typed to accept a string.

        public RelayCommand<string> GetTweetsCommand { get; private set; }
Constructor

Getting the command initialized begins with creating a new instance of the RelayCommand and assigning it to the property. However, this should only be done when the page is not in a designer.

        public MainViewModel()
        {
            IsLoading = false;
            if (!this.IsInDesignMode)
            {
				// Create a new relay command of type string and assign it to the property of the view model.
                GetTweetsCommand = new RelayCommand<string>((s) =>
                {
					// The variable S will come from a command argument
                    Title = s + " Tweets";
					// Makes a call to a method responsible for contacting twitter to get the results.
                    GetTweetsCall(s);
                });
            }
            else
            {
				// We are dealing with a designer, so setup some sample data for the designer to view.
                ObservableCollection<TwitterItem> SampleData = new ObservableCollection<TwitterItem>();
                for (int idx = 0; idx < 10; idx++)
                {
                    SampleData.Add(new TwitterItem()
                    {
                        UserName = "SampleUser",
                        Message = "Sample Message #" + idx + ". Lorem ipsum dolor color ebit alslit laughit.",
                        ImageSource = ""
                    });
                }
                Tweets = SampleData;
                Title = "SampleUser Tweets";
            }
        }
Calling Twitter Asynchronously

All service calls in Silverlight are asynchronous. This is to keep the UI responsive even when waiting for long periods of time. Two methods are needed to call Twitter asynchronously. The first one sets up the call and wires up a callback delegate. The second one parses the results and places then into an ObservableCollection

        private void GetTweetsCall(string UserName)
        {
            WebClient client = new WebClient();
			// This is an INotifyPropertyChanged property that will let the UI know data is being retrieved.
            IsLoading = true;
			// Setup the event delegate for when the request has completed.
            client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(twitter_Response);
			// Begin the asynchronous download of data from Twitter.
            client.DownloadStringAsync(new Uri("http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=" + UserName));
        }

        private void twitter_Response(object sender, DownloadStringCompletedEventArgs e)
        {
			// Always check for an error! A real application will need to include error handling back to the UI.
            if (e.Error == null)
            {
				// Using the static method declared on the model, the results are parsed into a List<TwitterItem>. Then, using an
				// extension method these are converted into an ObservableCollection.
                Tweets = TwitterItem.GetTweetsFromResponse(e.Result).ToObservableCollection();
				// Let's the UI know we've finished loading results.
                IsLoading = false;
            }
        }
Extension Methods, IValueConverter

Finally, to wrap things up with the view model, we need to create an extension method and an IValueConverter so the boolean property IsLoading can be converted into Visible and Collapsed when dealing with the progress bar on the page

The extension method simply extends an IEnumerable to allow it to be converted into an ObservableCollection

    public static class ObservableListExtentions
    {
        public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> enumerable)
        {
            ObservableCollection<T> col = new ObservableCollection<T>();
            foreach (T item in enumerable)
            {
                col.Add(item);
            }
            return col;
        }

    }

The IValueConverter is a simple implementation of BooleanToVisibilityConverter and converts a true/false value into the corresponding visibility values

    public class BooleanToVisibilityConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return ((bool)value) ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            return ((Visibility)value) == Visibility.Visible;
        }
    }
Returning to the View

Now that the view model has been defined, the view can be revisited for binding. First, namespaces need to be added to the opening tag of the phone application

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:viewModels="clr-namespace:TwitterMiniClient.ViewModels"
    xmlns:converters="clr-namespace:TwitterMiniClient"
    xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7"

The first reference is to the System.Windows.Interactivity DLL and must be added as a reference to the project. The next two are the ViewModel and the Value Converted created for the project. The final reference is to the GalaSoft MVVM Light toolkit. This will also need to be added as a reference to your project

Now that the references are in place, we need to create some resources for the user control. These will be references to the MainViewModel and the BooleanToVisibilityConverter

    <UserControl.Resources>
        <viewModels:MainViewModel x:Key="ViewModel"/>
        <converters:BooleanToVisibilityConverter x:Key="VisibilityConverter"/>
    </UserControl.Resources>

Now we can setup the data context for the LayoutRoot Grid

<Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding Source={StaticResource ViewModel}}">

Now, you can setup the remaining bindings for the list items. Set the binding of the ListBox ItemsSource to the Tweets property of the ViewModel. You can opt to do this visually if desired since the ViewModel is now a resource of the user control.

After the ListBox has an ItemsSource binding, you can move on to the inner controls and set their bindings as well.

            <ListBox Name="Results" Margin="15,70,15,15" ItemsSource="{Binding Path=Tweets}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="5,5,5,20">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="64"/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Image Grid.Column="0" Height="64" Width="64" VerticalAlignment="Top" Margin="0,10,8,0" Source="{Binding Path=ImageSource}" />
                            <Grid Grid.Column="1">
                                <Grid.RowDefinitions>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <TextBlock Grid.Row="0" Text="{Binding Path=UserName}" Style="{StaticResource PhoneTextGroupHeaderStyle}" />
                                <TextBlock Grid.Row="1" Text="{Binding Path=Message}" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" />
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>

Finally, the hardest part is to wire-up the commands. This is where the MVVM Light toolkit comes in with the System.Windows.Interactivity DLL.

                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cmd:EventToCommand Command="{Binding GetTweetsCommand}" CommandParameter="{Binding ElementName=Search, Path=Text}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>

The interaction is set to map the button click to the GetTweetsCommand of the view model. Setting the CommandParameter to the Search box’s Text property passes in the user name to find.

Finally, setting up the progress bar binding appears as follows:

            <ProgressBar Name="Loading" IsIndeterminate="True" VerticalAlignment="Top" Margin="0,62,0,0" 
			    Visibility="{Binding Path=IsLoading, Converter={StaticResource VisibilityConverter}}"></ProgressBar>

Here is the full source of the View

<phone:PhoneApplicationPage 
    x:Class="TwitterMiniClient.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    xmlns:viewModels="clr-namespace:TwitterMiniClient.ViewModels"
    xmlns:converters="clr-namespace:TwitterMiniClient"
    xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">
    <UserControl.Resources>
        <viewModels:MainViewModel x:Key="ViewModel"/>
        <converters:BooleanToVisibilityConverter x:Key="VisibilityConverter"/>
    </UserControl.Resources>
        <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent" DataContext="{Binding Source={StaticResource ViewModel}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="Twitter Mini Client" Style="{StaticResource PhoneTextNormalStyle}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <TextBox Name="Search" VerticalAlignment="Top" Margin="0,0,130,0"></TextBox>
            <ProgressBar Name="Loading" IsIndeterminate="True" VerticalAlignment="Top" Margin="0,62,0,0" Visibility="{Binding Path=IsLoading, Converter={StaticResource VisibilityConverter}}"></ProgressBar>
            <Button Name="StartSearch" VerticalAlignment="Top" HorizontalAlignment="Right" Content="Search" Width="140">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <cmd:EventToCommand Command="{Binding GetTweetsCommand}" CommandParameter="{Binding ElementName=Search, Path=Text}" />
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <ListBox Name="Results" Margin="15,70,15,15" ItemsSource="{Binding Path=Tweets}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="5,5,5,20">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="64"/>
                                <ColumnDefinition/>
                            </Grid.ColumnDefinitions>
                            <Image Grid.Column="0" Height="64" Width="64" VerticalAlignment="Top" Margin="0,10,8,0" Source="{Binding Path=ImageSource}" />
                            <Grid Grid.Column="1">
                                <Grid.RowDefinitions>
                                    <RowDefinition/>
                                    <RowDefinition/>
                                </Grid.RowDefinitions>
                                <TextBlock Grid.Row="0" Text="{Binding Path=UserName}" Style="{StaticResource PhoneTextGroupHeaderStyle}" />
                                <TextBlock Grid.Row="1" Text="{Binding Path=Message}" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}" />
                            </Grid>
                        </Grid>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </Grid>
    </Grid>
 
    <!--Sample code showing usage of ApplicationBar-->
    <!--<phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button1.png" Text="Button 1"/>
            <shell:ApplicationBarIconButton IconUri="/Images/appbar_button2.png" Text="Button 2"/>
            <shell:ApplicationBar.MenuItems>
                <shell:ApplicationBarMenuItem Text="MenuItem 1"/>
                <shell:ApplicationBarMenuItem Text="MenuItem 2"/>
            </shell:ApplicationBar.MenuItems>
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>-->

</phone:PhoneApplicationPage>
Advertisements

Data Error using REST in SharePoint 2010

I recently began a project in which I wanted to consume and use the REST data services offered by SharePoint 2010 only to find a wonderful error waiting for me to be discovered. Browsing to the service (http://<server>/_vti_bin/listdata.svc)  in a web browser provided this response:

image

After some fairly quick Google searches, I discovered there is a service pack release that must be installed to enable this service. The name of the service pack release is ADO.NET Data Services Update for .NET Framework 3.5 SP1 for Windows 7 and Windows Server 2008 R2 and can be found at the following URL.

http://www.microsoft.com/downloads/en/details.aspx?familyid=79d7f6f8-d6e9-4b8c-8640-17f89452148e&displaylang=en

Installing this update will require a reboot of the server. Once the service pack has been installed and the server rebooted, I received a much different response:

image

This means the service is now setup and running correctly. You should be able to point any VS studio project to it and retrieve the list of results.

image