방법: 데이터 서비스 데이터를 컨트롤에 바인딩(Silverlight 클라이언트)
WCF Data Services에서는 ListBox 또는 ComboBox와 같은 Silverlight 컨트롤을 DataServiceCollection<T> 인스턴스에 바인딩할 수 있습니다. 이 클래스는 DataServiceContext를 컨트롤의 데이터에 수행된 변경 내용과 동기화된 상태로 유지하기 위해 컨트롤로 발생한 이벤트를 처리합니다. DataServiceCollection<T>은 DataServiceQuery<TElement>를 기반으로 정의됩니다. 이 쿼리를 실행하면 컬렉션에 대한 개체를 제공하는 Open Data Protocol(OData)을 반환합니다.
이 항목의 절차에서는 다음과 같은 작업을 수행하는 방법을 보여 줍니다.
(선택 사항) Northwind 데이터 서비스에서 페이징 기능을 사용하도록 설정합니다.
새 Silverlight 응용 프로그램을 만듭니다.
자동 데이터 바인딩을 지원하는 클라이언트 데이터 서비스 클래스를 생성합니다.
데이터 서비스를 쿼리합니다.
결과를 응용 프로그램의 컨트롤에 바인딩합니다.
CollectionViewSource 인스턴스를 사용하여 마스터/세부 정보 Order 및 Order_Detail 개체의 바인딩을 단순화합니다. 이렇게 하려면 CollectionViewSource의 Source 속성을 마스터 DataServiceCollection<T>으로 설정합니다. 컨트롤의 데이터 바인딩은 선택한 Order를 변경하면 컬렉션이 로드될 때 표시된 Order_Details가 변경되도록 XAML에 정의됩니다. 자세한 내용은 How to: Bind to Hierarchical Data and Create a Master/Details View을 참조하십시오.
참고
CollectionViewSource 클래스는 Silverlight 3에서 지원되지 않습니다.
이 예제에서는 데이터 서비스에 페이징이 사용하도록 설정된 경우 OData 피드를 바인딩하는 방법을 보여 줍니다. 데이터 피드의 페이징은 OData 프로토콜의 버전 2.0 이상에서 지원됩니다.
WCF Data Services 퀵 스타트를 완료하면 응용 프로그램에서 액세스된 Northwind 샘플 데이터 서비스가 만들어집니다. OData 웹 사이트에 게시된 공용 Northwind 샘플 데이터 서비스를 사용할 수도 있습니다. 이 샘플 데이터 서비스는 읽기 전용이므로 변경 내용을 저장하려고 하면 오류가 발생합니다.
Northwind 샘플 데이터 서비스에서 페이징 기능을 사용하도록 설정하려면
솔루션 탐색기의 ASP.NET 프로젝트 아래에서 Northwind.svc를 두 번 클릭합니다.
그러면 Northwind 샘플 데이터 서비스에 대한 코드 페이지가 열립니다.
데이터 서비스의 코드에서 다음 코드를 InitializeService 메서드에 추가합니다.
' Set the data service version to V2 to support paging. config.DataServiceBehavior.MaxProtocolVersion = _ System.Data.Services.Common.DataServiceProtocolVersion.V2 ' Set paging limits for the Customers and Orders entity sets. ' These limits are set very low to demonstrate paging with the ' Northwind database. Paging should be configured to optimize ' data service performance. config.SetEntitySetPageSize("Orders", 2) config.SetEntitySetPageSize("Order_Details", 2)
// Set the data service version to V2 to support paging. config.DataServiceBehavior.MaxProtocolVersion = System.Data.Services.Common.DataServiceProtocolVersion.V2; // Set paging limits for the Customers and Orders entity sets. // These limits are set very low to demonstrate paging with the // Northwind database. Paging should be configured to optimize // data service performance. config.SetEntitySetPageSize("Orders", 2); config.SetEntitySetPageSize("Order_Details", 2);
그러면 Orders 및 Order_Details 엔터티 집합에 대한 페이징이 사용하도록 설정됩니다.
Silverlight 프로젝트에 대한 응용 프로그램을 만들려면
솔루션 탐색기에서 솔루션을 마우스 오른쪽 단추로 클릭하고 추가를 가리킨 다음 새 프로젝트를 선택합니다.
새 프로젝트 추가 대화 상자의 범주 창에서 Silverlight를 선택한 후 Silverlight 응용 프로그램 템플릿을 선택합니다. 프로젝트 이름을 DataBindingSample로 지정합니다.
Silverlight 응용 프로그램 추가 대화 상자에서 솔루션의 새 웹 사이트 또는 기존 웹 사이트에서 Silverlight 응용 프로그램 호스트를 선택합니다. 응용 프로그램을 참조하는 테스트 페이지 추가 및 시작 페이지로 설정을 선택합니다.
확인을 클릭합니다.
그러면 Silverlight에 대한 응용 프로그램이 만들어집니다.
프로젝트에 데이터 서비스 참조를 추가하려면
DataBindingSample 프로젝트를 마우스 오른쪽 단추로 클릭하고 서비스 참조 추가, 검색을 차례로 클릭합니다.
첫 번째 작업에서 만든 Northwind 데이터 서비스가 표시됩니다.
네임스페이스 텍스트 상자에 Northwind를 입력하고 확인을 클릭합니다.
데이터 서비스 리소스에 개체로 액세스하고 상호 작용하는 데 사용되는 데이터 클래스가 포함된 새 코드 파일이 프로젝트에 추가됩니다. 데이터 클래스는 DataBindingSample.Northwind 네임스페이스에 만들어집니다.
클라이언트 응용 프로그램 사용자 인터페이스를 정의하려면
솔루션 탐색기의 DataBindingSample에서 참조를 마우스 오른쪽 단추로 클릭하고 참조 추가를 클릭합니다.
이렇게 하면 참조 추가 대화 상자가 표시됩니다.
System.Windows.Controls.Data를 선택하고 확인을 클릭합니다.
솔루션 탐색기에서 MainPage.xaml 파일을 두 번 클릭합니다. 그러면 Silverlight 응용 프로그램에 대한 사용자 인터페이스인 Page 클래스에 대한 XAML 마크업을 엽니다.
기존 XAML 마크업을 응용 프로그램 사용자 인터페이스를 정의하는 다음 마크업으로 바꿉니다.
<!-- NOTE: By convention, the sdk prefix indicates a URI-based XAML namespace declaration for Silverlight SDK client libraries. This namespace declaration is valid for Silverlight 4 only. In Silverlight 3, you must use individual XAML namespace declarations for each CLR assembly and namespace combination outside the scope of the default Silverlight XAML namespace. For more information, see the help topic "Prefixes and Mappings for Silverlight Libraries". --> <UserControl x:Class="MainPage" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" Loaded="MainPage_Loaded" mc:Ignorable="d" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:my="clr-namespace:DataBindingSample.Northwind" d:DesignHeight="371" d:DesignWidth="0"> <UserControl.Resources> <CollectionViewSource x:Key="OrdersViewSource" d:DesignSource="{d:DesignInstance my:Order, CreateList=True}" /> <CollectionViewSource x:Key="OrdersOrder_DetailsViewSource" Source="{Binding Path=Order_Details, Source={StaticResource OrdersViewSource}}" /> </UserControl.Resources> <StackPanel Orientation="Vertical" Margin="10" Height="Auto" Name="LayoutRoot" Width="450"> <StackPanel Orientation="Horizontal"> <sdk:Label Content="Customer ID:"/> <TextBox Name="customerId" Text="ALFKI" Margin="10" Width="100"/> <Button Name="getCustomerOrders" Content="Get Orders" Height="25" Width="80" Click="getCustomerOrders_Click" /> </StackPanel> <StackPanel Name="ordersStackPanel" VerticalAlignment="Top" Orientation="Vertical" DataContext="{StaticResource OrdersViewSource}"> <Grid HorizontalAlignment="Left" Name="ordersGrid" VerticalAlignment="Top"> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <sdk:Label Content="Order:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" /> <ComboBox DisplayMemberPath="OrderID" Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" ItemsSource="{Binding}" Margin="3" Name="OrderIDComboBox" VerticalAlignment="Center" Width="120" SelectionChanged="ordersList_SelectionChanged"> <ComboBox.ItemsPanel> <ItemsPanelTemplate> <VirtualizingStackPanel /> </ItemsPanelTemplate> </ComboBox.ItemsPanel> </ComboBox> <sdk:Label Content="Freight:" Grid.Column="2" Grid.Row="0" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" /> <TextBox Grid.Column="3" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3" Name="FreightTextBox" Text="{Binding Path=Freight, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" /> <sdk:Label Content="Required Date:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" /> <sdk:DatePicker Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3" Name="RequiredDateDatePicker" SelectedDate="{Binding Path=RequiredDate, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" /> <sdk:Label Content="Order Date:" Grid.Column="2" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" /> <TextBox Grid.Column="3" Grid.Row="1" Height="23" HorizontalAlignment="Left" Width="120" Margin="3" Name="OrderDateTextBlock" Text="{Binding Path=OrderDate}" VerticalAlignment="Center" /> </Grid> </StackPanel> <StackPanel DataContext="{StaticResource OrdersOrder_DetailsViewSource}" Orientation="Vertical"> <sdk:Label Content="Order items:" Margin="10"/> <sdk:DataGrid AutoGenerateColumns="False" Height="170" ItemsSource="{Binding}" Name="Order_DetailsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected" Width="400"> <sdk:DataGrid.Columns> <sdk:DataGridTextColumn x:Name="ProductIDColumn" Binding="{Binding Path=ProductID}" Header="Product" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="QuantityColumn" Binding="{Binding Path=Quantity}" Header="Quantity" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="DiscountColumn" Binding="{Binding Path=Discount}" Header="Discount" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="UnitPriceColumn" Binding="{Binding Path=UnitPrice}" Header="Unit Price" Width="SizeToHeader" /> </sdk:DataGrid.Columns> </sdk:DataGrid> </StackPanel> <Button Name="saveChangesButton" Content="Save Changes" HorizontalAlignment="Right" Click="saveChangesButton_Click" Width="100" Height="25" Margin="10"/> </StackPanel> </UserControl>
참고
C# 응용 프로그램의 경우 UserControl의 Class 특성에 있는 네임스페이스를 포함해야 합니다.Visual Basic 응용 프로그램에는 기본 네임스페이스가 필요하지 않습니다.
데이터 서비스를 Silverlight 응용 프로그램에 바인딩하는 코드를 추가하려면
솔루션 탐색기의 DataBindingSample에서 MainPage.xaml 파일의 코드 페이지를 열고 다음 using 문(Visual Basic에서는 Imports)을 추가합니다.
using System.Windows.Data; using System.Data.Services.Client; using DataBindingSample.Northwind;
MainPage 클래스에 다음 선언을 추가합니다.
Dim context As NorthwindEntities Dim trackedOrders As DataServiceCollection(Of Order) Dim selectedOrder As Order Dim ordersViewSource As CollectionViewSource
NorthwindEntities context; DataServiceCollection<Order> trackedOrders; Order selectedOrder; CollectionViewSource ordersViewSource;
다음 MainPage_Loaded 메서드를 MainPage 클래스에 추가합니다.
Private Sub MainPage_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) ' Initialize the data service context. context = _ New NorthwindEntities(New Uri("https://localhost:54321/Northwind.svc")) ' Initialize the binding and view source collections. trackedOrders = New DataServiceCollection(Of Order)() ordersViewSource = CType(Me.Resources("OrdersViewSource"), CollectionViewSource) ' Define a handler for the LoadCompleted event of the collection. AddHandler trackedOrders.LoadCompleted, _ AddressOf trackedOrders_LoadCompleted End Sub
private void MainPage_Loaded(object sender, RoutedEventArgs e) { // Initialize the data service context. context = new NorthwindEntities(new Uri("Northwind.svc", UriKind.Relative)); // Initialize the binding and view source collections. trackedOrders = new DataServiceCollection<Order>(); ordersViewSource = (CollectionViewSource)this.Resources["OrdersViewSource"]; // Define a handler for the LoadCompleted event of the binding collection. trackedOrders.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(trackedOrders_LoadCompleted); }
페이지가 로드되면 이 코드가 바인딩 컬렉션 및 콘텐츠를 초기화하고 바인딩 컬렉션으로 발생한 LoadCompleted 이벤트를 처리하는 메서드를 등록합니다.
다음 코드를 MainPage 클래스에 삽입합니다.
Private Sub getCustomerOrders_Click(ByVal sender As Object, _ ByVal e As RoutedEventArgs) ' Define a query that returns orders for a give customer. Dim query = From orderByCustomer In context.Orders _ Where orderByCustomer.Customer.CustomerID = _ Me.customerId.Text _ Select orderByCustomer ' Asynchronously load the result of the query. trackedOrders.LoadAsync(query) ' Disable the button until the loading is complete. getCustomerOrders.IsEnabled = False End Sub Private Sub trackedOrders_LoadCompleted(ByVal sender As Object, _ ByVal e As LoadCompletedEventArgs) If e.Error Is Nothing Then ' Load all pages of Orders before binding. If trackedOrders.Continuation IsNot Nothing Then trackedOrders.LoadNextPartialSetAsync() Else ' Bind the root StackPanel element to the collection ' related object binding paths are defined in the XAML. ordersViewSource.Source = trackedOrders ' Re-enable the button since the loading is complete. getCustomerOrders.IsEnabled = True End If Else MessageBox.Show(String.Format("An error has occured: {0}", e.Error.Message)) getCustomerOrders.IsEnabled = True End If End Sub
private void getCustomerOrders_Click(object sender, RoutedEventArgs e) { // Reset the grids. ordersViewSource.Source = null; // Define a query that returns orders for a give customer. var query = from orderByCustomer in context.Orders where orderByCustomer.Customer.CustomerID == this.customerId.Text select orderByCustomer; // Asynchronously load the result of the query. trackedOrders.LoadAsync(query); // Disable the button until the loading is complete. getCustomerOrders.IsEnabled = false; } private void trackedOrders_LoadCompleted(object sender, LoadCompletedEventArgs e) { if (e.Error == null) { // Load all pages of Orders before binding. if (trackedOrders.Continuation != null) { trackedOrders.LoadNextPartialSetAsync(); } else { // Bind the root StackPanel element to the collection; // related object binding paths are defined in the XAML. ordersViewSource.Source = trackedOrders; // Re-enable the button since the loading is complete. getCustomerOrders.IsEnabled = true; } } else { MessageBox.Show(string.Format("An error has occured: {0}",e.Error.Message)); getCustomerOrders.IsEnabled = true; } }
getCustomerOrders 단추를 클릭하면 다음과 같은 작업이 수행됩니다.
제공된 고객 ID로 필터링된 주문을 반환하는 제공된 쿼리를 실행하기 위해 LoadAsync 메서드가 바인딩 컬렉션에서 호출됩니다.
Continuation 속성이 값을 반환하는 한 이후 결과 페이지를 로드하기 위해 LoadNextPartialSetAsync 메서드가 호출됩니다.
로드된 Order 개체의 컬렉션이 페이지의 모든 컨트롤에 대한 마스터 바인딩 개체인 CollectionViewSource의 Source 속성에 바인딩됩니다.
다음 코드를 MainPage 클래스에 삽입합니다.
Private Sub ordersList_SelectionChanged(ByVal sender As Object, _ ByVal e As SelectionChangedEventArgs) ' Get the selected Order in the DataGrid. Dim ordersList As ComboBox = CType(sender, ComboBox) selectedOrder = CType(ordersList.SelectedItem, Order) If Not selectedOrder Is Nothing Then ' Asynchronously load related items, if they are not already loaded. If selectedOrder.Order_Details.Count = 0 Then ' Register the method to handle the LoadCompleted event. AddHandler selectedOrder.Order_Details.LoadCompleted, _ AddressOf Order_Details_LoadCompleted Try ' Load the related items. selectedOrder.Order_Details.LoadAsync() Catch ex As InvalidOperationException MessageBox.Show(String.Format("An error has occured: {0}", _ ex.Message)) End Try End If End If End Sub Private Sub Order_Details_LoadCompleted(ByVal sender As Object, _ ByVal e As LoadCompletedEventArgs) Dim trackedItems As DataServiceCollection(Of Order_Detail) = _ CType(sender, DataServiceCollection(Of Order_Detail)) ' Load any remaining pages of Order_Details. If Not trackedItems.Continuation Is Nothing Then Try trackedItems.LoadNextPartialSetAsync() Catch ex As InvalidOperationException MessageBox.Show(String.Format("An error has occured: {0}", _ ex.Message)) End Try End If End Sub
private void ordersList_SelectionChanged(object sender, SelectionChangedEventArgs e) { // Get the selected Order in the DataGrid. ComboBox ordersList = sender as ComboBox; selectedOrder = ((Order)(ordersList.SelectedItem)); if (selectedOrder != null) { // Asynchronously load related items, if they are not already loaded. if (selectedOrder.Order_Details.Count == 0) { // Register the method to handle the LoadCompleted event. selectedOrder.Order_Details.LoadCompleted += new EventHandler<LoadCompletedEventArgs>( Order_Details_LoadCompleted); try { // Load the related items. selectedOrder.Order_Details.LoadAsync(); } catch (InvalidOperationException ex) { MessageBox.Show(string.Format("An error has occured: {0}", ex.Message)); } } } } void Order_Details_LoadCompleted(object sender, LoadCompletedEventArgs e) { DataServiceCollection<Order_Detail> trackedItems = sender as DataServiceCollection<Order_Detail>; // Load any remaining pages of Order_Details. if (trackedItems.Continuation != null) { try { trackedItems.LoadNextPartialSetAsync(); } catch (InvalidOperationException ex) { MessageBox.Show(string.Format("An error has occured: {0}", ex.Message)); } } }
ordersList_SelectionChanged 메서드가 SelectionChanged 이벤트를 처리합니다. 사용자가 ComboBox에서 주문을 선택하면 다음과 같은 작업이 수행됩니다.
선택한 주문의 관련 항목을 나타내는 DataServiceCollection<T>으로 발생한 LoadCompleted 이벤트를 처리하기 위해 Order_Details_LoadCompleted 메서드가 등록됩니다.
ComboBox에서 선택한 Order와 관련된 Order_Details 개체를 비동기적으로 로드하기 위해 LoadAsync 메서드가 호출됩니다.
Continuation 속성이 값을 반환하는 한 이후 결과 페이지를 로드하기 위해 LoadNextPartialSetAsync 메서드가 호출됩니다.
로드된 항목이 하위 CollectionViewSource에 의해 DataGrid에 전파됩니다.
변경 내용을 저장하는 다음 코드를 MainPage 클래스에 삽입합니다.
' We need to persist the result of an operation ' to be able to invoke the dispatcher. Private currentResult As IAsyncResult Private Sub saveChangesButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) ' Define the delegate to callback into the process Dim callback As AsyncCallback = AddressOf OnChangesSaved Try ' Start the saving changes operation. This needs to be a ' batch operation in case we are added a new object with ' a new relationship. context.BeginSaveChanges(SaveChangesOptions.Batch, _ callback, context) Catch ex As Exception MessageBox.Show(String.Format( _ "The changes could not be saved to the data service.\n" _ & "The following error occurred: {0}", ex.Message)) End Try End Sub Private Sub OnChangesSaved(ByVal result As IAsyncResult) ' Persist the result for the delegate. currentResult = result ' Use the Dispatcher to ensure that the ' asynchronous call returns in the correct thread. Dispatcher.BeginInvoke(AddressOf ChangesSavedByDispatcher) End Sub Private Sub ChangesSavedByDispatcher() Dim errorOccured As Boolean = False context = CType(currentResult.AsyncState, NorthwindEntities) Try ' Complete the save changes operation and display the response. Dim response As DataServiceResponse = _ context.EndSaveChanges(currentResult) For Each changeResponse As ChangeOperationResponse In response If changeResponse.Error IsNot Nothing Then errorOccured = True Next If Not errorOccured Then MessageBox.Show("The changes have been saved to the data service.") Else MessageBox.Show("An error occured. One or more changes could not be saved.") End If Catch ex As Exception ' Display the error from the response. MessageBox.Show(String.Format("The following error occured: {0}", ex.Message)) End Try End Sub
private void saveChangesButton_Click(object sender, RoutedEventArgs e) { try { // Start the saving changes operation. This needs to be a // batch operation in case we are added a new object with // a new relationship. context.BeginSaveChanges(SaveChangesOptions.Batch, OnChangesSaved, context); } catch (Exception ex) { MessageBox.Show(string.Format("The changes could not be saved to the data service.\n" + "The following error occurred: {0}", ex.Message)); } } private void OnChangesSaved(IAsyncResult result) { bool errorOccured = false; // Use the Dispatcher to ensure that the // asynchronous call returns in the correct thread. Dispatcher.BeginInvoke(() => { context = result.AsyncState as NorthwindEntities; try { // Complete the save changes operation and display the response. DataServiceResponse response = context.EndSaveChanges(result); foreach (ChangeOperationResponse changeResponse in response) { if (changeResponse.Error != null) errorOccured = true; } if (!errorOccured) { MessageBox.Show("The changes have been saved to the data service."); } else { MessageBox.Show("An error occured. One or more changes could not be saved."); } } catch (Exception ex) { // Display the error from the response. MessageBox.Show(string.Format("The following error occured: {0}", ex.Message)); } } ); }
이 코드는 데이터 바인딩된 컨트롤에 수행된 변경 내용을 다시 데이터 서비스로 비동기적으로 전송합니다.
참고 항목
관련 자료
Silverlight에 대한 WCF Data Services 퀵 스타트
WCF Data Services (Silverlight)