WCF RIA Services simplifies the development of n-tier solutions for Rich
Internet Applications (RIA), such as Silverlight applications. A common problem
when developing an n-tier RIA solution is coordinating application logic between
the middle tier and the presentation tier. To create the best user experience,
you want your RIA client to be aware of the application logic that resides on
the server, but you do not want to develop and maintain the application logic on
both the presentation tier and the middle tier. RIA Services solves this problem
by providing framework of components, tools, and services that make the
application logic on the server available to the RIA client without requiring
you to manually duplicate that programming logic. You can create a RIA client
that is aware of business rules and know that the client is automatically
updated with the latest middle tier logic every time that the solution is
re-compiled.
The following illustration shows a simplified version of an n-tier application.
RIA Services focuses on the box between the presentation tier and the data
access layer (DAL) to facilitate n-tier development with a RIA client.
RIA Services adds tools to Visual Studio that enable linking client and server
projects in a single solution and generating code for the client project from
the middle-tier code. The framework components support prescriptive patterns for
writing application logic so that it can be reused on the presentation tier.
Services for common scenarios, such as authentication and user settings
management, are provided to reduce development time.
In RIA Services, you expose data from the server project to client project by
adding domain services. The RIA Services framework implements each domain
service as a Windows Communication Foundation (WCF) service. Therefore, you can
apply the concepts you know from WCF services to domain services when
customizing the configuration.
Let's create one Silverlight Business Application. Silverlight Business
Application is template available if RIA Services is installed.
Remember we have not checked the checkbox for "Enable .NET RIA Services", now we
need to check it for enabling .NET RIA Services.
The Business Application comes with the following solution structure, where we
can find different folders for different functionalities.
Now we can take any one of the above described methods for Data Access such as
LINQ to SQL Classes or ADO.NET Entity Data Model. Let's take an ADO.NET Entity
Data Model as our Data Access method.
After creating the model, let's see how we can add RIA functionality to
Silverlight project.
Domain Service Class
Domain services are WCF services that expose the data access layer to the client
project. When you create an instance of a domain service, you specify the entity
classes that you want to expose and the data operations that are permitted
through the domain service.
DomainService and Derived Classes
The DomainService class is the base class for all classes that serve as domain
services. WCF RIA Services also provides the LinqToEntitiesDomainService<(Of
<(<'TContext>)>)> class, which is an abstract class that derives from
DomainService. The LinqToSqlDomainService<(Of <(<'TContext>)>)> class is
available in the RIA Services Toolkit.
To create a domain service that binds to an ADO.NET Entity model, you create a
class that derives from LinqToEntitiesDomainService<(Of <(<'TContext>)>)>. To
create a domain service that binds to a custom data object, you create a class
that derives from DomainService. When you use the Add New Domain Service Class
dialog box to create a domain service, the correct type of domain service based
on the entities you expose is automatically created.
A domain service class must be marked with the EnableClientAccessAttribute
attribute to make the service available to the client project.
WCF and Domain Services
As a WCF service, the domain service builds upon WCF concepts. The domain
service preserves the following:
- Standard usage of WCF services.
- Existing WCF programming models constructs, such as operation contracts, operation behaviors, and service behaviors.
- Standard WCF customization capabilities, such as binding configuration, behavior configuration, and management infrastructure.
The domain context communicates with the domain
service by using the WCF ChannelFactory to create a channel and passing to it a
service contract that was generated from the domain service.
By default, only the Binary endpoint is enabled for domain services.
Let's add a Domain Service Class to the Web Project.
After pressing Add button you would be notified to add entity to the Domain
Service Class.
Remember if you are not getting any Entity Frameworks listed, then just press
cancel and rebuild the Web Project. Follow the above steps again.
By Checking the checkboxes we are enabling Client Access and by Exposing OData
endpoint to share data over the web. OData is an emerging set of extensions for
the ATOM protocol. By checking the option the Domain Service would will be
exposed as OData feed.
We can see that along with the EmployeeDomainService.cs we have another CS file
auto generated.
As the filename specifies metadata.cs, it consists of the Entity properties. The
following listing shows the details of it.
- namespace RIASilverlight4Sample01.Web
- {
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.ComponentModel.DataAnnotations;
- using System.Linq;
- using System.Web.DomainServices;
- using System.Web.Ria;
- using System.Web.Ria.Services;
-
-
-
- [MetadataTypeAttribute(typeof(Employee.EmployeeMetadata))]
- public partial class Employee
- {
-
-
-
-
-
-
-
-
-
-
- internal sealed class EmployeeMetadata
- {
-
-
- private EmployeeMetadata()
- {
- }
-
- public string Contact;
-
- public string EmailID;
-
- public string FirstName;
-
- public long ID;
-
- public string LastName;
- }
- }
- }
As you see in the listing above, we have an internal sealed class that consists
of the properties auto generated.
We can set attributes for specific properties for validation purposes; we will
see this in later descriptions.
Now if you look at the EmployeeDomainService class it has all the CRUD operation
methods auto generated. See following listing.
- namespace RIASilverlight4Sample01.Web
- {
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.ComponentModel.DataAnnotations;
- using System.Data;
- using System.Linq;
- using System.Web.DomainServices;
- using System.Web.DomainServices.Providers;
- using System.Web.Ria;
- using System.Web.Ria.Services;
-
-
-
-
-
-
- [EnableClientAccess()]
- public class EmployeeDomainService : LinqToEntitiesDomainService<EmployeeDBEntities>
- {
-
-
-
-
-
- [Query(IsDefault = true)]
- public IQueryable<Employee> GetEmployees()
- {
- return this.ObjectContext.Employees;
- }
-
- public void InsertEmployee(Employee employee)
- {
- if ((employee.EntityState != EntityState.Added))
- {
- if ((employee.EntityState != EntityState.Detached))
- {
- this.ObjectContext.ObjectStateManager.ChangeObjectState(employee, EntityState.Added);
- }
- else
- {
- this.ObjectContext.AddToEmployees(employee);
- }
- }
- }
-
- public void UpdateEmployee(Employee currentEmployee)
- {
- if ((currentEmployee.EntityState == EntityState.Detached))
- {
- this.ObjectContext.AttachAsModified(currentEmployee, this.ChangeSet.GetOriginal(currentEmployee));
- }
- }
-
- public void DeleteEmployee(Employee employee)
- {
- if ((employee.EntityState == EntityState.Detached))
- {
- this.ObjectContext.Attach(employee);
- }
- this.ObjectContext.DeleteObject(employee);
- }
- }
- }
The listing above displays the methods that are auto generated, such as:
GetEmployees(), InsertEmployee(), UpdateEmployee(), and DeleteEmployee().
The GetEmployees method displays all data available for the Employee entity. We
can add some code to filter it, or we can even sort it.
- public IQueryable<Employee> GetEmployees()
- {
- return this.ObjectContext.Employees.OrderBy(e => e.FirstName);
- }
Data Operations
You add methods to a domain service that perform the data operation you want to
expose. For example, you can add methods that perform the following operations:
-
Query
-
Update
-
Insert
-
Delete
In addition, you can add the following more complicated operations:
Conventions
When you add methods to perform these operations, the method must match the
expected signature for that operation. In addition to matching the signature,
the method must include a name prefix that matches the naming convention for
that data operation. If the name of the method does not start with the expected
prefix, you must apply the corresponding attribute for that operation. The
attribute is optional if the name of the operation matches the naming
convention. Using the naming convention provides a more consistent experience
for developers.
You cannot overload methods that are domain operations. You must specify a
unique name for each method that can be called from the client project. All
methods representing domain service operations must be public. The methods must
use serializable types for parameters and return types.
You can prevent a method from being exposed by adding the IgnoreAttribute
attribute to the method.
The data operation signatures are provided in the following tables.
Query
The query method in the domain context typically has the same name as the domain
service query method plus a postfix of Query. For example, a GetEmployeesQuery
method in the domain context is generated from a GetEmployees method in the
domain service. The domain context query method returns an EntityQuery object
that you can use to apply additional operations.
All queries from a domain context are executed asynchronously. To execute the
query, you pass the EntityQuery object as a parameter in the Load method.
Return value
|
IEnumerable<T>,IQueryable<T>, or entity
|
Parameters
|
Any number
|
Name Prefix
|
Any name
|
Attribute
|
[Query] (C#)
|
Example
|
public IQueryable<Product> GetProducts() (C#)
|
Update
When the domain service includes methods for updating, inserting, and deleting
entities, those methods are not generated in the domain context. Instead, you
use the SubmitChanges method on the domain context and the proper operations on
the domain service are called. No changes in the data source are made until you
call SubmitChanges. You can cancel pending changes by calling the RejectChanges
method.
The DomainContext class also provides the HasChanges and EntityContainer
properties to enable you to evaluate pending changes. The EntityContainer object
for a domain context tracks the pending changes. Pending changes does not
include calls to invoke operations in the domain service because invoke
operations are executed immediately when they are called. When you call
SubmitChanges, all pending changes are sent to the domain service together.
Return value
|
None
|
Parameters
|
Entity
|
Name Prefix
|
Update, Change, or Modify
|
Attribute
|
[Update] (C#)
|
Example
|
public void UpdateProduct(Product product) (C#)
|
Insert
The expected signature values for an insert operation.
Return value
|
None
|
Parameters
|
Entity
|
Name Prefix
|
Insert, Add, or Create
|
Attribute
|
[Insert] (C#)
|
Example
|
public void InsertProduct(Product product) (C#)
|
Delete
The expected signature values for a delete operation.
Return value
|
None
|
Parameters
|
Entity
|
Name Prefix
|
Delete or Remove
|
Attribute
|
[Delete] (C#)
|
Example
|
public void DeleteProduct(Product product) (C#)
|
Invoke
The expected signature values for an invoke operation.
The domain context will contain a method for each service operation on the
domain service. Unlike domain operations, service operations are executed
immediately. You do not call the SubmitChanges method. Service operations are
executed asynchronously. The service operation returns an InvokeOperation
object. You retrieve the value of the Value property to get the returned value
from the service operation.
Return value
|
Any
|
Parameters
|
Any
|
Name Prefix
|
Any
|
Attribute
|
[Invoke] (C#)
|
Example
|
[Invoke]
public decimal GetCompetitorsPrice(Product product) (C#)
|
Named Update
The expected signature values for a named update operation.
Parameters
|
Entity any number of other parameters
|
Name Prefix
|
Any name other than one starting with the prefixes for Insert, Update, or Delete
|
Attribute
|
[Update(UsingCustomMethod=true] (C#)
|
Example
|
[Update(UsingCustomMethod=true]
public void DiscountProduct(Product product, int percentage) (C#)
|
As you see in previous listing we have given an OrderBy method where all the
Employees would be ordered by itsit"s FirstName property. By default Visual
Studio makes read operation as the default query.
In InsertEmployee method you can see an Employee entity is being passed, and
based on the EntityState the data would be inserted.
- public void InsertEmployee(Employee employee) {
- if ((employee.EntityState != EntityState.Added)) {
- if ((employee.EntityState != EntityState.Detached)) {
- this.ObjectContext.ObjectStateManager.ChangeObjectState(employee, EntityState.Added);
- } else {
- this.ObjectContext.AddToEmployees(employee);
- }
- }
- }
In UpdateEmployee method using ObjectContext's AttachAsModified method the
particular Employee could can be updated.
- public void UpdateEmployee(Employee currentEmployee) {
- if ((currentEmployee.EntityState == EntityState.Detached)) {
-
- this.ObjectContext.AttachAsModified(currentEmployee, this.ChangeSet.GetOriginal(currentEmployee));
-
- }
- }
In DeleteEmployee method, ObjectContext's DeleteObject method would delete a
particular Employee in respect to the EntityState.
- public void DeleteEmployee(Employee employee) {
- if ((employee.EntityState == EntityState.Detached)) {
- this.ObjectContext.Attach(employee);
- }
- this.ObjectContext.DeleteObject(employee);
- }
The web.config file is updated with the addition of the Domain Service Class to
the project.
- <?xml version="1.0" encoding="utf-8"?>
- <configuration>
- <configSections>
- <sectionGroup name="system.serviceModel">
- <section name="domainServices" type="System.ServiceModel.DomainServices.Hosting.DomainServicesSection, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
- allowDefinition="MachineToApplication"
- requirePermission="false" />
- </sectionGroup>
- </configSections>
- <system.web>
- <httpModules>
- <add name="DomainServiceModule" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
- </httpModules>
- <compilation debug="true" targetFramework="4.0">
- <assemblies>
- <add assembly="System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
- </assemblies>
- </compilation>
- <roleManager enabled="true" />
- <authentication mode="Forms">
- <forms name=".BusinessApplication02_ASPXAUTH" />
- </authentication>
- <profile>
- <properties>
- <add name="FriendlyName" />
- </properties>
- </profile>
- </system.web>
- <system.webServer>
- <validation validateIntegratedModeConfiguration="false" />
- <modules runAllManagedModulesForAllRequests="true">
- <add name="DomainServiceModule"
- preCondition="managedHandler" type="System.ServiceModel.DomainServices.Hosting.DomainServiceHttpModule, System.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
- </modules>
- </system.webServer>
- <system.serviceModel>
- <domainServices>
- <endpoints>
- <add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
- </endpoints>
- </domainServices>
- <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
- </system.serviceModel>
- <connectionStrings>
- <add name="EmployeeDBEntities"
- connectionString="metadata=res:
- provider=System.Data.SqlClient;provider connection string="Data Source=B314LTRV\SQLEXPRESS;Initial Catalog=EmployeeDB;Integrated Security=True;MultipleActiveResultSets=True"" providerName="System.Data.EntityClient" />
- </connectionStrings>
- </configuration>
Let's go to the Silverlight project and add our custom page to display Employee
data.
Remember to add all the Silverlight pages into View folder.
We need to set the navigation to our newly added Employee.xaml page in
MainPage.xaml
- <UserControl
- x:Class="RIASilverlight4Sample01.MainPage"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
- xmlns:uriMapper="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
- xmlns:dataControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d"
- d:DesignWidth="640"
- d:DesignHeight="480">
- <Grid x:Name="LayoutRoot" Style="{StaticResource LayoutRootGridStyle}">
- <Border x:Name="ContentBorder" Style="{StaticResource ContentBorderStyle}">
- <navigation:Frame x:Name="ContentFrame"
- Style="{StaticResource ContentFrameStyle}"
- Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
- <navigation:Frame.UriMapper>
- <uriMapper:UriMapper>
- <uriMapper:UriMapping Uri="" MappedUri="/Views/Home.xaml"/>
- <uriMapper:UriMapping Uri="/{pageName}"
- MappedUri="/Views/{pageName}.xaml"/>
- </uriMapper:UriMapper>
- </navigation:Frame.UriMapper>
- </navigation:Frame>
- </Border>
- <Grid Style="{StaticResource NavigationOuterGridStyle}">
- <Grid x:Name="NavigationGrid"
- Style="{StaticResource NavigationGridStyle}">
- <Border x:Name="BrandingBorder"
- Style="{StaticResource BrandingBorderStyle}">
- <StackPanel x:Name="BrandingStackPanel"
- Style="{StaticResource BrandingStackPanelStyle}">
- <ContentControl Style="{StaticResource LogoIcon}"/>
- <TextBlock x:Name="ApplicationNameTextBlock"
- Style="{StaticResource ApplicationNameStyle}" Text="{Binding
- ApplicationStrings.ApplicationName, Source={StaticResource ResourceWrapper}}"/>
- </StackPanel>
- </Border>
- <Border x:Name="LinksBorder"
- Style="{StaticResource LinksBorderStyle}">
- <StackPanel x:Name="LinksStackPanel"
- Style="{StaticResource LinksStackPanelStyle}">
- <HyperlinkButton x:Name="Link1"
- Style="{StaticResource LinkStyle}"
- NavigateUri="/Home"
- TargetName="ContentFrame" Content="{Binding Path=ApplicationStrings.HomePageTitle, Source={StaticResource ResourceWrapper}}"/>
- <Rectangle x:Name="Divider1"
- Style="{StaticResource DividerStyle}"/>
- <HyperlinkButton x:Name="Link2"
- Style="{StaticResource LinkStyle}"
- NavigateUri="/Employee"
- TargetName="ContentFrame"
- Content="Employee Details"/>
- <Rectangle x:Name="Divider2"
- Style="{StaticResource DividerStyle}"/>
- <HyperlinkButton x:Name="Link3"
- Style="{StaticResource LinkStyle}"
- NavigateUri="/About"
- TargetName="ContentFrame" Content="{Binding Path=ApplicationStrings.AboutPageTitle, Source={StaticResource ResourceWrapper}}"/>
- </StackPanel>
- </Border>
- </Grid>
- <Border x:Name="loginContainer"
- Style="{StaticResource LoginContainerStyle}">
- <!-- LoginStatus will be added here in code behind. This is required for the designer view to work -->
- </Border>
- </Grid>
- </Grid>
- </UserControl>
In our Employee Page let's add a DataGrid and customize it as we did previously.
- <navigation:Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d"
- xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
- xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" x:Class="RIASilverlight4Sample01.Views.Employee"
- Style="{StaticResource PageStyle}"
- Title="Employee Details"
- d:DesignHeight="480"
- d:DesignWidth="640">
- <Grid x:Name="LayoutRoot">
- <Grid.RowDefinitions>
- <RowDefinition Height="149*" />
- <RowDefinition Height="331*" />
- </Grid.RowDefinitions>
- <data:DataGrid x:Name="dgData"
- IsReadOnly="True"
- AutoGenerateColumns="False">
- <data:DataGrid.Columns>
- <data:DataGridTextColumn Binding="{Binding ID}"
- Header="ID"/>
- <data:DataGridTextColumn Binding="{Binding FirstName}"
- Header="First Name"/>
- <data:DataGridTextColumn Binding="{Binding LastName}"
- Header="Last Name"/>
- <data:DataGridTextColumn Binding="{Binding EmailID}"
- Header="Email ID" Width="*"/>
- <data:DataGridTextColumn Binding="{Binding Contact}"
- Header="Contact"/>
- </data:DataGrid.Columns>
- </data:DataGrid>
- </Grid>
- </navigation:Page>
Now we will see some good stuff that is available for easy design and
implementing data with controls associated.
After clicking on the above option we will see the following pane. If you do not
have any Domain Service Class then the list would be empty, for some reason if
you have one but still you don't get it; you need to rebuild the solution once
again. Remember, this pane is only displayed when a design view is opened (XAML
view or Design view).
As you see in the figure above, we have EmployeeDomainContext available to us
after we have added the Domain Service Class with respect to Entity Model.
Data Sources are used for binding controls with entity; so that the design time
can be reduced. As you see in the figure below, we have Employee as an entity
and it is preceded with the image of the DataGrid and the properties are
preceded with TextBox symbol.
That's right, the following controls would be associated once you drag and drop
into the design pane.
As soon as the DataGrid is added from Data Sources pane the Domain Context would
will be added in XAML behind.
- <riaControls:DomainDataSource
- AutoLoad="True"
- d:DesignData="{d:DesignInstance my:Employee, CreateList=true}"
- Height="0"
- LoadedData="employeeDomainDataSource_LoadedData"
- Name="employeeDomainDataSource"
- QueryName="GetEmployeesQuery"
- Width="0">
- <riaControls:DomainDataSource.DomainContext>
- <my1:EmployeeDomainContext />
- </riaControls:DomainDataSource.DomainContext>
- </riaControls:DomainDataSource>
The following is the structure of the DataGrid in XAML after it is added from
DataSource. Visual Studio will do automatic binding with each column type.
- <sdk:DataGrid AutoGenerateColumns="False"
- Height="200"
- HorizontalAlignment="Left"
- ItemsSource="{Binding ElementName=employeeDomainDataSource, Path=Data}"
- Margin="111,0,0,0"
- Name="employeeDataGrid"
- RowDetailsVisibilityMode="VisibleWhenSelected"
- VerticalAlignment="Top"
- Width="400">
- <sdk:DataGrid.Columns>
- <sdk:DataGridTextColumn x:Name="contactColumn"
- Binding="{Binding Path=Contact}"
- Header="Contact"
- Width="SizeToHeader" />
- <sdk:DataGridTextColumn x:Name="emailIDColumn"
- Binding="{Binding Path=EmailID}"
- Header="Email ID"
- Width="SizeToHeader" />
- <sdk:DataGridTextColumn x:Name="firstNameColumn"
- Binding="{Binding Path=FirstName}"
- Header="First Name"
- Width="SizeToHeader" />
- <sdk:DataGridTextColumn x:Name="iDColumn"
- Binding="{Binding Path=ID}"
- Header="ID"
- Width="SizeToHeader" />
- <sdk:DataGridTextColumn x:Name="lastNameColumn"
- Binding="{Binding Path=LastName}"
- Header="Last Name"
- Width="SizeToHeader" />
- </sdk:DataGrid.Columns>
- </sdk:DataGrid>
We have other options other than DataGrid, such as Details and Customize.
If the Details view is selected and dropped on to the designer surface, we will
get all the controls in a Grid Panel and the default control associated with it
is the TextBox control.
- <Grid DataContext="{Binding ElementName=employeeDomainDataSource, Path=Data}"
- HorizontalAlignment="Left"
- Margin="168,266,0,0"
- Name="grid1"
- VerticalAlignment="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto" />
- <ColumnDefinition Width="Auto" />
- </Grid.ColumnDefinitions>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <sdk:Label Content="Contact:"
- Grid.Column="0" Grid.Row="0"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1"
- Grid.Row="0" Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="contactTextBox"
- Text="{Binding Path=Contact, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue=''}" VerticalAlignment="Center" Width="120" />
- <sdk:Label Content="Email ID:"
- Grid.Column="0" Grid.Row="1"
- HorizontalAlignment="Left" Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1" Grid.Row="1"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="emailIDTextBox"
- Text="{Binding Path=EmailID, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue=''}" VerticalAlignment="Center"
- Width="120" />
- <sdk:Label Content="First Name:"
- Grid.Column="0" Grid.Row="2"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1" Grid.Row="2"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="firstNameTextBox"
- Text="{Binding Path=FirstName, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue=''}" VerticalAlignment="Center"
- Width="120" />
- <sdk:Label Content="ID:"
- Grid.Column="0" Grid.Row="3"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1" Grid.Row="3"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="iDTextBox"
- Text="{Binding Path=ID, Mode=TwoWay, NotifyOnValidationError=true}" VerticalAlignment="Center"
- Width="120" />
- <sdk:Label Content="Last Name:"
- Grid.Column="0" Grid.Row="4"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1" Grid.Row="4"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="lastNameTextBox"
- Text="{Binding Path=LastName, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue=''}" VerticalAlignment="Center"
- Width="120" />
- </Grid>
The above listing shows the implementation of Details view in XAML behind. You
can notice one thing isNote that the Column header name is separated with a
space; this is actually a great thing because in previous versions of Visual
Studio we had to customize everything on our own.
This is really helpful.
We can even change the control for each Column.
As you see in the figure above, we have the control options such as TextBox,
ComboBox, TextBlock and even we can Customize.
Let's have different controls for each column in the Details view and see how it
is helpful.
The following listing shows the Details view in XAML behind.
- <Grid DataContext="{Binding ElementName=employeeDomainDataSource, Path=Data}"
- HorizontalAlignment="Left"
- Margin="206,262,0,0"
- Name="grid1"
- VerticalAlignment="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto" />
- <ColumnDefinition Width="Auto" />
- </Grid.ColumnDefinitions>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- <RowDefinition Height="Auto" />
- </Grid.RowDefinitions>
- <sdk:Label Content="ID:"
- Grid.Column="0" Grid.Row="0"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBlock Grid.Column="1" Grid.Row="0"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="iDTextBlock"
- Text="{Binding Path=ID, Mode=TwoWay}"
- VerticalAlignment="Center" />
- <sdk:Label Content="First Name:"
- Grid.Column="0" Grid.Row="1"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1" Grid.Row="1"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="firstNameTextBox"
- Text="{Binding Path=FirstName, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue=''}" VerticalAlignment="Center"
- Width="120" />
- <sdk:Label Content="Last Name:"
- Grid.Column="0" Grid.Row="2"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1" Grid.Row="2"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="lastNameTextBox"
- Text="{Binding Path=LastName, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue=''}" VerticalAlignment="Center"
- Width="120" />
- <sdk:Label Content="Contact:"
- Grid.Column="0" Grid.Row="3"
- HorizontalAlignment="Left"
- Margin="3"
- VerticalAlignment="Center" />
- <TextBox Grid.Column="1" Grid.Row="3"
- Height="23"
- HorizontalAlignment="Left"
- Margin="3"
- Name="contactTextBox"
- Text="{Binding Path=Contact, Mode=TwoWay, NotifyOnValidationError=true, TargetNullValue=''}" VerticalAlignment="Center"
- Width="120" />
- </Grid>
Let's see how we can customize the Data Source.
The following dialog box would be displayed when we select Customize as
displayed in above figure.
In code behind of the view (Employee.xaml.cs) we can create an
EmployeeDomainContext instance by using the service Namespace and using that we
would perform all the operations.
We can use the following constructors based on our requirements:
By simply calling the method described in EmployeeDomainContext we can achieve
the respective operations.
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Documents;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Media.Animation;
- using System.Windows.Shapes;
- using System.Windows.Navigation;
- using BusinessApplication02.Web.Services;
-
- namespace BusinessApplication02.Views
- {
- public partial class Employee : Page
- {
- EmployeeDomainContext context = new EmployeeDomainContext();
-
- public Employee()
- {
- InitializeComponent();
- }
-
-
- protected override void OnNavigatedTo(NavigationEventArgs e)
- {
- LoadData();
- }
-
- private void LoadData()
- {
- employeeDataGrid.ItemsSource = context.Employees;
- context.Load(context.GetEmployeesQuery());
- }
-
- private void employeeDomainDataSource_LoadedData(object sender, System.Windows.Controls.LoadedDataEventArgs e)
- {
-
- if (e.HasError)
- {
- System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);
- e.MarkErrorAsHandled();
- }
- }
-
- }
- }
As you see in the above listing above, when we navigate to the Employee page the
data would be loaded.
As you see, in the preceding figure, all Employee data are is displayed.
Let's change the GetEmployees() query to get all the employee names in ascending
order of First Name.
Go back to the EmployeeDomainClass.cs in Service folder of the Web project, and
change the query for GetEmployees() method.
- [Query(IsDefault = true)]
- public IQueryable<Employee> GetEmployees()
- {
- return this.ObjectContext.Employees.OrderBy(m => m.FirstName);
- }