Asynchronous Master-Details with RadGridView for Silverlight and WCF RIA Services

by XAML Team | Comments 19

Inspired by Brad Abrams’ marvelous series of blog posts, I have decided to create a simple project demonstrating how to harness the enormous power of RIA Services with Telerik RadGridView for Silverlight. I have decided to use the new Chinook database after reading this wonderful post by Tim Heuer explaining how to work with relational data in the RIA Services paradigm. Make sure you have this database installed on the default instance of your SQL Server 2008 Express or you will have to modify the connection string as needed.

In one of my previous blog posts I have thoroughly explained How To Display Hierarchical Data with Row Details. Understanding Row Details is a must before you can go on.

So let’s get going.

1. Create a new Silverlight Application called MasterDetailsWithRIAServices. Host the Silverlight application in a new ASP.NET Web Application and enable .NET RIA Services.

2. In the ASP.NET Web Application create a new folder named Models.

3. Add a new “ADO.NET Entity Data Model” called “ChinookModel” to the Models folder. You can use all kinds of other models, but I decided to go with an Entity Framework model.

4. Select “Generate from database” and connect to the instance where you have installed the Chinook database. Save the entity connection settings as “ChinookEntities”.

5. Select the Album and Artist tables. The model namespace should read “Chinook Model’. The designer will then open and you should see something like this:

ChinookModel

6. Rebuild the solution.

7. In the ASP.NET Web Application create a new folder named Services.

8. Add a new “Domain Service Class” and call it “Chinook Service”.

9. Choose the “ChinookEntities” DataContext. For this demo we won’t need editing and metadata classes:

ChinookService

10. Clicking OK will generate ChinookService.cs in the Services folder.

11. Entity Framework has some weird ways of naming the generated classes so I have renamed the query methods to be in plural form, i.e. GetArtists instead of GetArtist.

12. Since we are doing a master-details demo let’s add another query method that will return all albums given the id of the artist:

Master-Details Query
  1. public IQueryable<Album> GetAlbumsForArtistId(int artistId)
  2. {
  3.     return this.ObjectContext.Album.Where(album => album.Artist.ArtistId == artistId);
  4. }

 

13. Rebuild the solution and let’s go to the client project.

14. Add references to the following Telerik assemblies:

  • Telerik.Windows.Controls
  • Telerik.Windows.Controls.GridView
  • Telerik.Windows.Controls.Input
  • Telerik.Windows.Data

and to:

  • System.Windows.Controls.Ria
  • System.Windows.Data

* You can either download the “Latest Internal Build” version of RadControls for Silverlight from your Client.NET or use the assemblies from the source code archive I have provided.

15. In the client project add a new class called ArtistIdToAlbumCollectionConverter. This is where the magic will happen. This converter creates a new DomainDataSource with the query “GetAlbumsForArtistId” we have created earlier, passes in the artistId parameter and calls the Load method of the DomainDataSource. Finally it returns the DataView which populates the details grid asynchronously:

MainPage
  1. using System;
  2. using System.Net;
  3. using System.Windows;
  4. using System.Windows.Controls;
  5. using System.Windows.Documents;
  6. using System.Windows.Ink;
  7. using System.Windows.Input;
  8. using System.Windows.Media;
  9. using System.Windows.Media.Animation;
  10. using System.Windows.Shapes;
  11. using System.Windows.Data;
  12. using MasterDetailsWithRIAServices.Web.Services;
  13.  
  14. namespace MasterDetailsWithRIAServices
  15. {
  16.     public class ArtistIdToAlbumCollectionConverter : IValueConverter
  17.     {
  18.         public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  19.         {
  20.             DomainDataSource albumsDataSource = new DomainDataSource()
  21.             {
  22.                 DomainContext = new ChinookContext(),            // Our context
  23.                 AutoLoad = false,                                // We will call Load a little bit later
  24.                 QueryName = "GetAlbumsForArtistIdQuery",        // This is the master-details query from ChinookService.cs
  25.                 LoadSize = 1,                                    // Slow things down artificially to see the asynchronous loading in action
  26.                 LoadInterval = new TimeSpan(0, 0, 0, 0, 100)    // Slow things down artificially to see the asynchronous loading in action
  27.             };
  28.  
  29.             int artistId = (int)value;
  30.             // I want only the albums for this particular artist.
  31.             albumsDataSource.QueryParameters.Add(new Parameter { ParameterName = "artistId", Value = artistId });
  32.  
  33.             albumsDataSource.Load();
  34.  
  35.             // albumsDataSource.DataView implements INotifyCollection and that is
  36.             // how the details grid is populated asynchronously.
  37.             return albumsDataSource.DataView;
  38.         }
  39.  
  40.         public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
  41.         {
  42.             throw new NotImplementedException();
  43.         }
  44.     }
  45. }

 

