Telerik blogs

After we prepared our data service it is time to put some client UI into it. I must apologize in advance as the only UI that I’ve used is a ListBox control, and it looks… well pretty much ugly. The goal was to proof that the whole setup works as expected, not to design a profound UI solution. I am pretty sure that as a follow up, our SilverLight team will enhance the sample to a full-scale integration example very soon.

To start with, add a SilverLight application in your solution:

 

image

 

In the process of adding you will have to setup where the SilverLight will be hosted:

image

This is also doable from the settings of the web project: invoke the context menu on your web site project then go to Property Pages > SilverLight Applications > New… and add/setup the SilverLight project.

image

 

After we successfully have made the setup, we can start enhancing our SilverLight application.

The first thing to notice is inside the code-behind class generated for the data service. It includes two classes named :

public partial class OADataContext : global::System.Data.Services.Client.DataServiceContext

and

public partial class Product

The first class implements the client-side data context (you can name it a proxy if you want) that is the entry point for the service. The Product class is the entity that is being handled by the service.

To be able to track changes to the entities that are processed in the SilverLight we have to implement the INotifyPropertyChanged interface for the Product entity. It is not implemented by default; my guess is because that way the code is not polluted with an enormous set of auto-generated overrides or partial implementations. We can do so in a partial class. Just add a class file named ProductPartial.cs, and inside define the partial class like:

public partial class Product : INotifyPropertyChanged

What we have generated inside the Product class are properties like:

 

/// <summary>
 /// There are no comments for Property ProductName in the schema.
 /// </summary>
 public string ProductName
 {
     get
     {
         return this._ProductName;
     }
     set
     {
         this.OnProductNameChanging(value);
         this._ProductName = value;
         this.OnProductNameChanged();
     }
 }
 private string _ProductName;

 

The basic pattern is to not allow overrides of the properties, but rather implement partial methods that have their signatures generated for example OnProductIDChanging(value) and OnProductIDChanged() that are called prior to and after setting the field value. We are interested only in the event that gets fired after the value is changed, so we will override for example the OnProductNameChanged method so that it will fire the change notification event:

partial void OnProductNameChanged()
{
    FirePropertyChanged("ProductName");
}

/// <summary>
///
Fires the property changed.
/// </summary>
/// <param name="propertyName">
Name of the property.</param>
void FirePropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

 

We iterate the operation for all Product properties that we know will be changed, and we require the change to be persisted on the server. Actually there is a lot of “data plumbing” code to be written in SilverLight, but I am sure the guys at Microsoft will address this issue pretty soon.

 

So after we implemented the missing part of our Product entity, lets see how the actual UI is bind and behaves. What I have put on the page is a simple ListBox that shows the products and two buttons – the first one changes the ProductName property of the first product in the list, and the second one pushes the change back to the server.

 

The definition of the listbox is the bare minimum to show data:

XAML

<ListBox x:Name="listBox1"
    HorizontalAlignment="Stretch" 
    Grid.RowSpan="1" 
    Grid.Row="0" VerticalAlignment="Stretch" 
    ItemsSource="{Binding Mode=OneWay}"  >

    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel x:Name="DisplayListData"
             Orientation="Horizontal"
             VerticalAlignment="Bottom" 
             Margin="5" >
                <TextBlock x:Name="ProductID"
                 Text="{Binding ProductID}"
                 Margin="5,0,0,0" 
                 VerticalAlignment="Bottom"
                 HorizontalAlignment="Left"
                 FontSize="12">
                </TextBlock>

                <TextBlock x:Name="ProductName"
                 Text="{Binding ProductName}"
                 Margin="5,0,0,0" 
                 VerticalAlignment="Bottom"
                 HorizontalAlignment="Left"
                 FontSize="12">
                </TextBlock>

                <TextBlock x:Name="UnitPrice"
                 Text="{Binding UnitPrice}"
                 Margin="5,0,0,0" 
                 VerticalAlignment="Bottom"
                 HorizontalAlignment="Left"
                 FontSize="12">
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

