Export (0) Print
Expand All

Consuming ADO.NET Data Services with WPF and Visual Basic 2008

By Alessandro Del Sole – Microsoft MVP  

Download the code

Introduction

ADO.NET Data Services (formerly known as “Project Astoria”) is a new data platform introduced by Microsoft .NET Framework 3.5 Service Pack 1. ADO.NET Data Services can expose data, such as ADO.NET Entity Data Models, to the Internet or a local Intranet and provide a simple, unified model for data exchange between a server and several clients.

ADO.NET Data Services are REST-enabled WCF services and allow querying data via HTTP requests. With Data Services we can expose different kinds of data sources (e.g. in-memory collections or entities from an Object Model) but the most common scenario for data-centric applications is exposing entities from an Entity Data Model provided by the ADO.NET Entity Framework.

For example, we can retrieve a list of customers from a Data Service by simply writing the following URI inside the Internet Explorer addresses bar:

http://LocalHost/Northwind.svc/Customers

The following URI retrieves all the orders made by customer ALFKI, sorted by the order date:

http://LocalHost/Northwind.svc/Customers(‘ALFKI’)/Orders?orderby=OrderDate

These kinds of queries are called query strings and can be also used by the clients in managed code, as we will see in the second part of this article.

Once created and deployed, ADO.NET Data Services can be consumed by several kinds of client applications like Windows applications (Windows Forms, Windows Presentation Foundation or Console) or Web applications (Silverlight, AJAX, ASP.NET Web applications). Clients can then manage data using a specific LINQ provider, called LINQ to ADO.NET Data Services. In this article I will show you how to create an ADO.NET Data Service and how to consume it from a Windows Presentation Foundation client, focusing on the execution of CRUD (Create-Retrieve-Update-Delete) operations over entities exposed by an Entity Data Model.

I will not dive deep into the server side of ADO.NET Data Services, so I suggest you to read this great article by Mike Flasko (called Using Microsoft ADO.NET Data Services) before proceeding with this document. Reading this article you will learn the basics of ADO.NET Data Services, including architecture, ways to expose data, security issues, service operations, query interceptors and custom exceptions.

We will expose a master-details relationship through an ADO.NET Data Service derived from some Northwind database tables mapped into an Entity Data Model. Then we will create a WPF application for presenting and modifying data. You should be relatively familiar with the fundamentals of Windows Communication Foundation, Windows Presentation Foundation and ADO.NET Entity Framework technologies before reading this article.

Creating the ADO.NET Data Service

We will first create an empty solution in Microsoft Visual Studio 2008 Service Pack 1 and then we will add two projects, a Web application and a WPF application. Start the IDE and select the File|New|Project command. When the New Project window appears, expand the Other Project Types folder, then the Visual Studio Solutions sub-folder and select the Blank Solution template, as shown in Figure 1.

Astoria_Figure1.jpg

Figure 1 – Selecting the Blank Solution template

Name the new solution as ConsumingDataServicesFromWPF, as shown in Figure 1, and click OK. The first step required for exposing an ADO.NET Data Service is creating a Web application. Select the File|Add|New Project command and in the New Project window expand the Visual Basic folder then click the ASP.NET Web Application template, as shown in Figure 2.

Figure 2 – Selecting the ASP.NET Web Application project template

Name the project as NorthwindDataService and click OK. After a few seconds the new project is created. At this point we need to establish a connection to the Northwind database. For the sake of simplicity, in the companion code sample I have added the Northwind.mdf database file to the App_Data folder, but you can instead establish a connection to SQL Server via the Server Explorer window if you have attached Northwind to SQL Server. Once we have established the data connection, the next step is adding an Entity Data Model based on ADO.NET Entity Framework. Right click the project in Solution Explorer and select Add|New item from the pop-up menu. When the Add New Item window appears, just select the ADO.NET Entity Data Model item template as shown in Figure 3.

Figure 3 – Adding a new Entity Data Model

Name the Entity Data Model (EDM) Northwind.edmx and click Add. This task will start the Entity Data Model Wizard. In the first step of the wizard we just need to specify that the EDM will be generated from an existing database. In the second step, we have to choose the data connection properties. Select the active database connection from the upper combo box and verify in the Entity connection string box that the connection string is correct. You can accept the default name of the entity connection setting in the Web.config. Figure 4 shows the execution of this step.

Figure 4 – Choosing the data connection

Click Next to go to the next step of the wizard, where we can specify the database objects that we want to map into the Entity Data Model. For example, we could choose the Customers, Orders and Order Details tables as shown in Figure 5.

Figure 5 – Choosing the database objects

We can also specify the Model Namespace but for this example we’ll just accept the default name. Now click Finish to complete the wizard. After a few seconds you will be able to see the Visual Studio Entity Designer showing the new entities derived from the database tables, as shown in Figure 6.

Figure 6 – The Entity Framework designer

If you are familiar with the Entity Framework designer, you already know that the Navigation Properties represent relationships while Scalar Properties are a managed way to represent columns from tables and that you can use the Properties Window to inspect and modify the properties.

Now it’s time to add the ADO.NET Data Service to the project. As we said at the beginning of the article, Data Services are REST-enabled Windows Communication Foundation services. This means that without ADO.NET Data Services we could implement a WCF service by writing code to enable REST serialization and by defining the service contracts ourselves. However, Visual Studio 2008 comes with an item template for ADO.NET Data Services so we don’t need to implement a custom WCF service, instead we just need a couple lines of code to indicate how entities in our data model should be exposed.

Now right-click the project name in Solution Explorer and select Add|New Item. When the Add New Item window appears, select the ADO.NET Data Service item template and name the new item as Northwind.svc, as shown in Figure 7.

Figure 7 – Adding a new ADO.NET Data Service

At this point Visual Studio will add a new ADO.NET Data Service to our project which is the .svc file that you can see in Solution Explorer. Now let’s switch to the code view by double clicking the Northwind.svc file. You will see the Visual Basic code shown in Listing 1.

