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;
// The MetadataTypeAttribute identifies EmployeeMetadata as the class
// that carries additional metadata for
the Employee class.
[MetadataTypeAttribute(typeof(Employee.EmployeeMetadata))]
public partial
class Employee
{
// This class allows you to attach custom attributes
to properties
// of the Employee class.
//
// For example, the following marks
the Xyz property as a
// required field and specifies the
format for valid values:
// [Required]
// [RegularExpression("[A-Z][A-Za-z0-9]*")]
// [StringLength(32)]
// public string Xyz;
internal
sealed class
EmployeeMetadata
{
// Metadata classes are not meant to be
instantiated.
private EmployeeMetadata()
{
}
public string
Contact;
public string
EmailID;
public string
FirstName;
public long
ID;
public string
LastName;
}
}
}
Listing
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;
// Implements application logic using the EmployeeDBEntities context.
// TODO: Add your application logic to
these methods or in additional methods.
// TODO: Wire up authentication (Windows/ASP.NET
Forms) and uncomment the following to disable anonymous access
// Also consider adding roles to restrict
access as appropriate.
// [RequiresAuthentication]
[EnableClientAccess()]
public class
EmployeeDomainService :
LinqToEntitiesDomainService<EmployeeDBEntities>
{
// TODO: Consider
// 1. Adding parameters to this method
and constraining returned results, and/or
// 2. Adding
query methods taking different parameters.
[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);
}
}
}
Listing
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);
}
Listing
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:
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);
}
}
}
Listing
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));
}
}
Listing
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);
}
Listing
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://*/Models.EmployeeModel.csdl|res://*/Models.EmployeeModel.ssdl|res://*/Models.EmployeeModel.msl;
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>
Listing
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>
Listing
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>
Listing
Now we will see some good stuff that is available for easy design and
implementing data with controls associated.
Listing
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>
Listing
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>
Listing
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>
Listing
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>
Listing
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();
}
// Executes when the user navigates to this page.
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();
}
}
}
}
Listing
As you see in the above listing above, when we navigate to the Employee page the
data would beis 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);
}
Listing