and the two buttons:

XAML

<Button x:Name="Push" Grid.Row="1">
    <TextBlock x:Name="BProductName"
                 Text="Change object named {Binding ProductName} to ModifiedXXX"
                 Margin="5,0,0,0" 
                 VerticalAlignment="Bottom"
                 HorizontalAlignment="Left"
                 FontSize="12">
    </TextBlock>
</Button>
<Button x:Name="Save" Grid.Row="2">
    <TextBlock x:Name="SaveText"
                 Text="Save Changes to the server using ADS."
                 Margin="5,0,0,0" 
                 VerticalAlignment="Bottom"
                 HorizontalAlignment="Left"
                 FontSize="12">
    </TextBlock>
</Button>

 

The first thing that we do inside the code-behind is to initialize the data service context in the constructor:

public Page()
{
    InitializeComponent();

    // Create the Service class specifying the location of the ADO.NET Data Services 
    context = 
      new OADataContext(new Uri("OADataService.svc", UriKind.Relative));

    // We use the Async pattern (BeginExecute/EndExecute) everywhere because oft the disconnected nature of
    // the application.
    context.BeginExecute<Product>(new Uri("Products", UriKind.Relative), loadProductsCallback, context);
}

 

What you really have to understand is that we always use asynchronous way of working with data in SilverLight, because of the nature of the data services that we use. The real job is done in the callback method that gets executed when the service logic finishes processing the communication part:

private void loadProductsCallback(IAsyncResult asyncResult)
{
    DataServiceContext ctx =
        asyncResult.AsyncState as DataServiceContext;

    this.products = ctx.EndExecute<Product>(asyncResult).ToList();

    foreach (Product product in products)
    {
        // Wireup Change Notification
        product.PropertyChanged +=
          new PropertyChangedEventHandler(product_PropertyChanged);
    }
    this.listBox1.DataContext = products; // ctx.EndExecute<Product>(asyncResult);
    this.Push.Click += new RoutedEventHandler(Push_Click);
    this.Save.Click += new RoutedEventHandler(Save_Click);
}

The most important part was the logic that wires the PropertyChanged  event of every entity to be tracked down (pretty messy isn't it?). The logic that gets executed updates the state of the entity inside the service's data context:

void product_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    Product product = (Product)sender;
    context.UpdateObject(product);
}

 

Also in the callback we prepare a list out of the query, set the list as a data provider to the  listbox's DataContext property, wire the buttons events and are ready to roll.

How about pushing the changes back to the server? Well it is pretty straight-forward too - we initiate an asynchronous operation again, and provide a callback that implements the real operation:

void Save_Click(object sender, RoutedEventArgs e)
{
    context.BeginSaveChanges(SaveChangesOptions.ContinueOnError,
                                new AsyncCallback(OnSaveAllComplete),
                                null);
}

 

and here is the callback:

void OnSaveAllComplete(IAsyncResult result)
{
    bool succeeded = true;
    try
    {
        DataServiceResponse response =
          (DataServiceResponse)context.EndSaveChanges(result);

        foreach (OperationResponse opResponse in response)
        {
            if (opResponse.Error != null)
            {
                succeeded = false;
            }
        }

    }
    catch (Exception ex)
    {
        succeeded = false;
    }

    // Alert the User
}

 

Well that was all of it - no fancy stuff, but what we accomplished so far is great: we pushed data back and forth through an Ado.Net DataService, we had a SQL database that was handled through OpenAccess on the server side, and on the client-side we had a SilverLight application as a front-end. Stay tuned as I expect that the SilverLight team will cook a real case and way more slick (in terms of UI) example for all of us.

Enjoy!


About the Author

Dimitar Kapitanov

Dimitar Kapitanov is Team Lead in Telerik Platform Team

Comments

Comments are disabled in preview mode.