WCF RIA Services

auburnhairΛογισμικό & κατασκευή λογ/κού

13 Δεκ 2013 (πριν από 3 χρόνια και 7 μήνες)

115 εμφανίσεις





WCF RIA Services


As we saw in
C
hapter 1 adding a WCF service to our Silverlight application is easy

and lets us
easily bind the serialized objects from the Windows service to our Silverlight UX
. Now these
objects were obtained on the Windows server

middle tier

by Linq to Database(I will use this
shorthand for Linq to SQL and EF)

using queries H
owever I

encourag
e

the use of WCF RIA

services (formerly known as RIA Data Services)

as they make
the process

even more painless

.
WCF RIA will let us move th
ese queries to the client application as it
simplifies the traditional
N
-
tier application pattern by bringing together the ASP.NET
(Middle tier)

and Silverlight
(Client)

platforms.
The same objects that are hydrated in the middle tier by Linq are made availa
ble thru
queries in out Silverlight code without an explicit WCF service call.




Now

the latest

version of RIA services is called WCF
RIA to

reflect the use of WCF for
communications and for more details see
Saurabh Pant's blog

.
As a summary it brings the power
of WCF without the complexity as WCF
RIA creates a

custom
WCF
service host at runtime

in
me
mory

based on how you define your DomainService. It is clear
that Microsoft

wants just WCF
at the basis of its services.

Most importantly t
he
framework will

out
-
of
-
the
-
box handle the
Create, Read,Update,Delete(
CRUD
)
operations

thru the DomainService
base
class

that
has Linq

to SQL and Linq to Entities
providers. As