Imports System.Data.Services
Imports System.Linq
Imports System.ServiceModel.Web
Public Class Northwind
    ' TODO: replace [[class name]] with your data class name
    Inherits DataService(Of [[class name]])
    ' This method is called only once to initialize service-wide policies.
    Public Shared Sub InitializeService(ByVal config As IDataServiceConfiguration)
        ' TODO: set rules to indicate which entity sets and service operations are visible,
        ' updatable, etc.
        ' Examples:
        ' config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead)
        ' config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All)
    End Sub
End Class

Listing 1

This is the basic code for an ADO.NET Data Service. The Northwind class inherits from System.Data.Services.DataService(Of T), which is the base class for a Data Service. The DataService class exposes everything we need on the server side. T represents the data source which in our case is the class deriving from the Entity Framework ObjectContext class (in our case it is called NORTHWNDEntities). So replace the [[class name]] as shown in Listing 2.

Public Class Northwind
    ' TODO: replace [[class name]] with your data class name
    Inherits DataService(Of NORTHWNDEntities)

Listing 2

Now it’s time to establish what data we want to expose to the web through our Data Service and what access rules must be assigned to the data.

Setting data access rules

In real world applications it’s not uncommon to set specific permissions or restrictions to data access and this should be always followed as a best practice. For example, you could grant read-only access to a particular table and read/write access to another one. ADO.NET Data Services allow developers to set permissions for one or more entities on the server side, both for reading and writing data.

Setting entity access rules can be accomplished using the SetEntityAccessRule method, which receives two arguments: the entity name and the permissions level. In our scenario we have to set permissions for the Customers, Orders and Order_Details entities. We want only the Orders and Customers entities to be modified and updated on the client side. So we will replace the commented SetEntityAccessRule method provided by Visual Studio 2008 as shown in Listing 3.

config.SetEntitySetAccessRule("Customers", EntitySetRights.All)
config.SetEntitySetAccessRule("Orders", EntitySetRights.All)
config.SetEntitySetAccessRule("Order_Details", EntitySetRights.AllRead)

Listing 3

The EntitySetRights enumeration provides a number of permissions that you can set according to your needs. In this case we set read-only permissions for Order_Details entity and full permissions for the Customer and Orders entities. ADO.NET Data Services also provide another way to allow or restrict data access on the server side through what is called Service Operations.

Exposing service operations

Service operations are Windows Communication Foundation extensions and are exposed as .NET methods for data access on the server side. For example, you could use service operations for implementing your custom business logic or custom validation rules. Service operations are decorated with the WebGet or WebInvoke attributes. The first one is used for returning data while the second one is used for create/update/delete operations. For example, we can implement a service operation for querying and returning the order details of a given order. The query result will be returned as an IQueryable(Of Order_Details) object. The code for our service operation is shown in Listing 4.

<WebGet()> Public Function GetOrderDetailsByOrderId(ByVal OrderID AsInteger) _
                           As IQueryable(Of Order_Details)
    'A simple custom validation rule based on the ID length
    If OrderID.ToString.Length = 5 Then
        Dim queryResult = From orderDetail In Me.CurrentDataSource.Order_Details _
                          Where orderDetail.OrderID = OrderID _
                          Order By orderDetail.Quantity _
                          Select orderDetail
        Return queryResult
    Else
        Throw New DataServiceException("The specified order ID is invalid")
    End If
End Function

Listing 4

Because we are retrieving data, our method is decorated with the WebGet attribute. This will cause an HTTP GET request on the server side. The method body implements a very basic custom validation rule based on the OrderID length. In the case the length is correct the method returns a collection of Order_Details objects sorted by orders quantity. As you can see we are retrieving data via a LINQ query. This is because we can use a new LINQ provider for querying data exposed by ADO.NET Data Services and that is called (as you may imagine) LINQ to ADO.NET Data Services which is available both on the server side and on the client side.

A very important object reference is CurrentDataSource which represents the ObjectContext instance that is being used to process the web request. You probably noticed that we didn’t need to instantiate the NORTHWINDEntities context explicitly. This is because the ADO.NET Data Services engine instantiates the ObjectContext for us, so CurrentDataSource represents the context instance on the server side.

In order to expose the service operation GetOrderDetailsById to clients we need  to set permissions the same way we did for the entities. Setting permissions to service operations can be accomplished using the SetServiceOperationAccessRule method which works like SetEntityAccessRule but is specific to service operations. Our method just returns data, so we can set permissions as shown in Listing 5 inside the InitializeService method.

config.SetServiceOperationAccessRule("GetOrderDetailsByOrderId", ServiceOperationRights.AllRead)

Listing 5

Setting the AllRead value for the ServiceOperationRights enumeration will allow accessing data just for reading. Service operations are very useful methods because they can return different CLR types, such as primitive types, IQueryable(Of TEntity) or IEnumerable(Of TEntity). You should also note that service operations must be implemented as public instance methods.

At this point we are ready to test our service from within a Web browser like Microsoft Internet Explorer.

Testing the service

We are now ready to check if our service works properly. Press F5 and after a few seconds you should see your Web browser showing an XML representation of the three entities, as shown in Figure 8. Please remember to disable the RSS view in your Web browser. In case of Microsoft Internet Explorer 7, choose the Tools|Internetoptions command and then the Content tab where you will find the Feed settings.

Figure 8 – The ADO.NET Data Service is properly running

As we said at the beginning of the article, ADO.NET Data Services can be queried using query strings in HTTP Get requests. For example, we can retrieve a one-to-many relationship obtaining all the orders made by the CACTU customer, sorting orders according to the ShipCity property. We can accomplish this by writing the following URI in the Web browser addresses bar:

http://localhost:1653/Northwind.svc/Customers('CACTU')/Orders?OrderBy=ShipCity

The result of this query is shown in Figure 9.

Figure 9 – Retrieving a one-to-many relationship using a query string

The last example I want to provide shows how to call service operations on the server side. According to the service operation we implemented before, we could retrieve all the order details related to the OrderID 10500. We can do this by typing the following URI:

http://localhost:1653/Northwind.svc/GetOrderDetailsByOrderId?OrderID=10500