16. Now let’s use this converter. We will bind the ItemsSource of the details (albums) grid residing inside the RowDetailsTemplate to the ArtistId and let the converter do the rest of the job for us. Here is what the main page should look like at the end:

MainPage
  1. <UserControl
  2.     x:Class="MasterDetailsWithRIAServices.MainPage"
  3.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  4.     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5.     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6.     xmlns:my="clr-namespace:MasterDetailsWithRIAServices"
  7.     xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Ria"
  8.     xmlns:services="clr-namespace:MasterDetailsWithRIAServices.Web.Services"
  9.     xmlns:telerik="clr-namespace:Telerik.Windows.Controls;assembly=Telerik.Windows.Controls.GridView"
  10.     xmlns:telerikGrid="clr-namespace:Telerik.Windows.Controls.GridView;assembly=Telerik.Windows.Controls.GridView"
  11.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  12.     Width="700"
  13.     d:DesignHeight="480"
  14.     d:DesignWidth="640"
  15.     mc:Ignorable="d">
  16.     <Grid x:Name="LayoutRoot">
  17.         <Grid.Resources>
  18.  
  19.             <!--This is the magic converter. It will take the ArtistId and
  20.             create a DomainDataSource to asynchronously retrieve all
  21.             albums for the given ArtistId.-->
  22.             <my:ArtistIdToAlbumCollectionConverter x:Key="ArtistIdToAlbumCollectionConverter"/>
  23.  
  24.             <!-- RowDetailsTemplate containing the details (albums) grid. -->
  25.             <DataTemplate x:Key="AlbumsTemplate">
  26.                 <Grid Width="500" HorizontalAlignment="Left" Margin="24,2,2,2">
  27.                     <Grid.RowDefinitions>
  28.                         <RowDefinition Height="Auto"/>
  29.                         <RowDefinition/>
  30.                     </Grid.RowDefinitions>
  31.                     <TextBlock Grid.Row="0" Margin="2">Albums:
  32.                     </TextBlock>
  33.                     <!--Bind the albums grid to the ArtistId and let the converter
  34.                     return all albums asynchronously. -->
  35.                     <telerik:RadGridView
  36.                         Name="albumsGrid"
  37.                         ItemsSource="{Binding ArtistId, Converter={StaticResource ArtistIdToAlbumCollectionConverter}}"
  38.                         Grid.Row="1"
  39.                         Margin="2"
  40.                         AutoGenerateColumns="False"
  41.                         ColumnWidth="*"
  42.                         ShowGroupPanel="False">
  43.                         <telerik:RadGridView.Columns>
  44.                             <telerik:GridViewDataColumn Header="Album Title"
  45.                                                         DataMemberBinding="{Binding Title}"/>
  46.                         </telerik:RadGridView.Columns>
  47.                     </telerik:RadGridView>
  48.                 </Grid>
  49.             </DataTemplate>
  50.         </Grid.Resources>
  51.  
  52.         <!-- This is where the master (artists) grid gets its data from. -->
  53.         <riaControls:DomainDataSource x:Name="artistsDataSource"
  54.                                       AutoLoad="True"
  55.                                       QueryName="GetArtistsQuery">
  56.             <riaControls:DomainDataSource.DomainContext>
  57.                 <services:ChinookContext/>
  58.             </riaControls:DomainDataSource.DomainContext>
  59.         </riaControls:DomainDataSource>
  60.  
  61.         <telerik:RadGridView
  62.             Name="artistsGrid"
  63.             ItemsSource="{Binding ElementName=artistsDataSource, Path=Data}"
  64.             IsBusy="{Binding ElementName=artistsDataSource, Path=IsBusy}"
  65.             RowDetailsTemplate="{StaticResource AlbumsTemplate}"
  66.             ColumnWidth="*"
  67.             AutoGenerateColumns="False"
  68.             ShowGroupPanel="False">
  69.             <telerik:RadGridView.Columns>
  70.                 <telerik:GridViewToggleRowDetailsColumn/>
  71.                 <telerik:GridViewDataColumn DataMemberBinding="{Binding Name}" Header="Artist"/>
  72.             </telerik:RadGridView.Columns>
  73.         </telerik:RadGridView>
  74.     </Grid>
  75. </UserControl>