a result we can issue client side query such as
Customers.Where(c=>c.CustomerName.StartsWith(“a”) from our client code.
W
e have a subset
of Linq to EF query operations that we can issue from the

client just like our code was executing
in the middle tier. Now

all the APIs needed to create these DomainServices are public so other
providers such as NHibernate can be created

but his is beyond our context
.

For this coding exercise
we
use VS2010 to
cre
ate

our example using

a
Silverlight

Business
Application

Template
that

brings
us Navigation

capabilities
and

access to ASP.NET
Membership services with
out

writing any code.
These are key services that we get for free from
this template and I strongly
encourage its use

as these functions are needed by most applications.
The Navigation

framework , allows us to easily implement navigation between the newly
introduced Page controls in a Silverlight application, interacts with the Browser History journal
(B
ack button) and provides us with Uri mapping. T
he M
embership
services
provide the

user
login
,

registration and

profile thru ASP.NET

and are implemented
thru the

WCF Ria services
. So
you really do not need to write any code to see how these Ria Services work ,
e.g.

when the user
is registered the following generated code will persist the Registration object to the Database on
our server (Middle tier):




private

void

RegisterButton_Click(
object

sender,
RoutedEventArgs

e)


{


if

(
this
.registerForm.ValidateItem() &&
this
.registerForm.CommitEdit())


{



SubmitOperation

regOp =
this
.userRegistrationContext.SubmitChanges(
this
.RegistrationOperation_Completed,
null
);


this
.BindUIToOperation(regOp);


this
.parentWindow.AddPendingOperation(regOp);


}


}


So we start by opening a Silverlight Business Application and add the Entity Data Model class
for Customers using much the same process as we did in Chapter 1. Note I am changing to the
Entity Framework as I hope Microsoft has gotten it right in VS2010. It

should be noted that for
this example there is no difference which we chose. Now we will add a Domain service by
clicking on
Add New item
..
Web..Domain Service

class. Now you will see this dialog:

SubmitChanges will
sen
d all changed
objects in Registration
context to server



As a result a Domain service class is generated for us that will contain the CRUD operation that
our application will need without writing any code. For example a default read or Get is
generated that has method signature defined as follows:


public

IQueryable
<
Customer
> GetCustomers()


{




return

this
.ObjectContext.Customers;


}



Make sure these are checked

We can manually add any DomainService get method as long as it fulfills the following criteria:


(1) It returns
IEnumerable<T>
or
IQueryable<
T>
where
T
is an Entity type, and

(2) It takes 0 or more parameters (which are restricted to a set of supported types described later
in this section),

(3) Its method name is prefixed with “Get”, “Fetch”, “Find”, “Query”, “Retrieve”, or “Select”
Alternat
ively, it is adorned with the
QueryAttribute
.


As we can see
GetCustomers

satisfies these requirements since its return type is
IQueryable<Category> and its method name starts with Get. This is all we need to do in the
middle tier so let’s look at the Sil
verlight side. Now the code generator will produce a
DomainContext version of Customer Entity model that is derived from the Entity class and
contains much the same info as the Customer EDM plus validation attributes from the
Customer.Metadata.cs class:




[
DataContract
(Namespace=
"http://schemas.datacontract.org/2004/07/SimpleOutOfBox.Web"
)]


public

sealed

partial

class

Customer

:
Entity


{




private

string

_address;


private

string

_city;


private

string

_companyName;


… Removed entries




partial

void

OnCreated();


partial

void

OnAddressChanging(
string

value);


partial

void

OnAddressChanged();


partial

void

OnCityChanging(
string

value);



partial

void

OnCityChanged();


. . .



Removed entries



#endregion


///

<summary>


///

Default constructor.


///

</summary>


public

Customer()


{


this
.OnCreated();


}




[
DataMember
()]


[
StringLength
(
60
)]


public

string

Address


{


get


{


return

this
._address;


}


set


{



if

((
this
._address !=
value
))


{


this
.ValidateProperty(
"Address"
,
value
);


this
.OnAddressChanging(
value
);


thi
s
.RaiseDataMemberChanging(
"Address"
);


this
._address =
value
;


this
.RaiseDataMemberChanged(
"Address"
);

Validation

So entity can
participate in 2
-
way
data binding and change
management

An Entity is any class with a ID


this
.OnAddressChanged();


}


}


}




[
DataMember
()]


[
StringLength
(
15
)]


public

string

City


{


get


{


return

this
._city;


}


set


{


if

((
this
._city !=
value
))


{


this
.ValidateProperty(
"City"
,
value
);


this
.OnCityChanging(
value
);


this
.RaiseDataMemberChanging(
"City"
);


this
._city =
value
;


this
.RaiseDataMemberChanged(
"Cit
y"
);


this
.OnCityChanged();


}


}


}


….. Much more


Silverlight coding

These operations will allow us to write queries in the Silverlight client just as if we were on the
server (middle tier). As part of this process a DomainContext class will be generated and added
to Silverlight project. To see this file we have to click on

the Show All files button in the solution
explorer:




The file that is generated is of the form
FOO.g.cs

where FOO is the name of the server side
project. In my case my server project is
SimpleOutOfBox.Web so my file is
SimpleOutOfBox.Web.g.cs
. Note that it is placed in a folder call Models which is appropriate
as these are the models we can program against in our client code.This generated file will contain
all objects and methods that we

will need to issue our queries against the middle tier and persist
changes with little code.

By default the Business Template will create a Views folder where we find the Views used by
membership services such as Login plus About and Home Pages. We could

use one of these but
let’s add a Customer Page for our new View. So we start by adding a new Page class by right
clicking on Views and choosing
Add New Item.. Silverlight Page

and name the page
Show all files

CustomerData
. Now we need to modify the XAML in MainPage.XAM
L as it contains the
Navigation control. So open it up and add after the HyperLinkButton for About:



<
HyperlinkButton

x
:
Name
="Link3"

Content
="Customers"

Style
="{
StaticResource

LinkStyle
}"



NavigateUri
="/CustomerData"

TargetName
="
ContentFrame"/>


So we will now have a new Customers button that the user can click on :





So instead of writing a service like we did in chapter 1 we add the query to our client code in
Cust
omerData.xaml.cs. In later chapters we will move this code to a ViewModel but let’s take
small steps and add the following to the constructor:


public

partial

class

CustomerData

:
Page

{

CustomerDomainService1

_customerContext =
new

CustomerDomainService1
();


public

CustomerData()


{


InitializeComponent();


//by default we will load all the customers


_customerContext.Load(_customerContext.GetCustomersQuery());


CustomerGrid.ItemsSource = _customerContext.Customers;



}


Calling the Load method will result in the GetCustomers method and its query being executed on
the server (middle tier). This generated code just returns all

Customers and the server code looks
like:


public

IQueryable
<
Customer
> GetCustomers()


{




return

this
.ObjectContext.Customers;


}



Want the context to be accessible to
other methods (later)

Results in a select * from Customers

Bind customer entiti
es to our
DataGrid

As a result the Customer objects are serialized to our Silverlight client and are displayed in the
DataGrid of CustomerData.xaml. We needed to add the following XAML:


<
Grid.ColumnDefinitions
>


<
ColumnDefinition
></
ColumnDefinition
>


<
ColumnDefinition
></
ColumnDefinition
>


</
Grid.ColumnDefinitions
>


<
Grid.RowDefinitions
>


<
RowDefinition

Height
="55"></
RowDefinition
>


<
RowDefinition

Height
="425*"></
RowDe
finition
>


</
Grid.RowDefinitions
>


<
data
:
DataGrid

Grid.Row
="1"

Grid.Column
="0"

Grid.ColumnSpan
="2"

AutoGenerateColumns
="False"

Name
="CustomerGrid"

>


<
data
:
DataGrid.Columns
>


<
data
:
DataGridTextColumn

Header
="Company Name"

IsReadOnly
="True"

Binding
="{
Binding

CompanyName
}" />


<
data
:
DataGridTextColumn

Header
="Contact Name"


Binding
="{
Binding

ContactName
}" />