As you can see, the service operation’s name is part of the URI and can receive query string arguments according to the URI syntax for Data Services. This query string will produce the result shown in Figure 10.

Figure 10 – Using a service operation on the server side

As you can see scrolling the page, you have successfully obtained a list of all the order details for the desired order.

Now we can be sure that our ADO.NET Data Service is working properly. But it would surely make more sense for us to reference the Data Service from a client application and provide a user interface for consuming data. This is what we’re going to do in the next section.

For further information about query strings and the server side of ADO.NET Data Services, please read Mike Flasko’s “Using ADO.NET Data Services” article.

Creating the WPF client

Client applications that consume ADO.NET Data Services reference the ADO.NET Data Services Client library, and can be both Windows applications and Web applications. In this article we will create a Windows Presentation Foundation client. For the sake of simplicity, let’s add the new project to our existing solution. We need a WPF project in Visual Basic, as shown in Figure 11.

Figure 11 – Adding a new WPF project to the solution

We can simply name our new project WpfClient. The first step is adding a service reference to the ADO.NET Data Service just like we would to any other Windows Communication Foundation service. So, right-click the project name in Solution Explorer and select Add service reference. In the Add service reference dialog we must provide the service URI. In this case the service resides in the same solution and is hosted by the ASP.NET development server so we can just click the Discover button and Visual Studio will automatically list all the WCF services included in the solution (of course just one in our case). Click the service name so that the ASP.NET runtime will host the service itself then specify NorthwindServiceReference as the namespace identifier. Figure 12 shows the result of all the above mentioned tasks.

Figure 12 – Adding a service reference to the ADO.NET Data Service