17. Build and run. Click on any of the row details toggle buttons.

Notice how the albums are retrieved asynchronously. Since you will be running this demo on your local machine, I have manually “slowed down” the connection so you can see what is really going on. For the master grid I have used Brad Abrams’ “hotel internet connection”© approach and for the details grid I have configured the DomainDataSource to pull data slowly.

Also, notice that when you click on a row that was already expanded, the data was cached and the row details appear immediately.

Now, let’s see what are my favorite albums, shall we:

 

You got to love RIA Services.

Here is the full source code of the demo. Enjoy!


Senior Software Developer,
Telerik XAML Team

19 Comments

Ben Hayat
Rossen, I can't believe you used Flash to demo Silverlight app. :-))

Nice!
..Ben
Rossen Hristov
Hello Ben,

Jing was the easiest tool for me to use since I was in a great hurry.
Ben Hayat
Hi Rossen;

Sorry if it came out the wrong way. I agree with you. I was sort of making a point how odd it is that in many cases we have to use "Flash" (a competing product) to show SL product. Even MSFT had done it and got lots of heat for that.

Again, excellent blog [as always]!
Rossen Hristov
Hello Ben,

I totally agree with you. I was just pressed by time to publish my blog and that is why I used Jing, which by the way is a great piece of software.

Actually, I am always using it when I have to communicate with our customers in support tickets or forum threads. You know that time matters in these cases. Sometimes words and code are just not enough, you have to see the action live. And when you want to send that response as soon as possible, Jing helps a lot. Long story short, it is just great. I just love the "Capture -> Publish -> Paste the Link" workflow. It saves a massive amount of time when you want to get that message delivered as soon as possible.

To tell you the truth, minutes after I read your comment I have noticed that the videos in my two blog posts were no longer available. I guess that they have reached some kind of maximum bandwidth limit, and since I was using the free version of Jing (I have to admit) they were no longer available, which is quite normal and expected.

For now I have migrated the two videos to YouTube, so that the integrity of the two posts stays intact and readers can actually see something until I come up with another alternative.

I am considering Silverlight Streaming for my future video needs. I think that this will drop the last piece of the puzzle in place. What do you think?
Ben Hayat
I am considering Silverlight Streaming for my future video needs. I think that this will drop the last piece of the puzzle in place. What do you think?

As much as I hate to say this, but I have to admit it. Flash in many ways is far more advance and more  mature than SL. Even though I joked with you why you're not using SL, but the reality is that Flash does certain things, that SL may never get to. [ A long discussion I had with Valio on this].
In some cases like above, sometimes is best to use what's best regardless of what technology it is, just to get the job done.
However, there is flip side to this. In your case, as employee of a company that specializes in MSFT product, particularly a vendor that is # 1 in SL, looks odd in the eyes of an outsider to see Telerik is using Flash. I just recently had this talk with Robert about Telerik.TV. Yes, it does a great job, but it gives a wrong message...

This reminds of a story that a few years ago, when Afghan people were fighting Soviet Union, they were looking to buy and use Russian arms to fight the Russians. Why, because they knew the Russian arms are the best and gets the job done. Now we are using Flash to fight Adobe... :-))

..Ben
Rossen Hristov
Hello Ben,

The Silverlight platform is developing so rapidly, and I am quite sure that soon enough we will be able to embrace in its full extent. 2010 and Silverlight 4 will be very important for the Silverlight community, without any doubt.

Also, I do not think that different technologies have to fight each other. They can work together.

As an example, Microsoft SQL Server Reporting Services and Telerik Reporting are both capable of exporting to Adobe's PDF file format. PDF is just popular and that is why it is supported by both products. Various technologies and platforms can work closely together in order to deliver a final product and I do not see anything wrong in that.
JAC
Hi, How can we pass two binding values to ArtistIdToAlbumCollectionConverter from the outer grid and  how do we parse them within the converter.
? Could you please comment on that.
Rossen Hristov
Hello JAC,

