Walkthrough: Taking a Tour of RIA Services
[WCF RIA Services Version 1 Service Pack 2 is compatible with either .NET framework 4 or .NET Framework 4.5, and with either Silverlight 4 or Silverlight 5.]
This walkthrough provides you with an overview of many of the features in WCF RIA Services. In this walkthrough, you create a RIA Services application that retrieves data from tables in the AdventureWorks OLTP sample database. First, you retrieve the data by specifying the LoadOperation. You then retrieve that data with the DomainDataSource control. You specify sorting, filtering, and paging for the data presentation control, and add a DataForm control to present a detailed view of the data. You apply validation rules to the fields and enable the user to edit data values. You restrict access to a domain operation to authenticated users. Finally, you define the association between two related tables and display the related data.
Tip
For shorter walkthroughs for getting started by creating a more basic RIA Services solution, see Walkthrough: Creating a RIA Services Solution or Walkthrough: Using the Silverlight Business Application Template.
Prerequisites
This and the other walkthroughs presented in the WCF RIA Services documentation require several prerequisite programs, such as Visual Studio 2010 and the Silverlight Developer Runtime and SDK, be installed and configured properly, in addition to WCF RIA Services and the WCF RIA Services Toolkit. They also require installing and configuring SQL Server 2008 R2 Express with Advanced Services and installing the AdventureWorks OLTP and LT database.
Detailed instructions for the satisfaction of each of these prerequisites are provided by the topics within the Prerequisites for WCF RIA Services node. Follow the instructions provided there before proceeding with this walkthrough to ensure that you encounter as few problems as possible when working through this RIA Services walkthroughs.
Creating and Setting Up the Solution
In this section, you create and set up the solution.
To create a new WCF RIA Services application
In Visual Studio 2010, create a new RIA Services project by selecting File, New, and then Project.
The New Project dialog box appears.
In the Installed Templates pane, expand the Visual Basic or Visual C# node, and select the Silverlight category.
Select the Silverlight Business Application template and name the application HRApp.
Click OK.
Notice the structure of the solution that is created:
The solution consists of two projects: a Silverlight client project named HRApp and an ASP.NET Web Application server project named HRApp.Web.
The default solution contains many automatically implemented features including navigation, user login and logout, and new user registration.
Build and Run (F5) the application and explore the default implementation.
Close your Web browser.
To set up the application
In Solution Explorer, in the client project, open MainPage.xaml.
In XAML view, find the TextBlock named ApplicationNameTextBlock.
As shown in the following markup, notice that the application name is retrieved from a resource.
<TextBlock x:Name="ApplicationNameTextBlock" Style="{StaticResource ApplicationNameStyle}" Text="{Binding ApplicationStrings.ApplicationName, Source={StaticResource ResourceWrapper}}"/>
In Solution Explorer, expand the Assets folder, and then expand the Resources folder.
Open the ApplicationStrings.resx file.
Change the ApplicationName resource to HR Application.
Save and close the ApplicationStrings.resx file.
In Solution Explorer, right-click the Views folder, click Add, and then click New Item.
The Add New Item dialog box is displayed.
Select the Silverlight Page template from the Silverlight category of Installed Templates and name it EmployeeList.xaml.
Click Add.
Open EmployeeList.xaml if it does not open automatically.
Add the following XAML between the <Grid> tags.
<ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" > <StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}"> <TextBlock Text="Employee List" Style="{StaticResource HeaderTextStyle}"/> </StackPanel> </ScrollViewer>
Save the EmployeeList.xaml file.
Open MainPage.xaml.
Add a new hyperlink button to the top of the page by adding the following XAML between the two existing hyperlink buttons.
<HyperlinkButton x:Name="Link3" Style="{StaticResource LinkStyle}" NavigateUri="/EmployeeList" TargetName="ContentFrame" Content="Employee List"/> <Rectangle x:Name="Divider2" Style="{StaticResource DividerStyle}"/>
Run the application and notice the new Employee List link on the upper right corner of the page, between the Home and About links. Click on it to display “Employee List” in the body of the page.
Displaying Data
In this section, you create an ADO.NET Entity Data Model for tables in the AdventureWorks sample database. Then, you create a domain service that exposes the entities and displays that data in the client project.
To add a data source
In Solution Explorer, right-click the HRApp.Web project, click Add, and then click New Item.
The Add New Item dialog box is displayed.
In the Data category, select the ADO.NET Entity Data Model template.
Change the name to AdventureWorks.edmx, and then click Add.
The Entity Data Model Wizard opens.
On the Choose Model Contents page, click Generate from database, and then click Next.
On the Choose Your Data Connection page, create a connection to the AdventureWorks database.
Name the entity connection settings AdventureWorks_DataEntities and then click Next.
On the Choose Your Database Objects page, expand the Tables node.
Add check marks next to the Employee, PurchaseOrderDetail, and PurchaseOrderHeader tables.
Name the model namespace AdventureWorks_DataModel and then click Finish.
The entity data model appears in the designer.
Build the solution.
To add a domain service object and query for data
In Solution Explorer, right-click the HRApp.Web project, click Add, and then click New Item.
The Add New Item dialog box is displayed.
In the Web category, select the Domain Service Class template.
Name the new item OrganizationService.
Click Add.
In the Add New Domain Service Class dialog box, select Employee, PurchaseOrderDetail, and PurchaseOrderHeader from the Entities list, and then select Enable editing for each entity.
Ensure that the Enable client access and Generate associated classes for metadata check boxes are selected.
Click OK.
OrganizationService.cs/vb and OrganizationService.metadata.cs/vb files are added to the project.
Open the OrganizationService.cs/vb file.
Notice that query, insert, update, and delete methods have been created for each entity. A query method is always created for an entity. The insert, update, and delete methods were added because Enable editing was selected.
Customize the GetEmployees() query method to return employees sorted by EmployeeID by replacing the generated code with the following code.
Public Function GetEmployees() As IQueryable(Of Employee) Return Me.ObjectContext.Employees.OrderBy(Function(e) e.EmployeeID) End Function
public IQueryable<Employee> GetEmployees() { return this.ObjectContext.Employees.OrderBy(e => e.EmployeeID); }
Build the solution.
Building the solution generates the Domain Context and entities in the client project.
Open EmployeeList.xaml.
From the Toolbox, drag a DataGrid control to Design view just after the TextBlock control.
Dragging a DataGrid to Design view adds a reference to the System.Windows.Controls.Data assembly and adds the sdk prefix to the Page element.
Change the default values for the DataGrid control by removing the Height and Width properties, making it read-only, setting it to automatically generate columns, and setting its minimum height.
<sdk:DataGrid AutoGenerateColumns="True" IsReadOnly="True" Name="dataGrid1" MinHeight="100" />
Save EmployeeList.xaml.
Open EmployeeList.xaml.cs/vb.
Add the following using or Imports statements.
Imports System.ServiceModel.DomainServices.Client
using HRApp.Web; using System.ServiceModel.DomainServices.Client;
Instantiate the OrganizationContext class and load employee data by adding the following code to EmployeeList.xaml.cs/vb.
The OrganizationContext class is automatically generated in the client project based on the OrganizationService class in the server project.
Partial Public Class EmployeeList Inherits Page Dim _OrganizationContext As New OrganizationContext Public Sub New() InitializeComponent() Me.dataGrid1.ItemsSource = _OrganizationContext.Employees _OrganizationContext.Load(_OrganizationContext.GetEmployeesQuery()) End Sub 'Executes when the user navigates to this page. Protected Overrides Sub OnNavigatedTo(ByVal e As System.Windows.Navigation.NavigationEventArgs) End Sub End Class
public partial class EmployeeList : Page { OrganizationContext _OrganizationContext = new OrganizationContext(); public EmployeeList() { InitializeComponent(); this.dataGrid1.ItemsSource = _OrganizationContext.Employees; _OrganizationContext.Load(_OrganizationContext.GetEmployeesQuery()); } // Executes when the user navigates to this page. protected override void OnNavigatedTo(NavigationEventArgs e) { } }
Run the application.
Click the Employee List link to see the DataGrid.
To add a custom query
In the HRApp.Web project, open OrganizationService.cs/vb.
Add a new method named GetSalariedEmployees by adding the following code to the body of the class.
Public Function GetSalariedEmployees() As IQueryable(Of Employee) Return Me.ObjectContext.Employees.Where(Function(e) e.SalariedFlag = True).OrderBy(Function(e) e.EmployeeID) End Function
public IQueryable<Employee> GetSalariedEmployees() { return this.ObjectContext.Employees.Where(e => e.SalariedFlag == true).OrderBy(e => e.EmployeeID); }
Build the solution.
In the client project, open EmployeeList.xaml.cs/vb.
In the constructor, replace the call to GetEmployeesQuery() with a call to GetSalariedEmployeesQuery().
_OrganizationContext.Load(_OrganizationContext.GetSalariedEmployeesQuery())
_OrganizationContext.Load(_OrganizationContext.GetSalariedEmployeesQuery());
Run the application and click the Employee List link.
Notice that all of the displayed employees have the SalariedFlag value checked. Employees with EmployeeID 1, 2, and 4 no longer appear in the list because they are not salaried.
To add a domain data source
Open EmployeeList.xaml.
From the Toolbox, drag the DomainDataSource control to Design view, just before the DataGrid. The DomainDataSource might appear at the bottom of the list of controls.
Tip
If the DomainDataSource control is not in the Toolbox, right click in the Toolbox, and click Choose Items. Under the Silverlight Components tab check DomainDataSource, and click OK.
When you drag the DomainDataSource control to Design view, a reference with the prefix riaControls is created for the System.Windows.Controls namespace in the Page element. Also, a data source icon appears in the lower-left corner of Design view.
For C# solutions, add the following namespace declaration to the XAML file.
xmlns:ds="clr-namespace:HRApp.Web"
For Visual Basic solutions, add the following namespace declaration to the XAML file.
xmlns:ds="clr-namespace:HRApp"
Name the DomainDataSource control employeeDataSource and set the LoadSize, AutoLoad, and query method by replacing the existing XAML with the following XAML.
<riaControls:DomainDataSource Name="employeeDataSource" LoadSize="20" QueryName="GetSalariedEmployees" AutoLoad="True"> </riaControls:DomainDataSource>
Set the DomainContext for the DomainDataSource by adding the following XAML.
<riaControls:DomainDataSource Name="employeeDataSource" LoadSize="20" QueryName="GetSalariedEmployees" AutoLoad="True"> <riaControls:DomainDataSource.DomainContext> <ds:OrganizationContext/> </riaControls:DomainDataSource.DomainContext> </riaControls:DomainDataSource>
Replace the DataGrid with the following XAML.
<sdk:DataGrid AutoGenerateColumns="True" IsReadOnly="True" Name="dataGrid1" MinHeight="100" Height="Auto" ItemsSource="{Binding Data, ElementName=employeeDataSource}" />
Open EmployeeList.xaml.cs/vb.
In the constructor, remove or comment out the code to instantiate the OrganizationContext instance, the call to GetSalariedEmployeesQuery(), and the code to set the DataGrid control’s ItemsSource property.
You no longer need to explicitly load data, since the DomainDataSource will do this automatically.
'Dim _OrganizationContext As New OrganizationContext Public Sub New() InitializeComponent() 'Me.dataGrid1.ItemsSource = _OrganizationContext.Employees '_OrganizationContext.Load(_OrganizationContext.GetSalariedEmployeesQuery()) End Sub
//OrganizationContext _OrganizationContext = new OrganizationContext(); public EmployeeList() { InitializeComponent(); //this.dataGrid1.ItemsSource = _OrganizationContext.Employees; //_OrganizationContext.Load(_OrganizationContext.GetSalariedEmployeesQuery()); }
Run the application and click the Employee List link.
The application works the same as before.
To add sorting, filtering, and paging to the data source
Open EmployeeList.xaml.
In the DomainDataSource, add the following SortDescriptors to specify how data is sorted in the DataGrid.
This XAML shows how to sort the VacationHours column in ascending order.
<riaControls:DomainDataSource Name="employeeDataSource" LoadSize="20" QueryName="GetSalariedEmployees" AutoLoad="True"> <riaControls:DomainDataSource.DomainContext> <ds:OrganizationContext/> </riaControls:DomainDataSource.DomainContext> <riaControls:DomainDataSource.SortDescriptors> <riaControls:SortDescriptor PropertyPath="VacationHours" Direction="Ascending" /> </riaControls:DomainDataSource.SortDescriptors> </riaControls:DomainDataSource>
Run the application and click the Employee List link.
The data is sorted by VacationHours and you can change the sort direction by clicking the column header.
Open EmployeeList.xaml.
To enable the user to filter which records are returned by providing a value, add the following XAML before the DataGrid.
The XAML adds a TextBox control so that the user can input a value.
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left"> <TextBlock VerticalAlignment="Center" Text="Min Vacation Hours Filter" /> <TextBox x:Name="vacationHoursText" Width="75" FontSize="11" Margin="4" Text="0"/> </StackPanel>
In the DomainDataSource, add a filter descriptor that is bound to the TextBox control you added in the previous step.
<riaControls:DomainDataSource.FilterDescriptors> <riaControls:FilterDescriptor PropertyPath="VacationHours" Operator="IsGreaterThanOrEqualTo" IgnoredValue="" Value="{Binding ElementName=vacationHoursText, Path=Text}" /> </riaControls:FilterDescriptor> </riaControls:DomainDataSource.FilterDescriptors>
Run the application and click the Employee List link.
In the Min Vacation Hours Filter text box, type 70.
Notice that the listed employees have VacationHours greater than or equal to 70.
Open EmployeeList.xaml.
From the Toolbox, drag a DataPager control to just below the DataGrid.
Set the page size to 5 and set the source as shown in the following XAML.
<sdk:DataPager PageSize="5" Source="{Binding Data, ElementName=employeeDataSource}" HorizontalAlignment="Left" />
Run the application and click the Employee List link.
You see only 5 rows of filtered data per page and pager controls below the DataGrid.
Creating a Master/Detail View
In this section, you use the DataForm control from the Silverlight Toolkit to provide a detailed view of the data. By default, the Silverlight Business Application project template contains the System.Windows.Controls.Data.DataForm.Toolkit.dll binary in the Libs folder.
To add a DataForm
Open EmployeeList.xaml.
Add the following namespace declaration.
xmlns:dataForm="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
After the DataPager control, add the following XAML to add a DataForm control.
This XAML sets the DataForm attributes and specifies the columns to be displayed.
<dataForm:DataForm x:Name="dataForm1" Header="Employee Information" AutoGenerateFields="False" HorizontalAlignment="Left" AutoEdit="False" AutoCommit="False" Width="400" CurrentItem="{Binding SelectedItem, ElementName=dataGrid1}" Margin="0,12,0,0"> <dataForm:DataForm.EditTemplate> <DataTemplate> <StackPanel> <dataForm:DataField Label="Employee ID"> <TextBox IsReadOnly="True" Text="{Binding EmployeeID, Mode=OneWay}" /> </dataForm:DataField> <dataForm:DataField Label="Login ID"> <TextBox Text="{Binding LoginID, Mode=TwoWay}" /> </dataForm:DataField> <dataForm:DataField Label="Hire Date"> <TextBox Text="{Binding HireDate, Mode=TwoWay}" /> </dataForm:DataField> <dataForm:DataField Label="Marital Status"> <TextBox Text="{Binding MaritalStatus, Mode=TwoWay}" /> </dataForm:DataField> <dataForm:DataField Label="Gender"> <TextBox Text="{Binding Gender, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True }" /> </dataForm:DataField> <dataForm:DataField Label="Vacation Hours"> <TextBox Text="{Binding VacationHours, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True }" /> </dataForm:DataField> <dataForm:DataField Label="Sick Leave Hours"> <TextBox Text="{Binding SickLeaveHours, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True }" /> </dataForm:DataField> <dataForm:DataField Label="Active"> <CheckBox IsChecked="{Binding CurrentFlag, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True }" /> </dataForm:DataField> </StackPanel> </DataTemplate> </dataForm:DataForm.EditTemplate> </dataForm:DataForm>
Run the application and click the Employee List link.
The DataForm displays details of the item selected in the DataGrid.
Updating the Database
When you select the Enable editing check box in the Add New Domain Service Class dialog box, methods are generated in the domain service layer to update, insert, and delete the entity. In this section, you add edit buttons to the user interface of the employee list to enable users to execute these operations.
To update a record
Open the EmployeeList.xaml file.
After the DataForm control, add the following XAML to add a Submit button.
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,12,0,0"> <Button x:Name="submitButton" Width="75" Height="23" Content="Submit" Margin="4,0,0,0" Click="submitButton_Click"/> </StackPanel>
In the DomainDataSource control, specify an event handler for the SubmittedChanges event.
<riaControls:DomainDataSource Name="employeeDataSource" LoadSize="20" QueryName="GetSalariedEmployees" AutoLoad="True" SubmittedChanges="employeeDataSource_SubmittedChanges">
Open EmployeeList.xaml.cs/vb.
Add the following event handler for the button click event.
The submitButton is disabled to prevent the user from submitting the change again as the operation is being processed.
Private Sub submitButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) submitButton.IsEnabled = False employeeDataSource.SubmitChanges() End Sub
private void submitButton_Click(object sender, RoutedEventArgs e) { submitButton.IsEnabled = false; employeeDataSource.SubmitChanges(); }
Add an event handler for the SubmittedChanges event that checks whether the submit operation completed successfully and enables submitButton.
Private Sub employeeDataSource_SubmittedChanges(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SubmittedChangesEventArgs) If (e.HasError) Then MessageBox.Show(String.Format("Changes were not saved: {0}", e.Error.Message)) e.MarkErrorAsHandled() End If submitButton.IsEnabled = True End Sub
private void employeeDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e) { if (e.HasError) { MessageBox.Show(string.Format("Changes were not saved: {0}", e.Error.Message)); e.MarkErrorAsHandled(); } submitButton.IsEnabled = true; }
Run the application and click the Employee List link.
Select an employee and click the pencil icon in the upper-right hand corner of the data form to enable editing.
You can now modify any editable field.
Make changes to the employee data and click OK.
Click the Submit button to save the data.
Changes are saved to the database on the server only when you click the Submit button.
To add custom methods to a domain service
In the HRApp.Web server project, open OrganizationService.cs/vb. File
Add the following custom method named ApproveSabbatical.
Public Sub ApproveSabbatical(ByVal current As Employee) Me.ObjectContext.Employees.AttachAsModified(current) current.CurrentFlag = False End Sub
public void ApproveSabbatical(Employee current) { // Start custom workflow here this.ObjectContext.Employees.AttachAsModified(current); current.CurrentFlag = false; }
Build the solution.
Open EmployeeList.xaml.
After the Submit button, add the following XAML to add an Approve Sabbatical button.
<Button x:Name="approveSabbatical" Width="115" Height="23" Content="Approve Sabbatical" Margin="4,0,0,0" Click="approveSabbatical_Click"/>
Open EmployeeList.xaml.cs/vb.
Add the following event handler for the button click event that calls the ApproveSabbatical domain operation.
Private Sub approveSabbatical_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Dim luckyEmployee As Employee luckyEmployee = dataGrid1.SelectedItem luckyEmployee.ApproveSabbatical() employeeDataSource.SubmitChanges() End Sub
private void approveSabbatical_Click(object sender, RoutedEventArgs e) { Employee luckyEmployee = (Employee)(dataGrid1.SelectedItem); luckyEmployee.ApproveSabbatical(); employeeDataSource.SubmitChanges(); }
Run the application and click the Employee List link.
Click the Approve Sabbatical button and note that the CurrentFlag check box for the selected employee is cleared.
Validating Data
The DataForm control can show validation errors from the data access layer (DAL). For example, if you enter a non-integer value in the VacationHours field, a validation error is displayed. The following illustration shows an example of the validation behavior.
When you select the Generate associated classes for metadata check box in the New Domain Service Class dialog box, a file that contains metadata is created. In this walkthrough, the metadata file is named OrganizationService.metadata.cs/vb. In this section, you add validation attributes to this file. The validation rules will be enforced in the client and server projects.
You also create a user interface to allow the addition of new employee records to the database. The validation rules that you added in the previous sections will automatically be applied in the new user interface.
To add basic validation
In the HRApp.web project, open OrganizationService.metadata.cs/vb.
Add the following attributes to the Gender and VacationHours properties.
<Required()> _ Public Property Gender As String <Range(0, 70)> _ Public Property VacationHours As Short
[Required] public string Gender { get; set; } [Range(0, 70)] public short VacationHours { get; set; }
Build the solution.
Run the application and click the Employee List link.
Select an employee and click the pencil icon in the upper-right hand corner of the data form to enable editing.
Enter a value in the Vacation Hours field that is not within the valid range (0-70) and move the focus to another control.
You see a validation error for vacation hours.
Delete the value in the Gender field and move the focus to another control.
You see a validation error for gender.
To add custom validation
In Solution Explorer, right-click the HRApp.Web project, click Add, and then click New Item.
The Add New Item dialog box is displayed.
In the Code category, select the Code File template.
Name the new item OrganizationService.shared.cs or OrganizationService.shared.vb.
Files that end with .shared.cs or .shared.vb are available in both the client and server projects. Shared files enable you to run the same validation rule in both projects. After you build the solution in a later step, look in the hidden Generated_Code folder on the client, and you will see the OrganizationService.shared.cs/vb file.
Click Add.
To create a customized validation class that verifies the values assigned to the Gender property, add the following code to the shared file.
Imports System Imports System.ComponentModel.DataAnnotations Public Module GenderValidator Public Function IsGenderValid(ByVal gender As String, ByVal context As ValidationContext) As ValidationResult If gender = "M" OrElse gender = "m" OrElse gender = "F" OrElse gender = "f" Then Return ValidationResult.Success Else Return New ValidationResult("The Gender field only has two valid values 'M'/'F'", New String() {"Gender"}) End If End Function End Module
using System; using System.ComponentModel.DataAnnotations; namespace HRApp.Web { public static class GenderValidator { public static ValidationResult IsGenderValid(string gender, ValidationContext context) { if (gender == "M" || gender == "m" || gender == "F" || gender == "f") { return ValidationResult.Success; } else { return new ValidationResult("The Gender field only has two valid values 'M'/'F'", new string[] { "Gender" }); } } } }
Open OrganizationService.metadata.cs/vb.
Add the following custom validation attribute to the Gender property.
<Required()> _ <CustomValidation(GetType(GenderValidator), "IsGenderValid")> _ Public Property Gender As String
[CustomValidation(typeof(HRApp.Web.GenderValidator), "IsGenderValid")] [Required] public string Gender { get; set; }
Build the solution.
Run the application and click the Employee List link.
Select an employee and click the pencil icon in the upper-right hand corner of the data form to enable editing.
Enter a value in the Gender field that is not M or F and move the focus to another control.
The result of the custom validation appears.
Adding New Records
In this section, you add a form that enables the user to create a new record in the Employee table.
To add a new record
In the HRApp project, add a new item.
In the Silverlight category, select the Silverlight Child Window template.
Name the new item EmployeeRegistrationWindow.xaml.
Click Add.
Open EmployeeRegistrationWindow.xaml.cs/vb.
If you are using C#, add the following using statement.
using HRApp.Web;
Add a property for the new Employee entity that is created with the user values.
Public Property NewEmployee As Employee
public Employee NewEmployee { get; set; }
Open EmployeeRegistrationWindow.xaml.
Add the following namespace declaration to EmployeeRegistrationWindow.xaml to use the DataForm control in this window.
xmlns:dataForm="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
Add the following DataForm control to EmployeeRegistrationWindow.xaml just before the Cancel button.
<dataForm:DataForm x:Name="addEmployeeDataForm" AutoGenerateFields="False" AutoCommit="True" AutoEdit="True" CommandButtonsVisibility="None"> <dataForm:DataForm.EditTemplate> <DataTemplate> <StackPanel> <dataForm:DataField Label="Login ID"> <TextBox Text="{Binding LoginID, Mode=TwoWay}" /> </dataForm:DataField> <dataForm:DataField Label="National ID"> <TextBox Text="{Binding NationalIDNumber, Mode=TwoWay}" /> </dataForm:DataField> <dataForm:DataField Label="Title"> <TextBox Text="{Binding Title, Mode=TwoWay}" /> </dataForm:DataField> <dataForm:DataField Label="Marital Status"> <TextBox Text="{Binding MaritalStatus, Mode=TwoWay}" /> </dataForm:DataField> <dataForm:DataField Label="Gender"> <TextBox Text="{Binding Gender, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True }" /> </dataForm:DataField> <dataForm:DataField Label="Salaried"> <CheckBox IsChecked="{Binding SalariedFlag, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True }" /> </dataForm:DataField> <dataForm:DataField Label="Active"> <CheckBox IsChecked="{Binding CurrentFlag, Mode=TwoWay,NotifyOnValidationError=True, ValidatesOnExceptions=True }" /> </dataForm:DataField> </StackPanel> </DataTemplate> </dataForm:DataForm.EditTemplate> </dataForm:DataForm>
Open EmployeeRegistrationWindow.xaml.cs/vb.
Add the following code to create a new Employee instance and handle either committing the new instance or canceling the insertion.
Partial Public Class EmployeeRegistrationWindow Inherits ChildWindow Public Sub New() InitializeComponent() NewEmployee = New Employee addEmployeeDataForm.CurrentItem = NewEmployee addEmployeeDataForm.BeginEdit() End Sub Public Property NewEmployee As Employee Private Sub OKButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles OKButton.Click Me.addEmployeeDataForm.CommitEdit() Me.DialogResult = True End Sub Private Sub CancelButton_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles CancelButton.Click NewEmployee = Nothing addEmployeeDataForm.CancelEdit() Me.DialogResult = False End Sub End Class
public partial class EmployeeRegistrationWindow : ChildWindow { public EmployeeRegistrationWindow() { InitializeComponent(); NewEmployee = new Employee(); addEmployeeDataForm.CurrentItem = NewEmployee; addEmployeeDataForm.BeginEdit(); } public Employee NewEmployee { get; set; } private void OKButton_Click(object sender, RoutedEventArgs e) { addEmployeeDataForm.CommitEdit(); this.DialogResult = true; } private void CancelButton_Click(object sender, RoutedEventArgs e) { NewEmployee = null; addEmployeeDataForm.CancelEdit(); this.DialogResult = false; } }
Open EmployeeList.xaml.
Between the DataPager and the DataForm, add the following XAML to create a button named addNewEmployee.
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,12,0,0"> <Button x:Name="addNewEmployee" Width="90" Height="23" Content="Add Employee" Margin="4,0,0,0" Click="addNewEmployee_Click"/> </StackPanel>
Open EmployeeList.xaml.cs/vb.
Add the following code to handle the button click event and display the EmployeeRegistrationWindow.
Private Sub addNewEmployee_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Dim addEmp As New EmployeeRegistrationWindow() AddHandler addEmp.Closed, AddressOf addEmp_Closed addEmp.Show() End Sub
private void addNewEmployee_Click(object sender, RoutedEventArgs e) { EmployeeRegistrationWindow addEmp = new EmployeeRegistrationWindow(); addEmp.Closed += new EventHandler(addEmp_Closed); addEmp.Show(); }
Add the following method to handle the closed event for EmployeeRegistrationWindow.
Private Sub addEmp_Closed(ByVal sender As Object, ByVal e As System.EventArgs) Dim emp As EmployeeRegistrationWindow = sender If Not emp.NewEmployee Is Nothing Then Dim _OrganizationContext As OrganizationContext = employeeDataSource.DomainContext _OrganizationContext.Employees.Add(emp.NewEmployee) employeeDataSource.SubmitChanges() End If End Sub
void addEmp_Closed(object sender, EventArgs e) { EmployeeRegistrationWindow emp = (EmployeeRegistrationWindow)sender; if (emp.NewEmployee != null) { OrganizationContext _OrganizationContext = (OrganizationContext)(employeeDataSource.DomainContext); _OrganizationContext.Employees.Add(emp.NewEmployee); employeeDataSource.SubmitChanges(); } }
Open OrganizationService.cs/vb.
Modify the InsertEmployee method by adding the following code.
Public Sub InsertEmployee(ByVal employee As Employee) employee.HireDate = DateTime.Now employee.ModifiedDate = DateTime.Now employee.VacationHours = 0 employee.SickLeaveHours = 0 employee.rowguid = Guid.NewGuid() employee.ContactID = 1001 employee.BirthDate = New DateTime(1967, 3, 18) If ((employee.EntityState = EntityState.Detached) _ = False) Then Me.ObjectContext.ObjectStateManager.ChangeObjectState(employee, EntityState.Added) Else Me.ObjectContext.Employees.AddObject(employee) End If End Sub
public void InsertEmployee(Employee employee) { employee.HireDate = DateTime.Now; employee.ModifiedDate = DateTime.Now; employee.VacationHours = 0; employee.SickLeaveHours = 0; employee.rowguid = Guid.NewGuid(); employee.ContactID = 1001; employee.BirthDate = new DateTime(1967, 3, 18); if ((employee.EntityState != EntityState.Detached)) { this.ObjectContext.ObjectStateManager.ChangeObjectState(employee, EntityState.Added); } else { this.ObjectContext.Employees.AddObject(employee); } }
Run the application and click the Employee List link.
Click the Add Employee button.
The EmployeeRegistrationWindow opens.
Add data in the window and select the Salaried check box.
Click OK.
Refresh the page and ensure that the new employee appears in the DataGrid.
Authenticating Users
In this section, you restrict access to the ApproveSabbatical method to only authenticated users.
To add authentication
Open OrganizationService.cs/vb.
Add the RequiresAuthentication attribute to the ApproveSabbatical method.
When you apply the RequiresAuthentication attribute to a domain operation, you ensure that only authenticated users can call the operation. If an anonymous user clicks the Approve Sabbatical button, the operation is not executed.
<RequiresAuthentication()> _ Public Sub ApproveSabbatical(ByVal current As Employee) Me.ObjectContext.Employees.AttachAsModified(current) current.CurrentFlag = False End Sub
[RequiresAuthentication] public void ApproveSabbatical(Employee current) { // Start custom workflow here this.ObjectContext.Employees.AttachAsModified(current); current.CurrentFlag = false; }
Open EmployeeList.xaml.cs/vb.
Add the following using or Imports statements.
Imports System.ServiceModel.DomainServices.Client.ApplicationServices Imports HRApp.LoginUI
using System.ServiceModel.DomainServices.Client.ApplicationServices; using HRApp.LoginUI;
Modify the approveSabbatical_Click method and add a LoggedIn handler to check whether the user is authenticated.
Private Sub approveSabbatical_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) If WebContext.Current.User IsNot Nothing AndAlso WebContext.Current.User.IsAuthenticated Then Dim luckyEmployee As Employee = dataGrid1.SelectedItem luckyEmployee.ApproveSabbatical() employeeDataSource.SubmitChanges() Else AddHandler WebContext.Current.Authentication.LoggedIn, AddressOf Current_LoginCompleted Dim newWindow As New LoginRegistrationWindow newWindow.Show() End If End Sub Private Sub Current_LoginCompleted(ByVal sender As Object, ByVal e As AuthenticationEventArgs) Dim luckyEmployee As Employee = dataGrid1.SelectedItem luckyEmployee.ApproveSabbatical() employeeDataSource.SubmitChanges() RemoveHandler WebContext.Current.Authentication.LoggedIn, AddressOf Current_LoginCompleted End Sub
private void approveSabbatical_Click(object sender, RoutedEventArgs e) { if (WebContext.Current.User.IsAuthenticated) { Employee luckyEmployee = (Employee)(dataGrid1.SelectedItem); luckyEmployee.ApproveSabbatical(); employeeDataSource.SubmitChanges(); } else { WebContext.Current.Authentication.LoggedIn += Authentication_LoggedIn; new LoginRegistrationWindow().Show(); } } private void Authentication_LoggedIn(object sender, AuthenticationEventArgs e) { Employee luckyEmployee = (Employee)(dataGrid1.SelectedItem); luckyEmployee.ApproveSabbatical(); employeeDataSource.SubmitChanges(); WebContext.Current.Authentication.LoggedIn -= Authentication_LoggedIn; }
Run the application and click the Employee List link.
Select an employee record and click the Approve Sabbatical button.
You are redirected to the login window.
Click the Register now link.
Complete the required fields to create a new account.
Click OK.
You are logged in with that account and your name appears in a bar below the navigation links.
Displaying Related Data
With RIA Services you can easily work with data from related tables. In this section, you add a new Silverlight Page and display data from the PurchaseOrderHeader and PurchaseOrderDetail tables. You can also customize the data modification operations so the related data is modified together. For an example of modifying the data in related tables through a domain operation, see Compositional Hierarchies.
To display data from related tables
In the HRApp project, right-click the Views folder, click Add, and then click New Item.
Add a new Silverlight Page named PurchaseOrders.xaml.
Open PurchaseOrders.xaml.
Add the following XAML between the <Grid> tags.
<ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" > <StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}"> <TextBlock Text="Purchase Orders" Style="{StaticResource HeaderTextStyle}"/> </StackPanel> </ScrollViewer>
Open MainPage.xaml.
After the EmployeeList link, add the following XAML to include a link to the PurchaseOrders page.
<Rectangle x:Name="Divider3" Style="{StaticResource DividerStyle}"/> <HyperlinkButton x:Name="Link4" Style="{StaticResource LinkStyle}" NavigateUri="/PurchaseOrders" TargetName="ContentFrame" Content="Purchase Orders"/>
Open OrganizationService.metadata.cs/vb.
Add the Include and Composition attributes to the PurchaseOrderDetails property in the PurchaseOrderHeaderMetadata class.
<Include()> _ <Composition()> _ Public Property PurchaseOrderDetails As EntityCollection(Of PurchaseOrderDetail)
[Include] [Composition] public EntityCollection<PurchaseOrderDetail> PurchaseOrderDetails { get; set; }
Open OrganizationService.cs/vb.
Change the GetPurchaseOrderHeaders method so that the related records in PurchaseOrderDetails are also retrieved with the query.
Public Function GetPurchaseOrderHeaders() As IQueryable(Of PurchaseOrderHeader) Return Me.ObjectContext.PurchaseOrderHeaders.Include("PurchaseOrderDetails").OrderBy(Function(p) p.PurchaseOrderID) End Function
public IQueryable<PurchaseOrderHeader> GetPurchaseOrderHeaders() { return this.ObjectContext.PurchaseOrderHeaders.Include("PurchaseOrderDetails").OrderBy(p => p.PurchaseOrderID); }
Open PurchaseOrders.xaml.
On the Data menu, click Show Data Sources to open the Data Sources window.
Drag the PurchaseOrderHeader node to the design surface for PurchaseOrders.xaml.
A DataGrid with columns from the PurchaseOrderHeader table appears.
In the Data Sources window, expand the PurchaseOrderHeader node.
Drag the PurchaseOrderDetails node that is located inside the PurchaseOrderHeader node to the design surface just under the DataGrid for PurchaseOrderHeader.
A DataGrid with columns from the PurchaseOrderDetails table appears.
In XAML view, find the DataGrid controls for both PurchaseOrderHeader and PurchaseOrderDetails.
Remove the Width=”400” property from each DataGrid so that it will fill the available width.
Before the PurchaseOrderHeader DataGrid, add the following TextBlock control to label the data.
<TextBlock Text="Order Headers"></TextBlock>
Before the PurchaseOrderDetails DataGrid, add the following TextBlock control to label the data.
<TextBlock Text="Order Details"></TextBlock>
To limit the number of records that are retrieved, add the following filter descriptor to the DomainDataSource control.
<riaControls:DomainDataSource.FilterDescriptors> <riaControls:FilterDescriptor PropertyPath="PurchaseOrderID" Operator="IsLessThan" Value="10"></riaControls:FilterDescriptor> </riaControls:DomainDataSource.FilterDescriptors>
The following shows the complete XAML for PurchaseOrders.xaml.
<navigation:Page x:Class="HRApp.Views.PurchaseOrders" xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="https://schemas.microsoft.com/expression/blend/2008" xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation" d:DesignWidth="640" d:DesignHeight="480" Title="PurchaseOrders Page" xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices" xmlns:my="clr-namespace:HRApp.Web" xmlns:sdk="https://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"> <sdk:Page.Resources> <CollectionViewSource x:Key="purchaseOrderHeaderPurchaseOrderDetailsViewSource" Source="{Binding Path=Data.PurchaseOrderDetails, ElementName=purchaseOrderHeaderDomainDataSource}" /> </sdk:Page.Resources> <Grid x:Name="LayoutRoot"> <ScrollViewer x:Name="PageScrollViewer" Style="{StaticResource PageScrollViewerStyle}" > <StackPanel x:Name="ContentStackPanel" Style="{StaticResource ContentStackPanelStyle}"> <TextBlock Text="Purchase Orders" Style="{StaticResource HeaderTextStyle}"/> <riaControls:DomainDataSource AutoLoad="True" d:DesignData="{d:DesignInstance my:PurchaseOrderHeader, CreateList=true}" Height="0" LoadedData="purchaseOrderHeaderDomainDataSource_LoadedData_1" Name="purchaseOrderHeaderDomainDataSource" QueryName="GetPurchaseOrderHeadersQuery" Width="0"> <riaControls:DomainDataSource.DomainContext> <my:OrganizationContext /> </riaControls:DomainDataSource.DomainContext> <riaControls:DomainDataSource.FilterDescriptors> <riaControls:FilterDescriptor PropertyPath="PurchaseOrderID" Operator="IsLessThan" Value="10"></riaControls:FilterDescriptor> </riaControls:DomainDataSource.FilterDescriptors> </riaControls:DomainDataSource> <TextBlock Text="Order Headers"></TextBlock> <sdk:DataGrid AutoGenerateColumns="False" Height="200" ItemsSource="{Binding ElementName=purchaseOrderHeaderDomainDataSource, Path=Data}" Name="purchaseOrderHeaderDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected"> <sdk:DataGrid.Columns> <sdk:DataGridTextColumn x:Name="employeeIDColumn" Binding="{Binding Path=EmployeeID}" Header="Employee ID" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="freightColumn" Binding="{Binding Path=Freight}" Header="Freight" Width="SizeToHeader" /> <sdk:DataGridTemplateColumn x:Name="modifiedDateColumn" Header="Modified Date" Width="SizeToHeader"> <sdk:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <sdk:DatePicker SelectedDate="{Binding Path=ModifiedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellEditingTemplate> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Path=ModifiedDate, StringFormat=\{0:d\}}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> <sdk:DataGridTemplateColumn x:Name="orderDateColumn" Header="Order Date" Width="SizeToHeader"> <sdk:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <sdk:DatePicker SelectedDate="{Binding Path=OrderDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellEditingTemplate> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Path=OrderDate, StringFormat=\{0:d\}}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> <sdk:DataGridTextColumn x:Name="purchaseOrderIDColumn" Binding="{Binding Path=PurchaseOrderID, Mode=OneWay}" Header="Purchase Order ID" IsReadOnly="True" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="revisionNumberColumn" Binding="{Binding Path=RevisionNumber}" Header="Revision Number" Width="SizeToHeader" /> <sdk:DataGridTemplateColumn x:Name="shipDateColumn" Header="Ship Date" Width="SizeToHeader"> <sdk:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <sdk:DatePicker SelectedDate="{Binding Path=ShipDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellEditingTemplate> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Path=ShipDate, StringFormat=\{0:d\}}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> <sdk:DataGridTextColumn x:Name="shipMethodIDColumn" Binding="{Binding Path=ShipMethodID}" Header="Ship Method ID" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="statusColumn" Binding="{Binding Path=Status}" Header="Status" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="subTotalColumn" Binding="{Binding Path=SubTotal}" Header="Sub Total" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="taxAmtColumn" Binding="{Binding Path=TaxAmt}" Header="Tax Amt" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="totalDueColumn" Binding="{Binding Path=TotalDue}" Header="Total Due" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="vendorIDColumn" Binding="{Binding Path=VendorID}" Header="Vendor ID" Width="SizeToHeader" /> </sdk:DataGrid.Columns> </sdk:DataGrid> <TextBlock Text="Order Details"></TextBlock> <sdk:DataGrid AutoGenerateColumns="False" Height="200" ItemsSource="{Binding Source={StaticResource purchaseOrderHeaderPurchaseOrderDetailsViewSource}}" Name="purchaseOrderDetailsDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected"> <sdk:DataGrid.Columns> <sdk:DataGridTemplateColumn x:Name="dueDateColumn" Header="Due Date" Width="SizeToHeader"> <sdk:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <sdk:DatePicker SelectedDate="{Binding Path=DueDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellEditingTemplate> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Path=DueDate, StringFormat=\{0:d\}}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> <sdk:DataGridTextColumn x:Name="lineTotalColumn" Binding="{Binding Path=LineTotal}" Header="Line Total" Width="SizeToHeader" /> <sdk:DataGridTemplateColumn x:Name="modifiedDateColumn1" Header="Modified Date" Width="SizeToHeader"> <sdk:DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <sdk:DatePicker SelectedDate="{Binding Path=ModifiedDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellEditingTemplate> <sdk:DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Text="{Binding Path=ModifiedDate, StringFormat=\{0:d\}}" /> </DataTemplate> </sdk:DataGridTemplateColumn.CellTemplate> </sdk:DataGridTemplateColumn> <sdk:DataGridTextColumn x:Name="orderQtyColumn" Binding="{Binding Path=OrderQty}" Header="Order Qty" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="productIDColumn" Binding="{Binding Path=ProductID}" Header="Product ID" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="purchaseOrderDetailIDColumn" Binding="{Binding Path=PurchaseOrderDetailID}" Header="Purchase Order Detail ID" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="purchaseOrderIDColumn1" Binding="{Binding Path=PurchaseOrderID}" Header="Purchase Order ID" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="receivedQtyColumn" Binding="{Binding Path=ReceivedQty}" Header="Received Qty" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="rejectedQtyColumn" Binding="{Binding Path=RejectedQty}" Header="Rejected Qty" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="stockedQtyColumn" Binding="{Binding Path=StockedQty}" Header="Stocked Qty" Width="SizeToHeader" /> <sdk:DataGridTextColumn x:Name="unitPriceColumn" Binding="{Binding Path=UnitPrice}" Header="Unit Price" Width="SizeToHeader" /> </sdk:DataGrid.Columns> </sdk:DataGrid> </StackPanel> </ScrollViewer> </Grid> </navigation:Page>
Run the application and click the Purchase Orders link.
Select different records in the PurchaseOrderHeader DataGrid.
Notice that the related PurchaseOrderDetail records are automatically displayed.
Next Steps
This walkthrough has given you a tour of many of the features in RIA Services. To learn the details of specific areas, see the other walkthroughs in this documentation.
See Also
Tasks
Walkthrough: Displaying Data in a Silverlight Business Application
Walkthrough: Using Authentication Service with Silverlight Business Application