In a real world application the service will be probably hosted by a Web site, so you will just need to change the URI pointing to the service address (e.g.: http://www.datasite.com/MyService.svc). After a few seconds the service reference is added (you can check this in Solution Explorer) which generates the entities that are exposed by the Data Service. It also adds a reference to the client library, System.Data.Services.Client, which is the client proxy used to access the data service. The client library handles the details of mapping LINQ statements to a URI in the data service and retrieving the specified entities as .NET objects.

Now we’re going to build a user interface like the one shown in Figure 13.

Figure 13 – The client application’s user interface

As you can see, the application will show master-details relationships for Customers and Orders and will allow CRUD operations (Create/Read/Update/Delete) onto the Orders table. Moreover, you will be able to show the order details for each order. Figure 14 shows an example of the order details window.

Figure 14 – The order details window of the client application

At last, Figure 15 shows the window we will implement for insert operations.

Figure 15 – Adding a new order via user interface

As you can see, the user interface is quite simple. Although we decided to create a WPF application we don’t need enhanced graphical effects or animations because in this case we want to focus on how we can consume ADO.NET Data Services from WPF clients and on WPF data-binding techniques. We can now begin writing code, providing useful methods for CRUD operations against the ADO.NET Data Service using the client library.

Implementing methods for CRUD operations

It’s a good idea to create a separate class for providing methods that will run CRUD operations so that your code can be easily reused. This approach is also used in the code samples provided by the Microsoft.NET Framework 3.5 Enhancements Training Kit (check it out if you didn’t before, because it contains lots of slides, demos and labs about ADO.NET Data Services). First let’s add a new class named Helper.vb to our client. Let’s begin by adding a couple of Imports directives (see Listing 6).

'Needed for service's objects
Imports WpfClient.NorthwindServiceReference
'Needed for handling Data Services exceptions
Imports System.Data.Services.Client
The next step is declaring the ObjectContext object that we can instantiate in the constructor, as shown in Listing 7.
Public Class Helper
    Public NorthwindContext As New NORTHWNDEntities(New Uri("http://localhost:1653/Northwind.svc"))

Listing 7

When instantiating the data service client’s DataServiceContext (in this case NORTHWNDEntities) we need to pass an argument of type Uri which represents the address of the ADO.NET Data Service which is the same we specified when we added the service reference. It also is a good idea to store the Uri in the configuration settings inside the App.config file. For this example, we need to implement a pair of methods for retrieving customers and orders. We can easily accomplish this by using LINQ to ADO.NET Data Services, as shown in Listing 8.

Public Function GetCustomers() As  _
                IQueryable(Of Customers)
    'LINQ to DataServices in action:
    Dim customerQuery = From customer In Me.NorthwindContext.Customers _
                        Order By customer.ContactName _
                        Select customer
    Return customerQuery
End Function
Public Function GetOrders(ByVal Customer _
                          As Customers) As IQueryable(Of Orders)
    'Eager loading with the Expand method
    Dim ordersQuery = From order In Me.NorthwindContext.Orders.Expand("Customers") _
                      Where order.Customers.CompanyName = Customer.CompanyName _
                      Order By order.OrderDate _
                      Select order
    Return ordersQuery
End Function

Listing 8

The GetCustomers method simply returns an IQueryable(Of Customers) which is the result of a really simple LINQ query. The GetOrders method is a little more interesting because it shows a particular technique called eager loading. This technique allows retrieving data of a one-to-many relationship and the ADO.NET Entity Framework provides a method called Expand to accomplish this. This method is exposed from each entity set. The Expand method is invoked against the “many” part of the relationship and receives, as an argument, the entity set that represents the “one” part of the relationship. Orders are sorted by the OrderDate property while the filter is done by comparing the Customer.CompanyName property. This happens because the user will be able to select the customer in the UI choosing the company name.

Now we can write code for the CUD (Create-Update-Delete) operations. The first two operations we want to handle are the Update and Delete. Listing 9 shows the code that I will explain below.

Public Sub DeleteOrder(ByVal Order As Orders)
    Try
        Me.NorthwindContext.DeleteObject(Order)
        Me.NorthwindContext.SaveChanges()
    Catch ex As DataServiceRequestException
        Throw New DataServiceRequestException(ex.InnerException.Message)
    Catch ex As ArgumentNullException
        Throw New ArgumentNullException("No row was selected.")
    End Try
End Sub

Public Sub UpdateOrder(ByVal Order As Orders)
    Try
        Me.NorthwindContext.UpdateObject(Order)
        Me.NorthwindContext.SaveChanges()
    Catch ex As DataServiceRequestException
        Throw New DataServiceRequestException(ex.InnerException.Message)
    Catch ex As ArgumentNullException
        Throw New ArgumentNullException("No row was selected.")
    End Try
End Sub

Listing 9

Both the UpdateOrder and DeleteOrder methods receive an argument of type Orders, which is the entity to be updated or deleted. They will respectively invoke the UpdateObject and DeleteObject methods that will tell the data service client to either mark the entity for update or delete. The SaveChanges method tells the data service client to send these entity changes to the ADO.NET Data Service which uses the Entity Framework to modify the underlying database. Since both methods will be invoked by the presentation window, if the user clicks the associated buttons without selecting any order then a null object will be passed as an argument. This is why we are handling an ArgumentNullException. The exception is re-thrown to the caller that will handle it in a user-friendly way. The same technique is used regarding the DataServiceRequestException that is thrown by the service if HTTP requests fail.

It’s worth mentioning that we also have the option of sending changes in batch, meaning that a group of operations can be sent via HTTP to the service. This is a good choice when we have multiple entities to be submitted or updated in a single database transaction and it reduces the number of requests we send to the service. So instead of calling SaveChanges after every operation, we can call it once to send all updates, inserts and deletes in one shot. In our case this could be accomplished by exposing a SaveChanges method from the Helper class, passing the System.Data.Services.Client.SaveChangesOptions.Batch argument. This is not necessary in our example because we are not sending order details, but if we did, then we would want to implement this additional method. Further information about sending changes in batch can be found in the MSDN Library.

At this point we need to write code for adding a new order to the database through the data service. Listing 10 shows the implementation of a new method called AddNewOrder.

Public Sub AddNewOrder(ByVal Order As Orders)
    Try
        Me.NorthwindContext.AddToOrders(Order)
        'Explicitly setting relationships
        Me.NorthwindContext.SetLink(Order, "Customers", Order.Customers)
        Me.NorthwindContext.SaveChanges()
    Catch ex As DataServiceRequestException
        Throw New DataServiceRequestException(ex.InnerException.Message)
    Catch ex As ArgumentNullException
        Throw New ArgumentNullException("Cannot add a null object.")
    End Try
End Sub

Listing 10

In the AddNewOrder method there is some interesting code. Notice that there is an AddTo method for each entity exposed by the service. This was automatically generated on the NorthwindEntities DataServiceContext when we added the ADO.NET Data Service reference to our client. The AddToOrders method will add a new Order entity to the collection. Also notice that we must explicitly set the relationship between the newly added order and its corresponding customer. This is accomplished by using the SetLink instance method that receives three arguments: the new entity’s instance (in this case a new Order), a string for the EntitySet representing the “one” association in the one-to-many relationship, and the .NET property that represents the “one” association in the one-to-many relationship in which the new entity (Order) belongs.

Note: You may be confused because the single association is called Customers and not Customer. This is because the names of the tables in the Northwind database are not singular. The names of the entities and the associations can easily be changed on the data model using the Visual Studio Entity Data Model designer and then rebuilding and updating the service reference on the client.

At this point we can implement a method that will call the service operation for retrieving the order details related to a particular order. As we said before, service operations that just read and return data are just simple HTTP GET requests to the service. The ADO.NET Data Services framework provides an overload of the Execute(Of TElement) method for HTTP GET requests. This method receives an argument of type Uri that is represented by a query string containing the name of the service operation. So we could implement our method as shown in Listing 11.

    Public Function QueryOrderDetails(ByVal OrderId As Integer) As IQueryable(Of Order_Details)
        Try
            Return Me.NorthwindContext.Execute(Of Order_Details) _
                                          (New Uri("GetOrderDetailsByOrderId?OrderID=" + _
                                          OrderId.ToString, UriKind.Relative)).AsQueryable
        Catch ex As Exception
            Throw
        End Try
    End Function
End Class

Listing 11

The Execute(Of TElement) method returns an IEnumerable(Of T), where T is an entity. Since a cast to IQueryable(Of T) is needed we can use the AsQueryable extension method for this purpose. The Uri argument is a string that we can build combining the service operation name and the OrderID.

Now it’s time to build the user interface.

Implementing value converters

Often users submit data as plain text, via the UI. Such text is often a representation of a .NET type; you may think of an order date submitted by the user and which must be first validated and then represented in the UI in the appropriate format. When developing WPF applications, you can implement valueconverters to control the input and output formatting. Value converters are .NET classes which provide custom logic for binding data to the UI and converting an Object into another .NET data type. The result of the conversion can be bound to the UI writing a XAML markup extension. Using value converters as opposed to simpler display formatting gives you full control over the input values users will enter. Value converter classes implement the IValueConverter interface, which requires two methods to be implemented: Convert and ConvertBack. In our case we need to convert order dates submitted as strings by the user and bind the result of such conversion to the UI, so that dates will be recognized as DateTime objects instead of plain text. At this point we need to add a class to our project (Project|Add class) and name the class DateConverter.vb. Code for the DateConverter class is shown in Listing 12.

<ValueConversion(GetType(String), GetType(DateTime))> _
Public Class DateConverter
    Implements IValueConverter
    Public Function Convert(ByVal value As Object, _
                            ByVal targetType As System.Type, _
                            ByVal parameter As Object, _
                            ByVal culture As System.Globalization.CultureInfo) _
                            As Object Implements System.Windows.Data.IValueConverter.Convert
        If value IsNot Nothing Then
            Dim ResultDate As DateTime = CType(value, DateTime)
            Return ResultDate
        Else
            'If the value is null, returns an empty string
            Return String.Empty
        End If
End Function
    Public Function ConvertBack(ByVal value As Object, _
   ByVal targetType As System.Type, _
   ByVal parameter As Object, _
   ByVal culture As System.Globalization.CultureInfo) As Object _
   Implements System.Windows.Data.IValueConverter.ConvertBack
        'We know we are receiving strings (via TextBox)
        Dim CurrentValue As String = CStr(value)
        Dim ResultDate As DateTime = Nothing
        If DateTime.TryParse(CurrentValue, ResultDate) Then
            Return ResultDate
        Else
            'A default value is returned in case of invalid date
            Return DateTime.Now
        End If
    End Function
End Class

Listing 12

Adding a ValueConversion attribute to the value converters definition is a best practice, because it allows specifying what data types will be involved in the conversion. Both the Convert and ConvertBack methods provide logic for converting strings into the appropriate DateTime representation. Notice how the ConvertBack method will return a default value (DateTime.Now) in case the parsing of the original string doesn’t match a valid date representation. We will see the usage of this converter later in the UI implementation.

Building the user interface

We’ll begin implementing the user interface starting from the order details window which is the simplest window among the ones we saw above. This can be useful to understand some data-binding techniques in Windows Presentation Foundation. The first step is adding a new WPF window, so right click the project name in Solution Explorer and select the Add|New window command. When the Add New Item dialog appears, just specify a name for the new window (e.g. ViewOrderDetails.xaml) as shown in Figure 16.

Figure 16 – Adding a new WPF window to the project

Our new window will look like what we saw in Figure 14, so we need to divide the window itself into two parts. The top portion of the window will contain a ListView for showing tabular data and below that there will be a button to close the window. To accomplish this we can declare a Grid control and split it into two rows. Listing 13 shows the beginning of the new window’s XAML code, where you can see how the window title has been changed and how the Grid and a Button are declared.

<Window x:Class="ViewOrderDetails"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="View order details" Height="300" Width="350">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Button Grid.Row="1" Width="100" Height="30" Content="Close" Name="CloseButton"
                Click="CloseButton_Click"/>

Listing 13

Now we have to define how data will be presented to the user. For showing tabular data we can use the ListView control. When using the ListView we can declare a DataTemplate for each cell, so that we can define how data must be presented (e.g. if we have a Boolean value to be shown we can declare a CheckBox to show it). Let’s begin by writing the code shown in Listing 14 which declares the appearance of the ListView (see comments inside the code).

<!--The ItemsSource is data-bound
    but suspended and will be populated at runtime-->
<ListView ItemsSource="{Binding}"
          Grid.Row="0"
          Name="OrdeDetailsListView"
          Margin="5" >
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <!--This will allow each column
                to be stretched to fit its content-->
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <!--This will ensure currency between the
                selected item and the data source-->
            <EventSetter Event="GotFocus"
                         Handler="Item_GotFocus"/>
        </Style>
    </ListView.ItemContainerStyle>

Listing 14

The most important thing in the above code is the ItemsSource property assignment. Using the {Binding} markup extension we are establishing that the property is data-bound but the data source will be assigned at runtime via Visual Basic code. Now we can implement a ListView custom template for showing tabular data. This can be accomplished by assigning a GridView item to the ListView.View object. The GridView will contain as many GridViewColumn objects as many properties we want to show. For each GridViewColumn we can define a DataTemplate object that represents the way data will be presented to the user. Everything we discussed is shown in Listing 15.

            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Product ID">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=ProductID}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Unit price">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=UnitPrice, StringFormat=c}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Quantity">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=Quantity}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                    <GridViewColumn Header="Discount">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Path=Discount, StringFormat=p}"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Window>