I could not understand your question. What do you mean by "two binding values"?  The converter simply receives an artist id and returns all the albums for this artist.

In case you need something else besides the id of the artist, you can bind to the whole artist. This is done either with "Path=." or by not specifying the property name.

ItemsSource="{Binding Path=., Converter={StaticResource ArtistIdToAlbumCollectionConverter}}"

Now, in the converter (which you better rename) you will receive the whole artist instance, instead of simply its id. Cast the value parameter to Artist, read what you need to read and do your thing according to that.

In case this is not what you are asking for then please elaborate.
JAC
Rossen,

Thank you, That is exactly what I was looking for. In a sample I was trying, I needed more than one value from the object.
Ron Frick
I am trying to find out if you can use the radgridview as the default control, for drag and drop data binding.   I really like the fact that you can drag and drop data sources on to the designer and it will generate the template for the columns but it uses the default grid view that ships with the toolkit, and I would like to change it to the radgridview.  I have tried using the "customize" option but the radgrid will not show up as a control to use.  Thanks....and yes "You got to love RIA Services."
Wim
One answer to Ron.

When I put a radgrid on my screen i can just drop the Query on it and that works just fine.

rgds

Wim
Great Post!

It helped me a lot

Thx
Pieter Claassens
Very nice article Rossen!

I want to use your idea in my project, but I am a little bit stuck....

I want to extend the example to call  a 'GetAlbumsCountForArtistId' method from my domain service.  (it just returns a integer say, 5, for testing purposes..)

I seems like a really straightforward requirement, but I am really struggling to actually get this right. 

I followed the above article and tried the way they call the domain service method - but I keep on getting a The type ChinookContext does not expose a method called 'GetAlbumsCountForArtistId'. Argument exception. I assumed that because this does not return IQueryable, I need to decorate it with [Invoke].

The problem is , now because this is an async call - how to I return the value from my converter , back to my RadGrid or whatever ?

========

I've tried:

=======

delegates  - it works , but the integer I initialise in the begging is returned, instead of  the value from my callback method  (containing my dummy '5' value form the domain service)

 c.GetAlbumsCountForArtistId(id, delegate(InvokeOperation<Int32> inv)
               {
                   Count = inv.Value; // Count is a public property in the converter class

               }, null);

====

I've tried a normal call back method like this that updates a property of the class:

 

int Count { get; set; }

        private void OnInvokeCompleted(InvokeOperation<int> invOp)
        {
            if (invOp.HasError)
            {
                MessageBox.Show(string.Format("Method Failed: {0}", invOp.Error.Message));
                invOp.MarkErrorAsHandled();
            }
            else
            {
                Count = invOp.Value;
            }
        }

...

..

 

from my Converter method...

------------------------------------

InvokeOperation<int> invokeOp = c.GetAlbumsCountForArtistId(id,OnInvokeCompleted,null);

 

====

 

I've tried putting a while loop with sleep() but then the callback method never gets executed...

 

     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {

         ..

         ..

            while (Count == 0)
            {
                System.Threading.Thread.Sleep(1000);

            }
            return Count;
      }

 

Hells bells, I've even tried a normal WCF web service, but I cannot get the thing to accept sync calls - it only allows async....

Is there actually a way to do this ?  Guys from the silverlight forums mentioned Attached Properties, but I first need to read up on how they work etc.

Carsti
Very nice, I have used it in my project and it works fine. But one thing. I always see my expand/toggle cross, the problem is that not each ID of the parentgrid returns records for the childgrid. What can I do? The example with "France" and "Brazil" of the telerik-silverlight-demo doesn´t work for me with your example.

greetz Carsti
Carsti
Very nice, I have used it in my project and it works fine. But one thing. I always see my expand/toggle cross, the problem is that not each ID of the parentgrid returns records for the childgrid. What can I do? The example with "France" and "Brazil" of the telerik-silverlight-demo doesn´t work for me with your example.

greetz Carsti
Rossen Hristov
I am afraid that I cannot think of a solution for this case.
Ron Frick

