John Papa
Code download path: SLDataServices2008_09a.exe (234 KB)
Online browsing code
This column is based on the Beta 2 version of Silverlight 2. All information in this document may be changed.
Download the code used in this article: DataPoints2008_09a.exe (414 KB)
Browse online code
Directory
Sample Application
Cross-origin Communication
Silverlight Client
List of bound Products
Asynchronous Communication
Product details and binding modes
Change event
Conclusion
Without a doubt, Silverlight 2 makes it easy to use a large number of graphic processing technologies to build rich Internet applications (RIA. On the other hand, Silverlight 2 can easily build a very professional LOB application. Silverlight 2 supports a powerful and XAML-based Data Binding subset with Windows Presentation Foundation (WPF) enabled. The XAML binding tag extension in Silverlight 2 simplifies the process of binding entities to the Silverlight control. Because they run completely on the client computer, the Silverlight application is isolated from the entity managed by the server. In this way, service-based Communication through technologies such as RSS, symbolic State transmission (REST), and Windows Communication Foundation (WCF) must be available. Fortunately, Silverlight 2 supports interaction with these technologies and other communication channels, which enables the Silverlight application to seamlessly interact with the backend LOB application.
I will demonstrate how to build the Silverlight 2 UI so that it can interact with business entities and databases through communication with WCF. Any presentation layer can use the business logic, entity model, and data ing code. I will create a WCF Service that will be used by the Silverlight 2 Application and create a server hosting the WCF Service to allow cross-origin calls. Please note that you can download these examples from the MSDN magazine website.
Sample Application
Before writing code, let's take a closer look at this example.Figure 1A complete application is displayed, showing the List of Products Retrieved From the Northwind database. After you select a product from ListBox, the product will be bound to the control in the lower half of the page. When you use the CheckBox and TextBox controls to edit the product and click the Save button, the product information is then sent to the database through WCF. Click "Cancel" to obtain the latest product list from the server through WCF, and update the ListBox and its binding.
Figure 1Sample Silverlight Application(Click an image to view the larger image)
The Silverlight 2 sample application consists of a few user controls and styles. The presentation layer uses asynchronous calls to communicate with the server through WCF. It uses the WCF Service reference and implements the communication between the Silverlight application and the service according to the service operation conventions and data conventions. Data conventions (such as Product or Category entity classes) expose the entity structures in server applications. This allows the control of the Silverlight application to be easily bound to the instances and attributes of these entities. The operation Convention defines how a Silverlight application calls a WCF Service.Figure 2It shows a high-level overview of this architecture.
Figure 2Architecture model of the sample application
I will start with the WCF Service, and use the lower layer to build the sample application. You can create a new WCF project in Visual Studio to build a WCF Service that can communicate with the Silverlight application. As long as the Silverlight application has a basichttpbinding binding type, it can call the standard WCF Service. You must ensure that you can change the default binding of the WCF Service from wshttpbinding to basichttpbinding. Otherwise, you must create a basichttpbinding binding. For example, the web. config file of the sample WCF Service Host application contains the following XML used to define the service configuration. Note that the endpoint binding is defined as basichttpbinding:
Copy code
<service behaviorConfiguration= "MySilverlightWcfService.NWServiceGatewayBehavior" name="MySilverlightWcfService.NWServiceGateway"> <endpoint address="" binding="basicHttpBinding" contract="MySilverlightWcfService.INWServiceGateway" /> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /></service>
As an alternative to creating a WCF Service, you can select a file project template in Visual Studio to create a WCF Service that enables Silverlight.Figure 3The new project template in Visual Studio is displayed. This template automatically sets the binding as basichttpbinding and adds some attributes to make the service compatible with ASP. NET. Although this method can set the correct binding configuration for you, do not forget that you can still use the existing WCF Service, provided that these bindings are set for basichttpbinding.
Figure 3Enable the Silverlight WCF Template(Click an image to view the larger image)
The WCF Service must be able to request the product list to fill in the ListBox, and be able to save any changes made to the product. This is a simple operation and requires no special Silverlight technology. The sample application uses a WCF Service class named nwservicegateway to implement the inwservicegateway interface. The inwservicegateway shown here is modified to servicecontract, which makes all classes implementing this interface public through WCF:
Copy code
[ServiceContract(Namespace = "")]public interface INWServiceGateway{ [OperationContract] Product FindProduct(int productId); [OperationContract] List<Product> FindProductList(); [OperationContract(Name="FindProductListByCategory")] List<Product> FindProductList(int categoryID); [OperationContract] List<Category> FindCategoryList(); [OperationContract] void SaveProduct(Product product);}
This interface lists multiple methods for modifying the OperationContract attribute. OperationContracts can be called through the WCF Service. Note that the FindProductList method has two overloading methods. One accepts the parameter while the other does not. Although this is completely acceptable in Microsoft. NET Framework methods, WCF cannot publish two methods with the same name. To solve this problem, you can rename the method or use the Name attribute of OperationContract in the service definition to specify a different Name.Figure 4Shows how to publish the FindProductList method and parameters to an operation with a new name in the WCF Service.
Figure 4 collect products at the data layer
Copy code
public List<Product> FindProductList(){ var productList = new List<Product>(); using (var cn = new SqlConnection(nwCn)) { const string sql = @"SELECT p.*, c.CategoryName " + " FROM Products p INNER JOIN Categories c ON " + " p.CategoryID = c.CategoryID ORDER BY p.ProductName"; cn.Open(); using (var cmd = new SqlCommand(sql, cn)) { SqlDataReader rdr = cmd.ExecuteReader( CommandBehavior.CloseConnection); if (rdr != null) while (rdr.Read()) { var product = CreateProduct(rdr); productList.Add(product); } return productList; } }}
What the WCF Service needs to do is to implement the agreed interface of this service and call a Manager class. This type of task is to obtain the Product from the database and map it to the List <Product>, so that they can be transferred out of the service.Figure 4The displayed code is used to obtain the Product List from the Northwind database, map each Product to the Product class, and add each Product instance to the List <Product>.
Entities returned by WCF must be modified using the DataContract attribute so that they can be correctly serialized and sent to the Silverlight application.Figure 4The involved Product class has the DataContract attribute, and all its attributes are modified using the DataMember attribute. This tells WCF to start serialization and provides the entity and its DataMember attributes to the Silverlight application. When the FindProductList method of the WCF Service is called, The Silverlight client application receives the List <Product> and can reference all features modified using the DataMember attribute.
Cross-origin Communication
The Silverlight application is executed in the client computer environment. This will cause some problems, that is, ASP. NET Web-based applications are not currently displayed, because they are all executed on the server, rendering HTML on the client and writing script code. Because Silverlight is executed on the client, it must use a service-oriented technology (such as WCF) to request information from this server. However, the WCF Service must be protected to prevent unnecessary client applications from exploiting it. The sample Silverlight application is hosted on a trusted Web server, so it should be allowed to interact with the WCF Service of the sample application. If other applications hosted on another Web Server attempt to communicate with the sample WCF Service, they should be rejected.
Service Access Control is handled through cross-origin policy files. The Adobe Flash application has a standard file that can be used to process the file named CrossDomain. xml (located in the root directory of the Web server of the Service ). The behavior of the Silverlight application is very similar. First, find the file named ClientAccessPolicy. xml in the root directory of the Web server (not the root directory of the Web application. If the file is found, the application reads it to determine whether the request is allowed or rejected. If not, the application continues to search for the CrossDomain. xml file. If none are found, the request is rejected and the Silverlight client application cannot call the WCF Service.
The content of each file must allow the caller to have permissions on these services. Because the sample application only exists on a protected development computer, its ClientAccessPolicy. xml allows all requests, as shown below:
Copy code
<?xml version="1.0" encoding="utf-8"?><access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access></access-policy>
The ClientAccessPolicy. xml file I created allows cross-origin access to all paths from all locations. Copies of this file are included in the sample application. After you create a WCF project for the first time, the default option is to use the Visual Studio Development Server with automatically assigned ports. However, the WCF Service Project of the sample application is set to "use IIS Web Server". This setting can be changed on the project properties page of the Web tab. Each option is valid if the ClientAccessPolicy. xml file is placed in the root directory of the Web site.
These policies can be restricted to allow only certain URIs to access specific services in a specific folder path. To perform this operation, you must specify the Web server that hosts the Silverlight application in this file so that it can interact with the WCF Service. For example, cross-domain policies with high limitations may be as follows:
Copy code
<?xml version="1.0" encoding="utf-8"?><access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="http://johnpapa.net"/> </allow-from> <grant-to> <resource path="/MyAwesomeServices/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access></access-policy>
Note that any request from a service in johnpapa.net is allowed to access the service in the/MyAwesomeServices path on the server.
It may be difficult to debug cross-origin calls. You can use tools such as Web Development Helper (my favorites) or Fiddler to check the communication traffic between Silverlight applications and server-based services. These tools will display individual HTTP requests and responses. You should also ensure that the ClientAccessPolicy. xml file is stored in the Web root directory rather than the application root directory. I will not take up space to emphasize this point.
In addition, when a service is hosted in a Visual Studio project (for example, not directly in IIS), the project creates an automatically assigned port, the easiest way to test this service is to set ClientAccessPolicy. the xml file is placed in the root directory of the project. Because the project gets an automatically allocated port (such as localhost: 32001/MyAwesomeService), it will search for ClientAccessPolicy in the root directory of the Web. xml file (localhost: 32001 in this example ).
Silverlight Client
After compiling these services and establishing a Cross-Domain Policy, you can establish a Silverlight client to communicate with the WCF Service. The sample application contains a Silverlight client project named SilverlightWCF, which requires a service reference for the sample WCF Service. In Solution Explorer, right-click the Reference node and select Add Service Reference ). Enter the service URL and click "GO". The service is displayed. Click OK. The service client configuration and generated proxy classes are added to the Silverlight project to simplify the process of calling services and using entities (as defined by DataContract.Figure 5The dialog window appears when you add a service.
Figure 5Add service reference(Click an image to view the larger image)
Note that by default, this example uses services located in the localhost/MySilverlightWcfService/NWServiceGateway. svc directory. Undoubtedly, if the service is moved, the endpoint address needs to be updated accordingly. If you make a few changes in the WCF Service (such as adding a new method or DataContract), you can go to Solution Explorer) select "Update Service Reference ).
List of bound Products
The sample application needs to display the product list to the user. The ListBox control will be used with DataTemplate to display products with special effects, instead of simply listing values in rows and columns.Figure 1). By setting the ItemSource attribute of ListBox, we specify that ListBox will obtain its value from the bound data source, as shown in the following mark:
Copy code
<ListBox x:Name="lbProducts" Height="220" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="10,10,10,10" Width="480" Style="{StaticResource ListBoxStyle}" ItemsSource="{Binding}" >
Note that the ItemSource attribute only indicates that it will be bound, but cannot specify any specific object or attribute. Because no source is specified, ListBox references any inherited DataContext object source in XAML. DataContext can be inherited from any parent FrameworkElement. After the WCF Service returns to the product list, the sample application sets the DataContext of ListBox in code hiding, and its name is lbProducts:
Copy code
lbProducts.DataContext = productList;
After executing this code, ListBox will be bound to the product list, and each ListBoxItem in the DataTemplate will be bound to each product in the list.Figure 6Displays the XAML used to create a ListBox and bind the items displayed in the DataTemplate to the source. Note that each control bound to the source will use the binding tag extension to indicate which attributes of the control to be bound to the Product and indicate that the OneWay binding mode will be used. For example,
Copy code
Text="{Binding ProductName, Mode=OneWay}"
Figure 6 ListBox XAML
Copy code
<ListBox x:Name="lbProducts" Height="220" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="10,10,10,10" Width="480" Style="{StaticResource ListBoxStyle}" ItemsSource="{Binding}" > <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <StackPanel Style="{StaticResource TitlePanel}" Orientation="Vertical"> <TextBlock Text="{Binding ProductName, Mode=OneWay}" Style="{StaticResource TitleTextBlock}" FontSize="14"/> <TextBlock Text="{Binding CategoryName, Mode=OneWay}" Style="{StaticResource SubTitleTextBlock}"/> </StackPanel> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Text="Product ID" Style="{StaticResource TextBlockStyle}" Grid.Row="0" Grid.Column="0"/> <TextBlock Text="{Binding Id, Mode=OneWay}" Style="{StaticResource TextBlockStyle}" Foreground="#FF001070" Grid.Row="0" Grid.Column="1"/> <TextBlock Text="Price" Style="{StaticResource TextBlockStyle}" Grid.Row="1" Grid.Column="0"/> <TextBlock Text="{Binding UnitPrice, Mode=OneWay, Converter={StaticResource priceConverter}}" Foreground="#FF001070" Style="{StaticResource TextBlockStyle}" Grid.Row="1" Grid.Column="1"/> <TextBlock Text="Units" Style="{StaticResource TextBlockStyle}" Grid.Row="2" Grid.Column="0"/> <TextBlock Text="{Binding UnitsInStock, Mode=OneWay}" Foreground="#FF001070" Style="{StaticResource TextBlockStyle}" Grid.Row="2" Grid.Column="1"/> </Grid> </StackPanel> </DataTemplate> </ListBox.ItemTemplate></ListBox>
All binding modes bound to the TextBox Control are set to OneWay. This means that the value of the source (that is, the Product instance) should be pushed to the target (ListBox and any project bound to it) at the initial loading and when any changes occur in the source) (For more information about "Mode", see "binding mode ).
Asynchronous Communication
Before binding, you must obtain the product list from the WCF Service. All WCF Service calls in Silverlight are performed through asynchronous communication. The example WCF Service published an OperationContract named FindProductList. Because the communication with Silverlight is asynchronous, this Convention uses an Asynchronous Method (This asynchronous method can start the communication and events triggered when the operation is complete) implemented in the generated proxy.
The following code creates a Service proxy instance:
Copy code
private void FindProductList(){ this.Cursor = Cursors.Wait; var proxy = new NWServiceGatewayClient(); proxy.FindProductListCompleted += new EventHandler<FindProductListCompletedEventArgs>( FindProductListCompleted); proxy.FindProductListAsync();}
After the proxy is created, an event handler is added to the findproductlistcompleted event. This is the method to receive the results of asynchronous WCF Service calls. Finally, execute the findproductlistasync method.
After the asynchronous WCF Service is called, the event handler is executed. The following code shows the process of receiving a list of products from the event handler findproductlistcompleted and binding it to the datacontext of ListBox:
Copy code
private void FindProductListCompleted(object sender, FindProductListCompletedEventArgs e){ ObservableCollection<Product> productList = e.Result; lbProducts.DataContext = productList; if (lbProducts.Items.Count > 0) lbProducts.SelectedIndex = 0; this.Cursor = Cursors.Arrow;}
Product details and binding modes
After a project is selected in ListBox, the code in the event handler obtains the selected product from selecteditem in ListBox and sets it to datacontext of the grid layout control. The grid layout control is used as a container for the product details section. It contains a series of controls for users to edit the selected product. Since datacontext is inherited from top to bottom in the control tree, you do not need to set datacontext in various textbox and checkbox controls. These controls are inherited from datacontext from its parent Grid Control (which has been set in lbproducts_selectionchanged event handler.
The grid layout control contains multiple textblock, Textbox, and checkbox controls that contain the binding declaration. Several XAML code segments in this Section are as follows:
Copy code
<TextBox Style="{StaticResource TextBoxStyle}" Text="{Binding UnitsInStock, Mode=TwoWay}" Grid.Row="1" Grid.Column="1" Margin="3,3,3,3" Height="30" x:Name="tbUnitsInStock" Width="100" VerticalAlignment="Bottom" HorizontalAlignment="Left"/><TextBlock Style="{StaticResource TextBlockStyle}" Text="{Binding CategoryName, Mode=OneWay}" Foreground="#FF001070" Grid.Row="1" Grid.Column="4" Margin="3,3,3,3" Height="22" HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
Note that the first code snippet represents Textbox, which displays the unitsinstock attribute in the product source object. The second code segment displays the value of the categoryname attribute of the product instance. Note that the mode attribute bound to textbox is set to twoway, and the mode attribute is set to oneway in textblock (this is the default setting ).
The bound mode attribute is an important binding setting that can be used to determine the frequency and direction of binding between the target and the source.Figure 7Display the Three binding modes available for Silverlight XAML and its update.
Figure 7 three binding modes in Silverlight XAML
Binding Mode
OneTime
OneWay
TwoWay
After DataContext is set for the first time, the target will be updated accordingly.
Yes
Yes
Yes
When the source changes, the target will update accordingly.
No
Yes
Yes
The source is updated when the target is changed.
No
No
Yes
When the application is started or the DataContext changes, the OneTime binding sends the source to the target. One-way binding can also send the source to the target. However, if the source implements the INotifyPropertyChanged interface, the target will receive updates when the source sends an update. The TwoWay binding will send the source data to the target, but if the value of the Target attribute is changed, the changes will be returned to the source. If the source object implements INotifyPropertyChanged and the source property setter triggers the PropertyChanged event, the OneWay and TwoWay bindings only notify the target of the changed message.
In the previous example, OneWay binding is used for CategoryName because it is displayed in TextBlock and cannot be edited. TwoWay binding is used for UnitsInStock because it is displayed in an editable TextBlock. When you edit the UnitsInStock value in TextBox and the TextBox loses focus, the change result is sent back to the source object.
One-time binding is most suitable for displaying read-only information that will never be changed during display. One-way binding is applicable to read-only information that may change at a certain time point during display. Further, if OneTime binding instead of OneWay binding is used in CategoryName TextBlock, click the "cancel" button. After the product list is refreshed, The TextBlock in OneTime binding mode will not be updated. Finally, when the user must be able to change the data in a control and reflect the changes in the data source, it is best to use TwoWay binding.
Change event
When the Source sends a change, the key to binding one-way and TwoWay to the notification target is to implement the INotifyPropertyChanged interface. The Product class can implement this interface, which consists of a single event PropertyChanged. The PropertyChanged event accepts the name of the sender and the changed attribute as the parameter. Silverlight Data Binding listens to these events from the data source. If this class neither implements this interface nor triggers the PropertyChanged event in the property setter, the target will never receive the update. PropertyChangedEventHandler is implemented using the following lines of code in the Product class:
Copy code
public override event PropertyChangedEventHandler PropertyChanged;
In this case, the event will be overwritten rather than simply defined, because the Product class will inherit from the custom EntityBase class (which can implement the INotifyPropertyChanged interface. In fact, the EntityBase class introduced here can implement PropertyChangedEventHandler, so it can be overwritten in a derived class such as Product or Category:
Copy code
[DataContract]public abstract class EntityBase : INotifyPropertyChanged { protected void PropertyChangedHandler(EntityBase sender, string propertyName) { if (PropertyChanged != null) PropertyChanged(sender, new PropertyChangedEventArgs( propertyName)); } public virtual event PropertyChangedEventHandler PropertyChanged; }
For convenience, you can also use the EntityBase class to define the PropertyChangedHandler method for the derived class, which in turn can trigger the PropertyChanged event. This is a simple refactoring process that places the logic of the event to a single location and reduces the amount of code. For example, you can set the product price value for the product class with the UnitPrice attribute and call the PropertyChangedHandler method of the basic class:
Copy code
[DataMember]public decimal UnitPrice{ get { return _unitPrice; } set { _unitPrice = value; base.PropertyChangedHandler(this, "UnitPrice"); }}
This method in turn triggers the PropertyChanged event. This event is then sent to any OneWay or TwoWay binding, so that these bindings update the target control to a new value. This mode is used for setter of each attribute in the Product class.
The property change notification can be demonstrated through the sample application by selecting a product from ListBox and editing the unit price in TextBox. Because tbUnitPrice TextBox is bound with TwoWay, the new price is sent to the source. Once the source is updated, the PropertyChanged event is triggered. This updates the target value for all bindings listening for this event. Because ListBox implements OneWay binding, the price value automatically updates itself to the new price value.
As you can see, using the declarative binding syntax shown in the sample application, you can easily implement the Silverlight 2 data binding function. These bindings will listen on PropertyChanged events so that they can update their targets. In this column, I show you how easy it is to bind using Silverlight and how to communicate with the WCF Service to interact with business objects and databases, it also introduces how to define ClientAccessPolicy. xml file to allow and restrict remote communication.
Please send your questions and comments to John to the mmdata@microsoft.com.