Listing 15

Notice how data-binding is established by the Binding markup extension indicating the name of the property in which to bind. This means that regardless of the name and type of the data source (in this case an Order_Details entity) WPF will data bind to the value of the specified property. We can now switch to the Visual Basic code editor and complete the code for this window. The code is shown in Listing 16 (see comments for explanations).

Imports WpfClient.NorthwindServiceReference
Partial Public Class ViewOrderDetails
    'The OrderID will be passed by the caller
    '(the main window)
    Public Sub New(ByVal OrderID As Integer)
        InitializeComponent()
        Dim helperClass As New Helper
        Me.OrdeDetailsListView.ItemsSource = helperClass.QueryOrderDetails(OrderID)
    End Sub
    Public Sub New(ByVal Source As IQueryable(Of Order_Details))
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.
        'This will populate the ListView
        Me.OrdeDetailsListView.ItemsSource = Source
    End Sub
    'Set correspondence between the selected item
    'and the data source
    Private Sub Item_GotFocus(ByVal sender As System.Object, _
               ByVal e As System.Windows.RoutedEventArgs)
        Dim item = CType(sender, ListViewItem)
        Me.OrdeDetailsListView.SelectedItem = item.DataContext
    End Sub
    Private Sub CloseButton_Click(ByVal sender As System.Object, _
                                   ByVal e As System.Windows.RoutedEventArgs)
        Me.Close()
    End Sub
End Class

Listing 16

Notice that we modified the constructor to receive an OrderID argument (passed by the caller) of type Integer which we pass to the Helper.QueryOrderDetails method. Invoking this method will return an IQueryable(Of Order_Details) object that will constitute the data source that will populate the ListView. The data source is then assigned to the OrderDetailsListView.ItemsSource property which sets up the data binding.

The second window we need to create for the user interface is the one for submitting new orders. For the sake of simplicity, I will show you how to create and submit a new order setting just a few properties for each order. You will then be able to extend the code and set the full range of properties for each order. Please follow the same steps shown before in order to add a new window to the project called NewOrder.xaml. According to the appearance we saw in Figure 15, we can implement the UI in a very simple way with the XAML code shown in Listing 17.