I was able to set this up very easily..thank you! My question is I have a scenario that is not a one to many like in the example but a many to many. So I would like to setup a user control that I can use in the data tempate instead of just coding the radgrid in the data template. This way I can have a way of drilling into the data becasue each "user control" would also have the async setup using a converter. The problem is that when I set this up and pass the converter to my grid in my user control the convert method in the converter does not fire. Not sure why. I can send you a sample project if you need to see what I am saying.
Here is a small snippet

  <Grid x:Name="LayoutRoot" >  
        <Grid.Resources>  
            <TechPark:VehcileUIDToCustomerSummaryConverter x:Key="VehcileUIDToCustomerSummaryConverter"/>  
            <TechPark:VehcileUIDToPermissionSummaryConverter x:Key="VehcileUIDToPermissionSummaryConverter"/>  
            <!-- RowDetailsTemplate containing the details (albums) grid. -->  
            <DataTemplate x:Key="DetailsTemplate">  
                <StackPanel>  
                    <my1:CustomerSummaryHierarchy   Data="{Binding VehUID, Converter={StaticResource VehcileUIDToCustomerSummaryConverter}}"  />  
                    <my1:PermissionSumTest Data="{Binding VehUID, Converter={StaticResource VehcileUIDToPermissionSummaryConverter}}" />  
 
                </StackPanel>  
            </DataTemplate>  
        </Grid.Resources>  
 
 
        <StackPanel >  
            <StackPanel Orientation="Horizontal" >  
                <Image Source="/TechPark;component/Images/CarGrey-icon.png" Height="27"   />  
                <TextBlock FontSize="14" Height="21"   x:Name="txtSummary" Text="Vehicle Summary">  
                  
                </TextBlock>  
                <telerik:RadButton Visibility="Collapsed"  Margin="5,0,0,0" x:Name="Expand" Click="Expand_Click" Width="23" Height="22">  
                    <Image Source="/TechPark;component/Images/RadTreeView_Silverlight.png" x:Name="ExpandImage">  
 
                    </Image>  
                </telerik:RadButton>  
                 
            </StackPanel>  
            <StackPanel >  
 
 
                <telerik:RadGridView Name="VehicleGrid"  AlternationCount="2"  AlternateRowBackground="LightGray" 
                    RowDetailsTemplate="{StaticResource DetailsTemplate}"  CanUserResizeColumns="False" CanUserReorderColumns="False" CanUserInsertRows="False" CanUserFreezeColumns="False" CanUserDeleteRows="False" AutoGenerateColumns="False" ShowGroupPanel="False" RowIndicatorVisibility="Collapsed" IsReadOnly="True"   IsSynchronizedWithCurrentItem="False"  FontSize="12" SelectionChanged="radGridView1_SelectionChanged" Visibility="Collapsed"   RowLoaded="VehicleGrid_RowLoaded" >  
                     
 
                    <telerik:RadGridView.Columns>  
                        <telerik:GridViewToggleRowDetailsColumn/>  
                        <telerik:GridViewDataColumn UniqueName="Status" Header=""  />  
                        <telerik:GridViewDataColumn   Header="Plate" DataMemberBinding="{Binding Plate}"/>  
                        <telerik:GridViewDataColumn   Header="Type" DataMemberBinding="{Binding Type}"/>  
                        <telerik:GridViewDataColumn  Header="Description" DataMemberBinding="{Binding Description}"/>  
                        <telerik:GridViewDataColumn  Header="EndDate" DataMemberBinding="{Binding EndDate}"/>  
                    </telerik:RadGridView.Columns>  
                    <telerik:RadGridView.GridViewGroupPanel>  
                        <telerik:GridViewGroupPanel Visibility="Collapsed" />  
                    </telerik:RadGridView.GridViewGroupPanel>  
 
                    
 
                </telerik:RadGridView>  
 
            </StackPanel>  
        </StackPanel>  
        
 
    </Grid> 

As you can see I have a user control(CustomerSummaryHierarchy ) in the data template and I want to pass the results of the converter to the property "Data" but I am unsuccessful.
Thanks for any advice as to how I can pull this off.
Ron
Ron Frick
Update...Got the converter to fire off. I made the "Data" property of my control a dependency property and the convert method fires off when the row is expanded, but the data is not getting return.
Ron Frick
Got it!!! I should really stop programming at 3 am. Thanks for the example this is awsome!!! 

Comments

  1.    
      
      
       
  2. (optional, emails won't be shown on public pages)
  3. (optional)
Read more articles by XAML Team - or - read latest articles in Developer Tools