<
data
:
DataGridTextColumn

Header
="Address"

Binding
="{
Binding

Address
}" />



<
data
:
DataGridTextColumn

Header
="Postal Code"

Binding
="{
Binding

PostalCode
}"
/>


</
data
:
DataGrid.Columns
>


</
data
:
DataGrid
>


As a result we see a DataGrid that looks like:






Now I want to emphasize that we only had to write 3 lines of code and have our DataGrid
populated but there is a way to reduce this to zero by using the
Domain Data Source
. This will
even generate the DataGrid XAML and you ask why not use it. My answer is
it depends on your
needs for the application, if it is short
-
term then for all means use however if your needs are long
term then I would avoid using it, more on this later.





Client side query modification

Defines row and columns for layout

This is pretty powerful as we can modify our qu
ery client side, so will add a UX to allow a letter
to entered by the user to filter the contact name as follows:



<
StackPanel

Orientation
="Horizontal"

Grid.Row
="0"

Grid.Column
="1">


<
TextBlock

Text
="search by name: "

Height
="30"></
TextBlock
>


<
TextBox

Name
="LetterValue"

Width
="30"

Height
="25"></
TextBox
>


<
Button

Name
="LetterButton"

Click
="LetterButton_Click"

Content
="Submit"

Height
="30"></
Button
>


</
StackPanel
>



We now define the handler for the
LetterButton that will only display Customers whose
ContactName starts with the input letter

by a client side modification of the GetCustomersQuery
:


private

void

LetterButton_Click(
object

sender,
RoutedEventArgs

e)

{


var

query = _customerContext.GetCustomersQuery().Where(c =>
c.ContactName.StartsWith(LetterValue.Text));


this
._customerContext.Load(query);


Custome
rGrid.ItemsSource = _customerContext.Customers;




DataPager








Now client side we also have Orderby, Skip and Take available to use. Skip and Take are great
for doing paged operations against a relational source as we can write queries like which
will
skip 10 records and read the next 10. I have seen some organizations use 9 page Stored
Procedures to replicate this line of code.


_customerContext.GetCustomersQuery().Skip(10).Take(10)


However there is an even easier way and that is to use the Dat
aPager and to explore this add a
new page called CustPaged and modify MainPage.Xaml to add a new HyperLink that I called
Paged. So now our MainPage has Hyperlinks’ that look like:





So we are modify the default query
by filtering names on the first letter
so a select * where ContactNa
me
like ‘m%’ is generated



Then just drop a DataGrid and DataPager unto

the design surface for
CustPaged and add the
following XAML:


<
Grid

x
:
Name
="LayoutRoot">


<
data
:
DataGrid


AutoGenerateColumns
="False"

Name
="CustomerGrid" >


<
data
:
DataGrid.Columns
>


<
data
:
DataGridTextColumn

Header
="Company Name"

IsReadOnly
="True"

Binding
="{
Binding

CompanyName
}" />


<
data
:
DataGridTextColumn

Header
="Contact Name"


Binding
="{
Binding

ContactName
}" />


<
data
:
DataGridTextColumn

Header
="Address"

Binding
="{
Binding

Address
}" />


<
data
:
DataGridTextColumn

Header
="Postal Code"

Binding
="{
Binding

PostalCode
}"
/>


</
data
:
DataGrid.Columns
>


</
data
:
DataGrid
>


<
data
:
DataPager


x
:
Name
="pager"

PageSize
="10"

Margin
="0,0,0,148"></
data
:
DataPager
>


<
/
Grid
>






Now we modify the code in CustPaged.Xaml.Cs so it looks like:


public

partial

class

CustPaged

:
Page