<Window x:Class="NewOrder"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfClient"
    Title="Insert a new order" Height="300" Width="300">
    <Window.Resources>
        <local:DateConverter x:Key="dateConverter"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <TextBlock Grid.Row="0" Text="Please specify new order's properties:" />
        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <StackPanel Grid.Row="0" Orientation="Vertical">
                <TextBlock Margin="2" Text="Order Date:"/>
                <TextBox Margin="2" Name="OrderDateTextBox"
                         Text="{Binding Path=OrderDate,
                         Converter={StaticResource dateConverter}, ConverterParameter='d'}" />
            </StackPanel>
            <StackPanel Grid.Row="1" Orientation="Vertical">
                <TextBlock Margin="2" Text="Required Date:"/>
                <TextBox Margin="2" Name="RequiredDateTextBox"
                         Text="{Binding Path=RequiredDate,
                         Converter={StaticResource dateConverter}, ConverterParameter='d'}"/>
            </StackPanel>
            <StackPanel Grid.Row="2" Orientation="Vertical">
                <TextBlock Margin="2" Text="Shipped Date:"/>
                <TextBox Margin="2" Name="ShippedDateTextBox"
                         Text="{Binding Path=ShippedDate,
                         Converter={StaticResource dateConverter}, ConverterParameter='d'}"/>
            </StackPanel>
            <StackPanel Grid.Row="3" Orientation="Vertical">
                <TextBlock Margin="2" Text="Ship City:"/>
                <TextBox Margin="2" Name="ShipCityTextBox"
                         Text="{Binding Path=ShipCity}"/>
            </StackPanel>
        </Grid>
        <StackPanel Grid.Row="2" Orientation="Horizontal">
            <Button Width="100" Height="30" Content="Submit" Margin="5"
                    Name="SubmitButton" Click="SubmitButton_Click" />
            <Button Width="100" Height="30" Content="Cancel" Margin="5"
                    Name="CancelButton" Click="CancelButton_Click" />
        </StackPanel>
    </Grid>
</Window>

Listing 17

With the StackPanel object we can arrange child elements into a single line both horizontally and vertically. As you can see we have just implemented controls for receiving information by the end user. Such information will populate the new order’s properties.

At this point we must focus on other interesting techniques demonstrated in the above XAML. First we imported an Xml namespace called local pointing to our assembly. This will allow us to use classes defined in Visual Basic code also in the XAML code. You should rebuild the project to ensure that the namespace is correctly recognized by the IDE. Second, in the Window resources we defined a dateConverter identifier which allows us to call in XAML code the DateConverter class that we previously declared in Visual Basic (please remember that identifiers in XAML are case sensitive). Also notice how the text property for each TextBox control is set via data-binding. The Binding XAML markup extension has an attribute called Path pointing to the related property of each Order object that must be shown. Take a look at the usage of the value converter we implemented before; for the three TextBoxes that receive dates a Converter attribute is specified. This attribute needs another XAML markup extension which points to the converter class (StaticResource) while the ConverterParameter indicates what kind of format must be applied to the returned data (in this case ‘d’ means a short date format). Implementing such techniques will be reflected upon properties set for each new order in the main Window.

Now let’s switch to the Visual Basic code editor and type the code shown in Listing 18 which will initialize the window and set event handlers for the buttons.

Imports WpfClient.NorthwindServiceReference
Partial Public Class NewOrder
    Private _order As Orders
    Public Property Order() As Orders
        Get
            Return _order
        End Get
        Set(ByVal value As Orders)
            _order = value
            'Binds the controls to this order
            Me.DataContext = _order
        End Set
    End Property
    Private CurrentCustomer As Customers
    Public Sub New(ByVal Customer As Customers)
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        ' Add any initialization after the InitializeComponent() call.
        Me.CurrentCustomer = Customer
    End Sub
    Private Sub SubmitButton_Click(ByVal sender As System.Object, _
                                    ByVal e As System.Windows.RoutedEventArgs)
        Me.Order.Customers = Me.CurrentCustomer
        Me.DialogResult = True
        Me.Close()    End Sub
    Private Sub CancelButton_Click(ByVal sender As System.Object, _
                                    ByVal e As System.Windows.RoutedEventArgs)
        Me.DialogResult = Nothing
        Me.Close()
    End Sub

Listing 18

As you can see in Listing 18, the constructor receives the Customer object that is the owner of the new order, so that we can set a relationship between the Customer and the new order when the SubmitButton is clicked. We also implemented an Order property that is submitted to the underlying database which is also bound to the Customer. We also assign a dialog result before closing the window, depending on the user’s choice. In Windows Presentation Foundation the DialogResult is of type Nullable(Of Boolean), so a Nothing value means that the user canceled the operation while a True value means that the user completed the operation.

When clicking the Submit button, the AddNewOrder of our helper class is invoked.

Obviously you can implement more complex and efficient validation rules. Regarding this, I suggest you to read this blog post by Beth Massi where she discusses how you can implement custom data validation in WPF forms and this one where she talks about WPF converters.

Now we can finally design the main window of the application. In Solution Explorer double click the Window1.xaml code file to activate the WPF designer. Please refer to Figure 13 in the following steps related to the UI implementation. First of all we can change the Window’s Title property and declare a Grid which will contain some children controls: a nested Grid where we will put a description and a ComboBox for the customer’s selection, a ListView for presenting and manipulating data, a StackPanel that will contain buttons for invoking CRUD operations. Listing 19 shows how to declare the first two Grids, the description and the ComboBox.

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Manage your orders" Height="350" Width="650">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="150"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="Please select a customer: " />
            <ComboBox Grid.Column="1" Margin="5"
                      DisplayMemberPath="CompanyName"
                      Name="CustomersCombo" />
        </Grid>

Listing 19

We will populate the ItemsSource of the ComboBox control via Visual Basic code to the Customers entity set. Another interesting property is DisplayMemberPath which will show the value of the specified property name in the data bound collection. In other words, if we assign a collection of Customers as a data source to the ComboBox.ItemsSource property, then we need to specify what property of the collection can be shown by the ComboBox itself for each item in the collection. This is accomplished by using the DisplayMemberPath property.

Similar to what we did in the ViewOrderDetails window, we can now declare a ListView object for tabular data. In this case we will use several TextBox controls for each DataTemplate object, because Orders entities can be modified according to the permissions we set on the server side. Listing 20 shows the code for the ListView.

