CHAPTER 21 Data Access with WCF Data Services .AddQueryOption“playerId”, 1 .Expand“Weapon” .Expand“Player” .Expand“Creature” orderby kill.KillTime descending select kill; killsRepeater.D
Trang 1CHAPTER 21 Data Access with WCF Data Services
.AddQueryOption(“playerId”, 1)
.Expand(“Weapon”)
.Expand(“Player”)
.Expand(“Creature”)
orderby kill.KillTime descending
select kill;
killsRepeater.DataSource = q;
killsRepeater.DataBind();
}
The first thing we do in this code is create an instance of the service proxy, which is a
generated class that inherits from the System.Data.Services.Client.DataServiceContext
class This class exposes properties for each of the entity sets exposed by the service In our
case, we have players, weapons, and creatures and foreign key relationships that enable
them all to link to the kill log (A Kill is another entity exposed by the service.)
The custom service context client proxy takes as a constructor parameter a Uri that
contains the address of the service In a production scenario, you might use service
discovery, a service registry, or Web.config files to store the URLs of the services
The next thing we do is call CreateQuery<Kill>(“GetMyKills”) There’s quite a bit of
func-tionality packed into this one method call CreateQuery() doesn’t actually talk to the
service; it is merely asking us for the shape of the rows that will be returned from the query
(the Kill class) and for either the name of a service method to invoke or the name of an
entity set to query If we had simply supplied the string ”Kills” to the method, we would
be querying the Kills entity set directly and not invoking a custom method
Next you can see that the output of the CreateQuery() method is something queryable;
so we can chain method calls onto this method in a “fluent” style We use the Expand()
method to tell the WCF Data Service that for every Kill we return, also expand the
speci-fied relationship properties This enables us to bring over all the data related to an entity
in a single method call rather than making a bunch of chatty calls with high overhead
and latency In our case, we want the names of the weapons and creatures that were
killed
If we wanted, we could supply virtually any additional LINQ queries to the end of our
statement, but in this case we were happy with sorting the kills in descending order by
time so that the players see their most recent kill first
At this point, the service has still not been queried All we’re doing up to this point is
build-ing an in-memory expression tree The service will not actually be called until we, directly
or indirectly, invoke GetEnumerator() on the query object For the code in Listing 21.1,
we won’t make the actual network call until somewhere inside the DataBind() method of
the repeater control
Figure 21.2 shows what the MyKills.aspx page looks like when fed data from the WCF
Data Service
Trang 2The great part about using WCF Data Services is that, with a few minor exceptions, the
classes coming back from the service containing results are just specialized C# classes This
means if I want to know the name of the weapon used in a particular kill (and I’ve
pre-expanded the property chain), I can refer to kill.Weapon.Name Likewise, to find the name
of the creature killed, I can refer to kill.Creature.Name
This property chaining syntax still works with ASP.NET’s binding syntax and templating
system The code in Listing 21.2 shows MyKills.aspx; you can see how the
DataBinder.Eval() calls work perfectly well with nested properties returned on the entity
objects from the WCF Data Service
LISTING 21.2 MyKills.aspx
<form id=”form1” runat=”server”>
<div>
<asp:Repeater ID=”killsRepeater” runat=”server”>
<ItemTemplate>
[<%# DataBinder.Eval(Container.DataItem, “KillTime”) %>]
<a href=’Player.aspx?playerId=
<%# DataBinder.Eval(Container.DataItem, “PlayerID”) %>’>
<%# DataBinder.Eval(Container.DataItem, “Player.Name”) %></a>
fragged
<a href=’Create.aspx?creatureId=
<%# DataBinder.Eval(Container.DataItem, “CreatureID”) %>’>
<%# DataBinder.Eval(Container.DataItem, “Creature.Name”) %></a>
with a
<a href=’Weapon.aspx?weaponId=
<%# DataBinder.Eval(Container.DataItem, “WeaponID”) %>’>
<%# DataBinder.Eval(Container.DataItem, “Weapon.Name”) %></a>
(<%# DataBinder.Eval(Container.DataItem, “Notes”) %>)
<br />
</ItemTemplate>
</asp:Repeater>
</div>
</form>
FIGURE 21.2 Displaying “My Kills” with data from WCF Data Service
Trang 3The main thing to take away from this code listing is that we can use the dot notation in
the data binder’s Eval() method to access and render nested properties on the bound
item
The moral of this story is that whether you access a WCF Data Service because your
infra-structure prevents direct SQL access or because another team has decided to expose its
data that way, it’s incredibly simple to create a service reference and start working with
the data on that service Also keep in mind that WCF Data Services aren’t just simple
query services; you can insert, delete, and update as well, and data services can even be
configured to support optimistic concurrency checking, which comes in handy for
high-volume intranet applications
To add a new creature to the system and update a kill using the service reference, we can
just use the code shown in Listing 21.3 When querying data, the web service is not
contacted until code invokes GetEnumerator() on the query When modifying data, the
change requests are not sent to the service until you invoke the SaveChanges() method
on the service context
LISTING 21.3 Adding and Updating Entities with a WCF Data Service Proxy
ZombieKillaService.ZombieKillaContainer svc = new
ZombieKillaService.ZombieKillaContainer(
new Uri(“http://localhost:5000/ZombieKilla.svc”));
svc.MergeOption =
System.Data.Services.Client.MergeOption.PreserveChanges;
ZombieKillaService.Creature creature = new ZombieKillaService.Creature()
{
Armor = 215,
Hitpoints = 512,
Name = “Fantastically Big Zombie”
};
svc.AddToCreatures(creature);
svc.SaveChanges();
Response.Write(“Created a new creature with ID of “ +
creature.ID.ToString());
ZombieKillaService.Player player = svc.Players.FirstOrDefault();
ZombieKillaService.Weapon weapon = svc.Weapons.FirstOrDefault();
ZombieKillaService.Kill kill = new ZombieKillaService.Kill() {
KillTime = DateTime.Now,
PlayerID = player.ID,
CreatureID = creature.ID,
WeaponID = weapon.ID,
Notes = “Awesome shot!”
};
CHAPTER 21 Data Access with WCF Data Services
Trang 4From this listing, you can see that the service reference proxy gives us classes that we can
instantiate to represent any of the entities hosted by the data service In addition, we also
get methods such as AddToCreatures() that queue up all the work necessary to add an
entity to an entity set on the server We can also query for a specific item, make changes
to it (or items related to it), and save those changes as well You can do a few hundred
other things with the data service proxy, but this is an ASP.NET book so we’re going to
stick to the basics for this chapter
Using Data Services with a Data Context
In the previous section, we built a sample application that displays the kill log from a
fictional video game called Zombie Killa This kill log is exposed as a WCF Data Service,
and we communicated with that service through an automatically generated proxy class
In this section, we take a look at how we can talk to the WCF Data Service without using
any generated code or classes by using the DataServiceContext class directly
As with the generated proxy, we create an instance of the proxy by providing it with the
URL of the WCF Data Service After that, we can create our own queries (and make
changes, inserts, and deletes) by invoking various methods on the context In the code in
Listing 21.4, we call CreateQuery() and pass it the name of the entity set we want to
query: Players
LISTING 21.4 Querying a Data Service Manually with a DataServiceContext
// NOTE: Not using auto-generated client proxy, using generic client with POCO
View Models!
DataServiceContext ctx = new DataServiceContext(
new Uri(“http://localhost:5000/ZombieKilla.svc”));
ctx.IgnoreMissingProperties = true;
var q = from ViewModels.Player player in
ctx.CreateQuery<ViewModels.Player>(“Players”)
.Expand(“Kills”).Expand(“Kills/Weapon”).Expand(“Kills/Creature”)
where player.ID == Int32.Parse(Request[“playerId”])
select player;
ViewModels.Player p = q.FirstOrDefault();
StringBuilder sb = new StringBuilder();
sb.Append(p.Name + “<br/>”);
foreach (ViewModels.Kill kill in p.Kills)
{ sb.AppendFormat(“ Killed {1} with a {2} ({3})<br/>”,
kill.KillTime, kill.Creature.Name, kill.Weapon.Name, kill.Notes);
}
placeHolder.Controls.Add(new LiteralControl(sb.ToString()));
Trang 5The calls to Expand() differ slightly from the previous samples The beauty of WCF Data
Service relationship expansion is that we can expand multiple levels So, not only can I
ask for a player, but I can also ask for that player’s kills, and, for each of those kills I can
obtain the weapon and creature used for that kill All this comes over the wire as a single
response; I don’t need to make multiple calls to get all the referential and lookup data.
Finally, the code calls FirstOrDefault() to actually send the query over the wire The
DataServiceContext class is so powerful that it knows how to convert the information in
the payload from the service into the data type required by the client When we create the
query, we pass it the type ViewModels.Player This is a Plain Old CLR Object (POCO) class
that we created that is little more than a code-based schema that tells the service how to
shape the response Listing 21.5 shows the code for the ViewModels.Player class and the
ViewModels.Kill class
LISTING 21.5 The ViewModels.Player Class and ViewModels.Kill Class
public class Player
{
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Kill> Kills { get; set; }
}
public class Kill
{
public int ID { get; set; }
public int PlayerID { get; set; }
public int CreatureID { get; set; }
public DateTime KillTime { get; set; }
public string Notes { get; set; }
public Player Player { get; set; }
public Creature Creature { get; set; }
public Weapon Weapon { get; set; }
}
If you find that you create or refresh a client-side proxy every time the entity definitions
change on the service, creating your own view models might be a better idea This gives
you the added benefit of placing all these view models in a shared assembly that can be
used by multiple web applications or multiple service clients
Another advantage of the view model approach using the raw data service context is that
you can decorate these view models with any attributes you want, including those that
might be compatible with various client-side validation frameworks or other custom uses
for your ASP.NET or ASP.NET MVC applications With an auto-generated proxy, any changes
you make or decorations you add can be wiped out every time you refresh the reference
CHAPTER 21 Data Access with WCF Data Services
Trang 6The real power here is that you can choose whether you want to access a raw data service
context or one created by adding a service reference Which option you choose and when
should be based on how frequently your service entities change and how much extra
functionality you want to put into the client-side objects
Summary
In this chapter you learned a little bit about WCF Data Services and the OData protocol
There are countless strategies for getting data in and out of your ASP.NET application
ranging from traditional raw SQL access to service-based data access such as WCF Data
Services
WCF Data Services provide developers with a platform-independent way of querying and
manipulating data without needing to know the specifics of the underlying schema or
even the underlying data store
Whether you choose to access a WCF Data Service using a raw data context, a generated
data context wrapper, or even through a raw HTTP request, these services provide
incredi-ble value and can dramatically increase productivity, code reuse, and even scalability of
distributed applications
For more information on WCF Data Services, check out MSDN’s page for them at
http://msdn.microsoft.com/en-us/data/bb931106.aspx
Trang 7This page intentionally left blank
Trang 8Using the Navigation
Controls
Understanding Site Maps Using the SiteMapPath Control Using the Menu Control
Using the TreeView Control Building a SQL Hierarchical Data Source Control Summary
In this chapter, you learn how to use the SiteMapPath,
Menu, and TreeView controls You can use all three of these
controls to enable users to navigate your website
Furthermore, you can use the Menu and TreeView controls
independently of website navigation You can bind these
two controls to other data sources such as XML documents
or database data
This chapter explores different methods of binding the Menu
and TreeView controls to different data sources and shows
you how to format the rendered output of both of these
controls You also learn how to take advantage of Ajax
when working with the TreeView control
In the final section, we build a SqlHierarchicalDataSource
control, which enables you to bind controls such as the
TreeView and Menu controls to hierarchical database data
Understanding Site Maps
Before you learn about the navigation controls, you first
need to understand Site Maps All three navigation controls
use Site Maps to retrieve navigation information A Site
Map enables you to represent the navigational relationships
between the pages in an application, independent of the
actual physical relationship between pages as stored in the
file system
Site Maps use the provider model In the next chapter, you
learn how to create custom Site Map providers to store Site
Maps in custom data stores such as database tables The
examples in this chapter take advantage of the default XML
Site Map provider, which enables you to store a Site Map in
an XML file
Trang 9CHAPTER 22 Using the Navigation Controls
By default, the navigation controls assume the existence of an XML file named
Web.sitemap, which is located in the root of your application
For example, Listing 22.1 contains a simple Site Map
LISTING 22.1 Web.sitemap
<?xml version=”1.0” encoding=”utf-8” ?>
<siteMap xmlns=”http://schemas.microsoft.com/AspNet/SiteMap-File-1.0” >
<siteMapNode
url=”~/Default.aspx”
title=”Home”
description=”The home page of the Website”>
<! Product Nodes >
<siteMapNode
title=”Products”
description=”Website products”>
<siteMapNode
url=”~/Products/FirstProduct.aspx”
title=”First Product”
description=”The first product” />
<siteMapNode
url=”~/Products/SecondProduct.aspx”
title=”Second Product”
description=”The second product” />
</siteMapNode>
<! Services Nodes >
<siteMapNode
title=”Services”
description=”Website services”>
<siteMapNode
url=”~/Service/FirstService.aspx”
title=”First Service”
description=”The first service” />
<siteMapNode
url=”~/Products/SecondService.aspx”
title=”Second Service”
description=”The second service” />
</siteMapNode>
</siteMapNode>
Trang 10A Site Map file contains <siteMapNode> elements There can be only one top-level node
In the case of Listing 22.1, the top-level node represents the website’s home page
A <siteMapNode> supports three main attributes:
title—A brief title that you want to associate with a node
description—A longer description that you want to associate with a node
url—A URL that points to a page or other resource
The url attribute is not required Both the Products and Services nodes do not include a
url attribute because these nodes do not represent pages to which you can navigate
Each <siteMapNode> can contain any number of child nodes In Listing 22.1, both the
Products and Services nodes include two child nodes
The Site Map in Listing 22.1 represents a website that has the following folder and page
structure:
Default.aspx
Products
FirstProduct.aspx
SecondProduct.aspx
Services
FirstService.aspx
SecondService.aspx
The navigational structure of a website as represented by a Site Map is not required to
have any relationship to the navigational structure of a website as stored in the file
system You can create any relationship between the nodes in a Site Map that you want
The SiteMapPath control enables you to navigate easily to any parent page of the current
page It displays the standard breadcrumb trail that you see on many popular websites (see
Figure 22.1)
You can use the SiteMapPath control simply by declaring the control in a page The
control automatically uses the Web.sitemap file located in the root of your application For
example, the page in Listing 22.2 includes the SiteMapPath control (see Figure 22.2)