Wednesday, April 1, 2009

Silverlight 3.0, Astoria, LINQ to SQL (2): DataForm CRUD

In the last post, I wrote about how to build/prepare the solution, projects, and some ground work in LINQ to SQL and connecting it to ADO.NET Data Service. Now in this post, I am going to continue in building the Silverlight application using Silverlight 3.0 DataForm.

We will first add the service reference to our ADO.NET Service that we built from our Silverlight application. Then how to hook it up to our DataForm so it will display the first record by default.

1. Add a service reference to the ADO.NET Data Service ("NorthwindDataService.svc"). Run the solution with "NorthwindDataService.svc" as the Start page. Once it loads, copy the URL in the location bar and close the browser.

On the "Northwind.Silverlight" project, right click on "Service References" and click "Add Service Reference". Paste the URL you copy to clipboard into the "Address" field and click "Go". It should then populate the "Services" left pane. Hit OK.


2. Create the XAML. We need to add a DataForm to our XAML. Now, I do not have Expression Blend, so I did mine using Visual Studio. First of all, you need to add a reference to System.Windows.Controls.Data.DataForm. Your XAML should look like this:


<UserControl x:Class="Northwind.Silverlight.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:df="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="450">
<Grid x:Name="LayoutRoot" Background="White">
<df:DataForm x:Name="dfCustomer"></df:DataForm>
</Grid>
</UserControl>

3. Bind the data to the DataForm control. We need to bind the data to our dataform. There are multiple ways on how to go about doing this. In this post I bind the data as soon as the Silverlight application is loading. Or, you can create a button (or whatever event) to initiate the bind. Open "MainPage.xaml.cs" and create a handler method for the "Loaded" event. In this method, basically I will query the data using our data service reference and then bind the result to our dataform.

public partial class MainPage : UserControl
{
Uri baseUri = new Uri("http://localhost:10403/NorthwindDataService.svc", UriKind.Absolute);
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);

var query = (DataServiceQuery<Customer>)
from c in northwindDSContext.Customers
orderby c.ContactName
select c;

query.BeginExecute((asyncResult) =>
{
try
{
IEnumerable<Customer> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Customer> data = new ObservableCollection<Customer>();
foreach (Customer p in result)
{
data.Add(p);
}
dfCustomer.DataContext = data;
dfCustomer.ItemsSource = data;
});
}
catch (Exception ex)
{
throw ex;
}
}, null);
}
}
Now, we do we need to do all that? It looks overly complicated for just doing a data binding. Why not just do a LINQ query and then bind the result directly? What's "BeginExecute" for? The answer to all that really boils down to because the call to the data service is being down asynchronously. Therefore, the callback is needed. If you look into the method signature of "BeginExecute", you will see that the first parameter is a callback function.

So let's break it down ... see the line where it does this?

query.BeginExecute((asyncResult) => { ... }, null);
That line basically is executing the query and then upon completion of the query and getting the result back (stuffed into the param "asyncResult"), run this generic method (stuff inside the curly braces).

So let's take a look the callback function (stuff inside the curly braces).

IEnumerable<Customer> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Customer> data = new ObservableCollection<Customer>();
foreach (Customer p in result)
{
data.Add(p);
}
dfCustomer.DataContext = data;
dfCustomer.ItemsSource = data;
});
The first line is the "closer" for the async call and it will return a result as an IEnumerable. Next we then using Dispatch.BeginInvoke (because we are transferring data over web service) get the data one by one and put them into an ObservableCollection. Then after that we bind our DataForm to the ObservableCollection.

So far so good? In the next post: Add, Edit, Delete!

2 comments:

Anonymous said...

I see you are referencing the NorthwindDBDataContext (defined in the Northwind.DalLink project) from the Northwind.Silverlight project:

void MainPage_Loaded(object sender, RoutedEventArgs e)
10.
{
11.
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);
.
.
.
}

How did you do this? I am unable to figure out a simple way of doing this.

Johannes Setiabudi said...

I use LINQ to SQL and setup - read here - http://setiabud.blogspot.com/2009/03/silverlight-30-astoria-linq-to-sql-1.html