<ListView ItemsSource="{Binding}" Grid.Row="1" Name="OrdersListView" Margin="5" >
    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            <EventSetter Event="GotFocus" Handler="Item_GotFocus"/>
        </Style>
    </ListView.ItemContainerStyle>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Order ID">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding Path=OrderID}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Order Date">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=OrderDate}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Freight">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=Freight}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Required Date">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=RequiredDate}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Ship Address">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=ShipAddress}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Ship City">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=ShipCity}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Ship Country">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=ShipCountry}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Ship Region">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=ShipRegion}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Ship Postal Code">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=ShipPostalCode}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Ship Name">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=ShipName}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Shipped Date">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=ShippedDate}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

Listing 20

TextBox controls allow the so-called “two-way” data-binding. This means that data-bound objects can display their properties as well as have them modified via the data bound UI controls. In our example we have mapped all the properties in the Orders entity to ListView columns but you could also decide to restrict the mapping. Only the OrderID ListView column is read-only (in fact we declared a TextBlock object) because that is the primary key and is managed by the SQL Server engine even when submitting new orders.

The last step in declaring UI objects for the main window is adding some buttons that will be associated to some tasks. Buttons can be nested into a StackPanel container and a useful approach is to define a WPF style for assigning the same properties to each button just once. Listing 21 shows the implementation of the StackPanel and children buttons.

        <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="5">
            <StackPanel.Resources>
                <!-- A simple style for setting Buttons properties-->
                <Style x:Key="ButtonStyle" TargetType="Button">
                    <Setter Property="Margin" Value="5"/>
                    <Setter Property="Width" Value="120"/>
                    <Setter Property="Height" Value="30"/>
                </Style>
            </StackPanel.Resources>
            <Button Content="New order" Style="{StaticResource ButtonStyle}"
                    Name="NewOrderButton" Click="NewOrderButton_Click"  />
            <Button Content="Update order" Style="{StaticResource ButtonStyle}"
                    Name="UpdateOrderButton" Click="UpdateOrderButton_Click" />
            <Button Content="Delete order" Style="{StaticResource ButtonStyle}"
                    Name="DeleteOrderButton" Click="DeleteOrderButton_Click" />
            <Button Content="View order details" Style="{StaticResource ButtonStyle}"
                    Name="OrderDetailsButton" Click="OrderDetailsButton_Click" />
        </StackPanel>
    </Grid>
</Window>

Listing 21

Defining a style is really useful because we can define the same properties for a particular type of control just once. In this case the style defines the Width, the Height and the Margin properties for the buttons and is assigned to each button via a markup extension called StaticResource. At this point ensure that the UI you see in the designer is the same of the one shown in Figure 13. Now we can switch to the Visual Basic code editor and finalize the application. We have to consider that when the application is running the first thing to do is to populate the ComboBox with a list of customers. Once that is completed, the application must load the orders associated with the first customer in the list. We also need to provide the logic for updating the list of orders when the user selects another customer from the ComboBox. To accomplish some of these tasks we will invoke methods provided by the Helper.vb class (see the Implementing methods for CRUD operations section of the article). Code in Listing 22 shows how we can do this (see comments for explanations).

Imports WpfClient.NorthwindServiceReference
Imports System.Data.Services.Client
Class Window1
    Private WithEvents OrderView As ListCollectionView
    Private HelperClass As New Helper
    Public Sub New()
        ' This call is required by the Windows Form Designer.
        InitializeComponent()
        'Populating the ComboBox
        Me.LoadCustomers()
    End Sub
    'Populating the customers ComboBox
    Private Sub LoadCustomers()
        Me.CustomersCombo.ItemsSource = Me.HelperClass.GetCustomers
        Me.CustomersCombo.SelectedIndex = 0
    End Sub
    'Populating the orders ListView
    Private Sub LoadOrders()
        'First I need to get the current object in the ComboBox
        'then I must convert that object into a Customers instance
        Dim SelectedCustomer As Customers = _
                                CType(CustomersCombo.SelectedItem, Customers)
        Me.DataContext = Me.HelperClass.GetOrders(SelectedCustomer).ToList
    End Sub
    'Set currency between the selected item
    'and the data source
    Private Sub Item_GotFocus(ByVal sender As System.Object, _
                   ByVal e As System.Windows.RoutedEventArgs)
        Dim item = CType(sender, ListViewItem)
        Me.OrdersListView.SelectedItem = item.DataContext
    End Sub
    'When the user selects a customer then loads the associated orders
    'into the ListView
    Private Sub CustomersCombo_SelectionChanged(ByVal sender As Object, ByVal e As  _
                                                System.Windows.Controls.SelectionChangedEventArgs) _
                                                Handles CustomersCombo.SelectionChanged
        Me.LoadOrders()
    End Sub

Listing 22

Note that we created just one instance of the Helper class so that we have the flexibility of sending changes to the service in one call, instead of sending multiple requests over the network. As mentioned before, all we would need to do to enable that in the Helper is move the calls to SaveChanges into one method and expose that to our form as well.

Handling the ComboBox.SelectionChanged event will allow us to load and show the orders associated with the customer that the user selected in the CustomersCombo control, invoking a method called LoadOrders. Regarding this method, we have to say that at the beginning of the code in Listing 22, we declared an object OrderView of type ListCollectionView. This is a particular .NET object which offers a view for manipulating data. It acts over your collections exposing methods for adding, removing and editing data and can be used in data-binding operations, as we will see later. At the moment we just need to focus on the Window.DataContext property assignment: a list of orders, retrieved by the Helper.GetOrders method and then converted into a generic List(Of Orders) collection, becomes the data source for the ListView control. There are a couple different types of CollectionView objects that are returned depending on the type of collection you are binding to but the ListCollectionView is used when working with simple List(Of T) collections.