{


CustomerDomainService1

_Context =
new

CustomerDomainService1
();


public

CustPaged()


{


InitializeComponent();


_Context.Load(_customerContext.GetCustomersQuery(), CustomersLoaded,
null
);




}

//completion method


pu
blic

void

CustomersLoaded(
LoadOperation

lo)


{


PagedCollectionView

custs =
new

PagedCollectionView
(_Context.Customers);


pager.Source = custs;


CustomerGrid.ItemsSource = custs;


}



Same as above
DataGrid

Defines our pager (note the name)

The completion
method fired when
the Customers have
been loaded

Makes the paging work so we no
longer need writing Skip and Take
queries (must include
System.Windows.Data)

So now our output page looks like:

Fi
g
ure 1





Domain

Data

S
ources


For completeness I will add one more page to our views and show how easy it is to use the
DomainDatasources to do what we did above, so add a new page called DDS to our view. Then
click on
Data ..Add New Data Source

and you should see:




Now drag the DataPager control unto the same surface and I placed it right under the DataGrid
and made the width the same as the DataGrid.



Page controls

Just Drag Customer to the Design
surface for useDataSrc.XAML


So our XAML looks like:


<
Grid

x
:
Name
="LayoutRoo
t">


<
riaControls
:
DomainDataSource

AutoLoad
="True"

Height
="0"

LoadedData
="customerDomainDataSource_LoadedData"

Name
="customerDomainDataSource"

QueryName
="GetCustomersQuery"

Width
="0">


<
riaControls
:
DomainDataSource.DomainContext
>


<
my
:
CustomerDomainService1

/>


</
riaControls
:
DomainDataSource.DomainContext
>

</
riaControls
:
DomainDataSource
>



<
data
:
DataGrid

AutoGenerateColumns
="False"

Height
="200"

Horizontal
Alignment
="Left"

ItemsSource
="{
Binding

ElementName
=customerDomainDataSource,

Path
=Data}"

Margin
="66,36,0,0"

Name
="customerDataGrid"

RowDetailsVisibilityMode
="VisibleWhenSelected"

VerticalAlignment
="Top"

Width
="400">


<
data
:
DataGrid.Columns
>



<
data
:
DataGridTextColumn

x
:
Name
="addressColumn"

Binding
="{
Binding

Path
=Address}"

Header
="Address"

Width
="SizeToHeader" />


<
data
:
DataGridTextColumn

x
:
Name
="cityColumn"

Binding
="{
Binding

Path
=City}"

Header
="City"

Width
="
SizeToHeader" />


<
data
:
DataGridTextColumn

x
:
Name
="companyNameColumn"

Binding
="{
Binding

Path
=CompanyName}"

Header
="Company Name"

Width
="SizeToHeader" />


<
data
:
DataGridTextColumn

x
:
Name
="contactNameColumn"

Binding
="{
Binding

Pa
th
=ContactName}"

Header
="Contact Name"

Width
="SizeToHeader" />


<
data
:
DataGridTextColumn

x
:
Name
="contactTitleColumn"

Binding
="{
Binding

Path
=ContactTitle}"

Header
="Contact Title"

Width
="SizeToHeader" />


<
data
:
DataGridTextColumn

x
:
Name
="countryColumn"

Binding
="{
Binding

Path
=Country}"

Header
="Country"

Width
="SizeToHeader" />


</
data
:
DataGrid.Columns
>


</
data
:
DataGrid
>



<
data
:
DataPager

Height
="26"

HorizontalAlignment
="Left"

Margin
="66,231,0,0"

Name
="dataPager1"


PageSize
="10"

Source
="{
Binding

Data
,
ElementName
=customerDomainDataSource}"

VerticalAlignment
="Top"

Width
="400" />


</
Grid
>





So we actually had to do some typing!


No
w run the application and open the DDS Page and you will see the same output as in Figure 1
and you say wonderful no code and I can ship my product! But hit the > in the Pager and you
will see the following exception:


We are binding the dataGrid to our
DomainDataSource

Pager uses the same DDS

Add the
PageSize and Source
to the XAML







This tells us that the
DomainDataSource will only work with sorted list so we must modify the
default query. Open the CustomerDomainService1 class in the Web project and modify the
default query method so the code looks like:


[
EnableClientAccess
()]


public

class

CustomerDomainService1

:
LinqToEntitiesDomainService
<
Entities
>


{





public

IQueryable
<
Customer
> GetCustomers()


{



return

this
.ObjectContext.Customers.OrderBy(c => c.ContactName);


}


Now I admit this is a great RAD way to work and will serve your needs in many cases.
However I will not use the pattern again as we are interested in exploring how one
builds application with longevity in mind. When this is our goal these RAD tools become
a liability but they certainly have their place. One must always weigh their use on a
project by project basis.



Added the OrderBy ContactName
lambda



WCF Basis

As I mentioned a while back WCF is the basis of WCF RIA as the name suggests and as such
one can c
onsume a DomainSource from a Winform as Brad Abrams
describes

. The URL to the
service is of the following format:

http://[hostname]/services/[namespacename]
-
[classname].svc

so in my example if you enter:



http://localhost:58900/services/SimpleOutOfBox
-
Web
-
CustomerDomainService1.svc


Then you will see the standard WCF proxy help screen:




It is then easy to add the
service to your Winform app by just adding a service reference. Brads
article then goes on to show how to query and save using the Domainservice. It is a cool article
and would like to point out that the technique of entering the service URL in the browser

should
be the first line of defense in the case of deployment issues.





We will now look at persisting changes and validation issues in the next chapter.







Note the . became a hyphen