At this point we just need to write event handlers for the buttons’ Click events. Here we will invoke some other methods exposed by the Helper.vb class specifically for executing CRUD operations as shown in Listing 23 (see comments in the code for a better exposition).

    Private Sub NewOrderButton_Click(ByVal sender As System.Object, _
                                     ByVal e As System.Windows.RoutedEventArgs)
        'Adding a new Order to the List
        'First, adds to the instance of the ListCollectionView
        'a new item of the specified type (Orders)
        Dim ord As Orders = CType(Me.OrderView.AddNew(), Orders)
        'then moves the ListView selection to point to the new item
        Me.OrdersListView.ScrollIntoView(ord)
        'The NewOrder dialog's constructor receives a Customer as an argument.
        'In this case we want to associate the new order to the current Customer
        'so we retrieve the instance and pass it to the constructor
        Dim AddOrderDialog As New NewOrder(DirectCast _
                                          (CustomersCombo.SelectedItem, Customers))
        'Sets a relationship between the new Order and the new item
        'added to the ListCollectionView
        AddOrderDialog.Order = ord
        AddOrderDialog.Owner = Me
        'If the user clicks OK
        If AddOrderDialog.ShowDialog() = True Then
            'Saves changes to the collection
            Me.OrderView.CommitNew()
            Me.HelperClass.AddNewOrder(AddOrderDialog.Order)
            'and refreshes the UI
            Me.LoadOrders()
        Else
            'Otherwise cancels editing data
            Me.OrderView.CancelNew()
        End If
    End Sub
    Private Sub UpdateOrderButton_Click(ByVal sender As System.Object, _
                                        ByVal e As System.Windows.RoutedEventArgs)
        'First I need the selected ListView item, then I must convert this item
        'into an Orders object that I can manipulate
        Dim order As Orders = CType(Me.OrderView.CurrentItem(), Orders)
        'then I can update my order
        Try
            Me.HelperClass.UpdateOrder(order)
        Catch ex As ArgumentNullException
            MessageBox.Show(ex.ToString)
        Catch ex As DataServiceRequestException
            MessageBox.Show(ex.ToString)
        Finally
            'and then populate again the ListView
            Me.LoadOrders()
        End Try
    End Sub
    Private Sub DeleteOrderButton_Click(ByVal sender As System.Object, _
                                        ByVal e As System.Windows.RoutedEventArgs)
        'First we obtain the current order from the ListCollectionView
        Dim order As Orders = CType(Me.OrderView.CurrentItem(), Orders)
        Try
            'since the order from the ListCollectionView is data-bound
            'to the ListView, we don't need to retrieve the selected item
            'in the ListView itself.
            Me.HelperClass.DeleteOrder(order)
            Me.OrderView.Remove(order)
        Catch ex As ArgumentNullException
            MessageBox.Show(ex.ToString)
        Catch ex As DataServiceRequestException
            MessageBox.Show(ex.ToString)
        Finally
            Me.LoadOrders()
        End Try
    End Sub
    Private Sub OrderDetailsButton_Click(ByVal sender As System.Object, _
                                          ByVal e As System.Windows.RoutedEventArgs)
        Dim order As Orders = DirectCast(Me.OrdersListView.SelectedItem, Orders)
        Try
            Dim ViewDetails As New ViewOrderDetails(order.OrderID)
            ViewDetails.ShowDialog()
        Catch ex As NullReferenceException
            MessageBox.Show("No order was selected.")
        End Try
    End Sub
    Private Sub Window1_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) _
                        Handles Me.Loaded
        'This is required for binding the original data source to the ListCollectionView
        Me.OrderView = CType(CollectionViewSource.GetDefaultView(Me.DataContext),  _
                       ListCollectionView)
    End Sub
End Class

Listing 23

The ListCollectionView class offers methods for editing items in the collection, such as EditItem and CommitEdit. These methods will only be available when extending partial classes (in our sample the Order partial class) with the IEditableObject interface. Also it is a good practice implementing the INotifyPropertyChanged interface (so that the UI can be notified about changes on data objects made outside the UI itself) or using collections, such as the ObservableCollection, that implement that interface. Another useful interface when working with data-binding is the IDataErrorInfo, which is very useful for providing information to the UI about data errors. INotifyPropertyChanged and IDataErrorInfo, like IEditableObject, must be implemented in a partial class related to the data object (e.g. Order). You can find an example of implementing the three interfaces in this project from the MSDN Code Gallery.

It’s worth mentioning that the body of the Window1_Loaded event handler retrieves a ListCollectionView based on the data source (Me.DataContext) and then assigns the result to the OrderView object. Once this is populated, we can manipulate data that it stores.

Notice also how we are handling exceptions and showing the error messages through MessageBox objects. Regarding this, please pay particular attention to the ArgumentNullException and NullReferenceException objects: the user must select a row in the ListView before executing a CRUD operation but if she selects no row and then clicks a button, a null object is passed to the methods, so handling these kinds of exceptions (each according to the particular method implementation) is really important.

Testing the application

We are now ready to test our application. Press F5 and, when the orders’ list for the first customer is shown, try to perform the following tasks:

  • viewing the order details for a particular order;
  • adding a new order;
  • updating the newly added order;
  • deleting the order.

Figure 17 shows a moment of the application running.

Figure 17 – the running sample application

Surely you could improve the code in several ways, first of all by implementing custom data validation in the UI. In this article we needed a fast way to understand how WPF clients can efficiently make use of ADO.NET Data Services for data exchange so some improvements are left to your particular needs.

Known issues

The current version of the ADO.NET Data Services does not allow extension methods provided by Queryable and Enumerable classes on the client side (except for the AsQueryable and AsEnumerable ones). So please take care of this limitation when architecting your applications. Additionally, you could experience exceptions when deleting entities with associations. This is due to a bug in the first release of ADO.NET Data Services which you can fix by making sure this update is installed. See this article in the Knowledge Base for details.

Useful resources

ADO.NET Data Services Portal

ADO.NET Data Services (“Astoria”) Team Blog

ADO.NET Data Services “How-do-I” videos

Article: Using ADO.NET Data Services

ADO.NET Data Services on Beth Massi’s blog

WPF Forms Over Data Video series

About the author

Alessandro Del Sole is a Microsoft Visual Basic MVP and Team Member in the Italian “Visual Basic Tips & Tricks” Community. He writes lots of Italian and English language community articles and books about .NET development. He also enjoys writing freeware and open-source developer tools. You can visit Alessandro’s Italian language blog or his English language blog.

Show:
© 2014 Microsoft