Tải bản đầy đủ (.pdf) (112 trang)

Mastering Microsoft Visual Basic 2010 phần 10 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (907.52 KB, 112 trang )

BUILDING AND USING WCF SERVICES 913
of the service’s methods; instead of using the collections as its data source, the service will
contact a database. In between sessions, you can persist t he collections to an XML file, as dis-
cussed in Chapter 13, ‘‘XML in Modern Programming.’’
Start a fresh instance of Visual Studio and select File  New  New Project. Click WCF in
the Installed Templates pane, and in the pane with the matching project types, select WCF Ser-
vice Library. Name the new project WCFProducts and click OK to create the project. The new
project that Visual Studio will create for you contains two files: the IService1.vb class, which
is an interface that contains the signatures of the service methods, and the Service1.vb class,
which contains the implementation of the methods. The methods are some extremely trivial
sample methods and you can delete them right away.
The first step is the design of a class that represents the entities our service knows about.
Obviously, we need a Product and a Category class and I have nested the Category class within
the Product class. Both classes contain just a few properties to identify products and categories.
Add the Product class to the WCF project and insert in it the code of Listing 21.4.
Listing 21.4: The Product.vb class
<DataContract()>
Public Class Product
<DataMember()> Public ProductID As Integer
<DataMember()> Public ProductName As String
<DataMember()> Public ProductPrice As Decimal
<DataMember()> Public ProductCategoryID As Integer
Public Overrides Function ToString() As String
Return ProductName & " (" & ProductID & ")"
End Function
<DataContract()> Public Class Category
<DataMember()> Public CategoryID As Integer
<DataMember()> Public CategoryName As String
Public Overrides Function ToString() As String
Return CategoryName & " (" & CategoryID.ToString & ")"
End Function


End Class
End Class
This is a very simple class that describes products and categories, if you ignore the deco-
rations of the various members. The entire class is marked with the DataContract attribute,
which tells the compiler that the following class contains the service’s contract: the entities our
service knows about. The clients of the service will communicate with our class by submit-
ting objects from the Product a nd Product.Category classes, and the service will return data
to the client application using instances of these two classes. The DataMember attribute tells
the compiler that the corresponding property is a member of the contract and that it should be
exposed to the clients. If you have properties that you want to use in your code but not expose
to clients, just don’t mark them with the DataMember attribute.
914 CHAPTER 21 BUILDING AND USING WEB SERVICES
The next step is the definition of the methods that the clients will use to contact our ser-
vice. The methods must be defined in an interface class, which contains the method definitions
but not their actual implementation. This interface class contains the service’s metadata — the
information required by a client to figure out the service’s capabilities. Add the IProduct class
to the project and insert the statements shown in Listing 21.5 in it (the IProduct.vb file).
Listing 21.5: Describing the Product class with an interface
Imports System.ServiceModel
<ServiceContract()>
Public Interface IProduct
<OperationContract()> Function
GetAllCategories() As List(Of Product.Category)
<OperationContract()> Function
GetAllProducts() As List(Of Product)
<OperationContract()> Function
AddProduct(ByVal prod As Product) As Product
<OperationContract()> Function
RemoveProduct(ByVal ID As Integer) As Boolean
<OperationContract()> Function

GetCategoryProducts(ByVal ID As Integer)
As List(Of Product)
End Interface
This is another trivial class, except that it’s marked with the ServiceContract attribute,
which tells the compiler that the class contains the service’s operations (as opposed to the ser-
vice’s data structure, which was defined in the Product class). The methods are also marked
with the OperationContract attribute, which makes them available to clients. Without this
attribute, the procedures would be internal methods and clients wouldn’t be able to see them.
If the service exchanges simple data types with the client, this interface would be a dequate.
However, practical services are not limited to simple data types like integers and strings. They
communicate with the client using business objects, which are based on custom classes. These
objects represent customers, products, invoices, and the like. If you want to make use of custom
objects, you must declare them in a separate class like the Product class shown earlier.
Finally, you must add yet another class to the project — this time a class that contains the
actual code and implements the service. This is the ProductService class in the sample project,
and its code is shown in Listing 21.6. The ProductService class implements the IProduct inter-
face, taking into consideration the classes defined in the Product class.
Listing 21.6: The implementation of the ProductService class
<ServiceBehavior()> Public Class ProductService :
Implements IProduct
Shared _products As New List(Of Product)
Shared _categories As New List(Of Product.Category)
BUILDING AND USING WCF SERVICES 915
Public Function AddProduct(ByVal prod As Product) As Product
Implements IProduct.AddProduct
‘ grab the next ID in _products list
prod.ProductID =
(From p In _products
Select p.ProductID
Order By ProductID Descending).

FirstOrDefault + 1
‘ If category field is not set to a valid category, ignore it
If (From c In _categories
Where c.CategoryID = prod.ProductCategoryID).Count = 0 Then
prod.ProductCategoryID = Nothing
End If
products.Add(prod)
Return prod
End Function
Public Function GetAllCategories() As
System.Collections.Generic.List(
Of Product.Category)
Implements IProduct.GetAllCategories
Return _categories
End Function
Public Function GetAllProducts() As
System.Collections.Generic.List(Of Product)
Implements IProduct.GetAllProducts
Return _products
End Function
Public Function RemoveProduct(ByVal ID As Integer)
As Boolean Implements IProduct.RemoveProduct
products.Remove(_products.Find(Function(p) p.ProductID = ID))
End Function
Protected Overrides Sub Finalize()
MyBase.Finalize()
End Sub
Public Function GetCategoryProduct(
ByVal categoryID As Integer)
As List(Of Product)

Implements IProduct.GetCategoryProducts
Return (From p In _products
Where p.ProductCategoryID = categoryID).ToList
End Function
Public Sub New()
916 CHAPTER 21 BUILDING AND USING WEB SERVICES
_categories.Add(New Product.Category With
{.CategoryID = 101, .CategoryName = "Electronics"})
_categories.Add(New Product.Category With
{.CategoryID = 102, .CategoryName = "Cameras"})
_categories.Add(New Product.Category With
{.CategoryID = 103, .CategoryName = "Software"})
End Sub
End Class
The data are stored in the _products and _categories collections, which are private to the
class, and no client can manipulate these collections directly. This is the essence of the service:
It allows clients to view and manipulate the data through a well-defined interface, and the ser-
vice itself is in charge of maintaining the integrity of the data. Since we don’t have a database
at the back end, we’re also responsible for maintaining the IDs of the various entities. Every
time a new product is added, the code retrieves the largest product ID from the _products
collection, adds 1 to it, and forms the ID of the new p roduct. The same is true for the IDs of
the categories. Notice also that every time the service is initialized, it adds three rows to the
_categories table. It goes without saying that you can change the implementation of the ser-
vice so that it interacts directly with the Northwind database instead of custom collections. To
do so, you will change the implementation of the ProductService class without having to touch
the other two classes. As a consequence, client applications will continue using your service,
but with the new implementation of the service, they will be seeing the data of the Northwind
database.
The two collections must be declared with the Shared keyword so that a ll instances of the
service will see the same data. Had we declared the two collections with a Dim statement, a

new set of collections would be created for each instance of the class invoked by a client. In
other words, every client application would see its own data source.
Your WCF service is ready to service clients. To test it, press F5 and you will see an icon
in the lower-right corner of your screen informing you that the WcfSvcHost utility has started.
This utility, which comes with Visual Studio, hosts the service and makes it available to clients
at a specific IP address and port. Since WcfSvcHost is meant for testing purposes, only appli-
cations running on the same machine can connect to it. Once the service has been debugged, it
can be deployed as an IIS application.
A few moments later you will see another window, the WCF Test Client utility, which
allows you to test your new service without writing a client application. The first time you
run the project you’ll see an error message to the effect that the service doesn’t expose any
metadata. This happened because you haven’t configured your application yet. Right-click
the App.config file in the Solution Explorer, and from the shortcut menu, select Edit WCF
Configuration. The Configuration Editor window appears, as shown in Figure 21.11.
Configuring WCF Services
A WCF service is defined by three parameters: an address where it can be reached (the end-
point), a binding protocol, and a contract. All three parameters are configurable, and you need
not edit the service code to redeploy it or to support additional bindings. You only need to edit
the configuration file.
The address is an IP address or URL that specifies where the service is located. It’s the
address of the machine on which the service is running and, consequently, the address to
BUILDING AND USING WCF SERVICES 917
which clients must connect to make requests. The binding determines how the clients will
talk to a service and WCF supports multiple bindings. The contract, finally, specifies what the
service does; in other words, the methods it provides. The contract is the service interface,
much like the classes and methods of a namespace. If you want to totally abstract a WCF
service, think of it as a namespace that’s being hosted on a remote computer. Just as you can
call a method in a namespace, you can call a method of a WCF service . As you saw in the first
example of this chapter, you can access a remote service from within your project b y adding
a reference to the service. With that reference in place, Visual Studio will take care of the

housekeeping needed to connect to the remote service (what’s also known as the plumbing).
Figure 21.11
The Configuration Edi-
tor’s window allows you
to configure the parame-
ters of a WCF service.
The binding tells the client how the messages will be transmitted between the client and the
service. Web services use HTTP to exchange data with the clients. This, however, is not perfect
for all cases. HTTP is a universal protocol, and it’s been implemented on every ope rating sys-
tem, but it’s t he most generic protocol. Windows, as well as other operating systems, support
messaging: a mechanism for reliably transmitting information between two computers, even if
one of them is offline. When the receiving computer is connected, the message will be delivered
and processed. This mechanism, which relies on Microsoft’s Message Queues, is the most reli-
able mechanism for exchanging data between two computers, but this type of communication
isn’t synchronous.
There are situations where the WCF service and its client are deployed on the same local
area network. For these cases, there are protocols, such as Named Pipes, that perform much
faster than HTTP. I can’t discuss the merits of all available bindings in this chapter. This is
an advanced topic, and the goal of this chapter is to introduce you to a technique for writing
applications as services. The basic techniques are within the reach of the average VB devel-
oper, and I will limit the discussion to the basic techniques for building WCF services and
918 CHAPTER 21 BUILDING AND USING WEB SERVICES
deploying them on a web server. WCF allows you to build a service following the steps dis-
cussed so far and then configure them. The configuration involves the specification of the end-
point (the service’s address) and the binding protocol. You can even implement multiple bind-
ings on the same service so that different clients can contact the same service using different
protocols.
To configure the sample WCF service, you must first change the name of the service.
Although you changed the default name of the service, the configuration file still remembers
the original name, Service1, which was the name of the sample class that was generated auto-

matically and that we removed from the project. Click the WCFProducts.Se rvice1 item in the
left pane of the Configuration Editor, which was shown in Figure 21.11, and then select the
Name property in the right pane. Click the button with the ellipsis next to the service’s name
and the Service Type Browser dialog box will open, as shown in Figure 21.12. This dialog box
is similar to the Open dialog box, which allows you to select a file. Navigate to the project’s
Bin/Debug folder and select the WCFProducts.dll file. Click open, or double-click the file’s
name to see the names of all services implemented in the DLL. You will see a single service
name, the WCFProducts.ProductService name. Select it and the close the dialog box by
clicking the OK button.
Figure 21.12
Configuring the name of
the WCF service
You will notice that the new service has two predefined endpoints. Click the first one
and the endpoint’s properties will be displayed on the editor’s right pane. The first end-
point uses the wsHttpBinding binding and implements the contract WCFProducts.Service1
service. There’s no Service1 service, so you must change the name of the service with the
same process described earlier. Locate the project’s DLL and set the endpoint’s contract
to WCFProducts.IProduct interface. While configuring the first endpoint, set its name to
HTTPBinding.
Now select the second endpoint and you’ll see that it implements the mexHttpBinding, as
shown in Figure 21.13. This binding provides the service’s metadata and you need not change
its settings. Just set its name to MEXBinding, so that it won’t be displayed as (Empty Name).
BUILDING AND USING WCF SERVICES 919
Figure 21.13
Configuring an endpoint
of a WCF service
Save the configuration with the Save command from the File menu and close the Config-
uration Editor. Now you’re ready to test the service. Press F5 again and this time the WCF
Test Client window appears, as shown in Figure 21.14. The WCF Test Utility window con-
sists of two panes: on the left pane you see all the methods of the service (you must expand

the service’s interface to see the names of the methods), and on the right pane you can call the
selected method and see the results. To see the categories shown in Figure 21.14, for example,
I double-clicked the GetAllCategories item in the left pane and then I clicked the Invoke but-
ton. The utility called the GetAllCategories method and displayed the results in the lower
part of the pane.
You can use the WCF Test Client utility to test the methods of your new service, but even-
tually you must host your service to a web server, or an application, and call it from another
Windows or web client.
You can also test the methods that submit data to the service. If you double-click the
AddProduct method name, a new tab will open in the right pane, as shown in Figure 21.15, and
you’ll be prompted to enter values for the method parameters. Specify the parameter values
(you don’t have to provide a value for the ProductID parameter; this value is assigned auto-
matically by the service) and then click the Invoke button. The utility will call the AddProduct
method, and if executed successfully, it will display the new product in the lower half of the
tab, as shown in the figure.
Note that the new product’s ID is included in the result of the method because the method
returns an object of the Product type that represents the newly inserted row.
Implementing a web or WCF service is no different from implementing a class that exposes
certain functionality through a set of methods and communicates with another application by
exchanging specific types. The only difference between a class you’d use in a Windows applica-
tion and a service is that the members of the service are marked with special attributes. More-
over, when it comes to WCF services, you must also configure them with the Configuration
Editor.
920 CHAPTER 21 BUILDING AND USING WEB SERVICES
Figure 21.14
Testing the methods of
the new WCF service
in the WCF Test Client
utility
Figure 21.15

Submitting a new
product to the Product-
Service service through
the WCF Test Client
ADO.NET Data Services
Before ending this chapter, I’d like t o show you briefly how to create services that expose an
entire database. This type of service comes as a special project component of Visual Studio, the
ADO.NET Data Services component. An ADO.NET Data service is a web service that exposes
an entire database, or part of it, as a web service. What’s special about this component is that
it’s generated automatically for you; all you have to do is specify the tables you want to expose
ADO.NET DATA SERVICES 921
and a wizard will generate the service for you. The data source can be a database, an Entity
Data Model (EDM), or a collection of custom objects. There’s nothing new to learn and you can
create and use data services immediately, with the exception of some t echniques for securing
your data. The data service will expose methods to both query and update the data source, but
you obviously don’t want to give access to your database to anyone on the Web.
Let’s start the exploration of data services by building a new project, the DataService project;
you can download it from this URL: www.sybex.com/go/masteringvb2010. Create a new pro-
ject of the ASP.NET Web Site type, since your data will be exposed over HTTP. As you will
see, you have no need for a website per se, just a web service that will expose the data of a
specific database (or part of it). As soon as the project is created, delete the ASPX page that
Visual Studio adds by default to any project of this type.
First, you must create a data source. For the purpose of this example, you’ll expose data
from the Northwind database, and to do so, you’ll create an ADO.NET Entity Data Model
by adding a new component of this type to the project. Keep the default name, which is
Model1.edmx. When the wizard starts, select all of the tables in the database, as you learned
in Chapter 19. For this example, I’ve included all 12 tables of the Northwind database. I just
dropped them on the EDM design surface and Visual Studio generated the Model1.edmx data
model.
Now that you have the data source, you can add an ADO.NET D ata Service component to

your project to expose the selected tables through a web service. Right-click the project name
and select Add New Item. When the Add New Item dialog box opens, select the ADO.NET
Data Service. Name the new service NWWebDataService. Visual Studio will create the
NWWebDataService.svc file for you and will open the new data service’s code window. You
will see that the new class contains just a few lines of code:
Imports System.Data.Services
Imports System.Linq
Imports System.ServiceModel.Web
Public Class NWWebDataService
‘ TODO: replace [[class name]] with your data class name
Inherits DataService(Of [[class name]])
‘ This method is called only once to initialize service-wide policies.
Public Shared Sub InitializeService(
ByVal config As IDataServiceConfiguration)
‘ TODO: set rules to indicate which entity sets
‘ and service operations are visible, updatable, etc.
‘ Examples:
‘ config.SetEntitySetAccessRule("MyEntityset", EntitySetRights.AllRead)
‘ config.SetServiceOperationAccessRule(
‘ "MyServiceOperation", ServiceOperationRights.All)
End Sub
End Class
The NWWebDataService inherits from another class, whose name you must supply
by replacing class name in the code line that reads: Inherits DataService(Of [[class
name]]). The class it derives from should be NorthwindEntities, which is the name of the
922 CHAPTER 21 BUILDING AND USING WEB SERVICES
Data Entity class you created as the project’s data source. Technically, you didn’t specify the
NorthwindEntities name, but Visual Studio created this class and named it after the database.
The statements that are commented out specify the data you want to expose through your
data service. By default, the data service won’t expose any data unless you tell it to do so.

Replace the last two statements of the InitializeService routine with the following:
config.SetEntitySetAccessRule("*", EntitySetRights.All)
This statement instructs the service to expose all resources. Obviously, this is the last thing
you want to do with a real service that will be accessed from outside your test machine,
and I’ll come back to the issue o f security, but for now let’s grant unconditional access to all
tables through your service. For the purpose of this chapter, the service will be hosted by the
ASP.NET Development Server, which can be accessed from the same machine only.
You’re probably thinking it’s time to add some real code, or look at a few thousand lines of
code generated by the wizard. This isn’t the case; your data service is complete. Right-click the
NWWebDataService component name, and from the context menu, select View In Browser. A
few seconds later you will see a new Internet Explorer window displaying a list of all tables in
the database, as shown in Figure 21.16.
Figure 21.16
The service exposes the
names of all tables in its
data source.
Each table, which is an EntitySet in the data source, is exposed as a collection. By appending
the entity name to the base URL of t he service, you can view the rows in the corresponding
tables. Change the URL in the browser address bar to any of the following to see the products,
categories, and customers in the Northwind database:
http://localhost:51000/NWWebDataService.svc/Products
ADO.NET DATA SERVICES 923
http://localhost:51000/NWWebDataService.svc/Categories
http://localhost:51000/NWWebDataService.svc/Customers
As you w ill recall from our discussion of the Entity Framework in Chapter 17, ‘‘Using the
Entity Data Model,’’ the Products table is translated into the Products EntitySet, which is made
up of Product entities. The port will be different on your machine, but you will see it on your
browser’s address bar as soon as you open the service in the browser. This port number will
be different every time you start the application, so it wouldn’t be a bad idea to set a specific
port for the service. Select the DataService project in the Solution Explorer, open the Project

menu, and select DataService Properties. On the Project Properties window that opens, select
the Web tab, which is shown in Figure 21.17. On this tab, you can set the start action (what
happens when you start the project) as well as the server that will host your server. For now,
select the Specific Port option and set its value to the number of an unused port (I’ve used the
port number 51000). If you decide to make the service public, don’t forget to limit the access
to the service (you don’t want people changing your data at will). You’ll see shortly how you
can restrict access to specific tables and even how to intercept certain operators, like insertions,
modifications, and deletions, and determine whether to allow or block them from within
your code.
Figure 21.17
Configuring the server
that will host the data
service
Figure 21.18 shows how the first customer, the ubiquitous ALFKI customer, is displayed.
The output of the service is just XML, and you can write a client application to access the ser-
vice and process the selected rows with LINQ. Notice the links to the related tables. Each cus-
tomer has two related tables with the following relative URLs:
Orders:
Customers(’ALFKI’)/Orders
CustomerDemographics:
Customers(’ALFKI’)/CustomerDemographics
924 CHAPTER 21 BUILDING AND USING WEB SERVICES
Figure 21.18
Requesting a specific
customer through a URL
made up of the service’s
URL and the table name
To view the orders of the customer ALFKI, enter the following URL in your browser:
http://localhost:51000/NWWebDataService.svc/Customers(’ALFKI’)/Orders
To isolate a specific column, append it to the table’s URL. The following URL will return the

city of the ALFKI customer:
http://localhost:51000/NWWebDataService.svc/Customers(’ALFKI’)/City
The service will return it as an XML element:
<City xmlns=" />You can also request just the value (in this case, Berlin) by appending a dollar sign fol-
lowed by the keyword value:
http://localhost:51000/NWWebDataService.svc/Customers(’ALFKI’)/City/$value
And you can navigate through related rows using this URL syntax. To view the IDs o f the
orders placed by the customer BLAUS, connect to the following URL:
http://localhost:51000/NWWebDataService.svc/Customers(’BLAUS’)/Orders
Here I used the URL that retrieves the customer and appended the name of the related tables
(the Orders table). The URL so far returns the orders of the specified customer. You can select
ADO.NET DATA SERVICES 925
a specific order by its ID and request its detail lines. The following URL does exactly that (the
URL should be entered as a single line; it’s been broken here to fit the printed page):
http://localhost:51000/NWWebDataService.svc/Customers(’BLAUS’)/
Orders(10501)/Order_Details
The service will return the following XML document:
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
- <feed xml:base=http://localhost:51000/NWWebDataService.svc/
xmlns:d= />xmlns:m= />xmlns=" /><title type="text">Order_Details</title>
<id>http://localhost:51000/NWWebDataService.svc/Customers(’BLAUS’)
/Orders(10501)/Order_Details</id>
<updated>2009-11-30T22:59:21Z</updated>
<link rel="self" title="Order_Details" href="Order_Details" />
- <entry>
<id>http://localhost:51000/NWWebDataService.svc/
Order_Details(OrderID=10501,ProductID=54)</id>
<title type="text" />
<updated>2009-11-30T22:59:21Z</updated>
- <author>

<name />
</author>
<link rel="edit" title="Order_Detail"
href="Order_Details(OrderID=10501,ProductID=54)" />
<link rel=" />dataservices/related/Order" type="application/atom+xml;
type=entry" title="Order"
href="Order_Details(OrderID=10501,ProductID=54)/Order" />
<link rel= />type="application/atom+xml;type=entry" title="Product"
href="Order_Details(OrderID=10501,ProductID=54)/Product" />
<category term="NorthwindModel.Order_Detail"
scheme=" />
- <content type="application/xml">
- <m:properties>
<d:OrderID m:type="Edm.Int32">10501</d:OrderID>
<d:ProductID m:type="Edm.Int32">54</d:ProductID>
<d:UnitPrice m:type="Edm.Decimal">7.4500</d:UnitPrice>
<d:Quantity m:type="Edm.Int16">20</d:Quantity>
<d:Discount m:type="Edm.Single">0</d:Discount>
</m:properties>
</content>
</entry>
</feed>
926 CHAPTER 21 BUILDING AND USING WEB SERVICES
If you examine it, you’ll see that it includes two related entities: the Order entity (the order
to which all detail lines belong) and the Product entity (the product listed in each detail line).
By default, the data service doesn’t move the related rows from the database to the client. If
you want to view the related data, you must use the expand keyword followed by the name(s)
of the entities you wish to retrieve along with each product. The following URL will bring a
single product, along with its category and supplier:
http://localhost:51000/WebDataService1.svc/

Products(12)?$expand=Category,%20Supplier
If you examine the XML returned by this query, you’ll see that it has three <link> elements,
which represent the related rows in other tables: the Order Details, Categories, and Suppliers
tables. The links to the Categories and Suppliers tables have a plus symbol in front of them,
and if you click it to expand the corresponding entity, you’ll see the details of the product’s
category or supplier, as shown in Figure 21.19.
Figure 21.19
Retrieving related rows
with the $expand
option
Likewise, the following statement will bring up an order along with its detail lines and a
product for each detail line:
http://localhost:51000/WebDataService1.svc/Customers(’BLAUS’)/
Orders(10501)/Order_Details?$expand=Product
Filtering
Querying your data through a URL may seem odd at first, but it’s sure easy to understand the
notation of querying through URLs. You can also filter the results returned by the query with
ADO.NET DATA SERVICES 927
the filter keyword, which must be followed by a filtering expression. The following statement
will return all products with a price that exceeds $100:
http://localhost:51000/WebDataService1.svc/Products?$filter=UnitPrice gt 100
Note that the comparison operator is not the greater than sign (>)butthegt operator. The fil-
tering expressions can get quite complicated because the URL syntax isn’t flexible enough for
expressing multiple criteria, joining tables together, and so on. The data service allows us to get
the data we need with a simple HTTP request, but for many operations you’ll have to down-
load the desired data at the client and process them locally with LINQ.
The data service works very nicely, and it took us no time at all to write. You’ll see shortly
how to write client applications against this service and how to submit updates to the database
as well. For now, keep in mind that an ADO.NET Data service exposes (or ‘‘surfaces’’) the data
in a data source. We have used a Data Entity Model as our data source in this example, but

you can use any data source such as LINQ to SQL, or even a collection of custom objects. No
matter how you specify the data source, the data service will expose it through HTTP requests.
The Basic Query Operators
For those of you who are interested in e xploring the notation for URL querying further,
Table 21.1 provides a quick overview of the various operators.
Table 21.1: Basic query operators
Type Operators
Logic operators eq (equals), ne (not equals)
gt (greater than), gteq (greater than or equal to)
lt (less than), lteq (less than or equal to)
and, or,andnot
Arithmetic operators add (addition), sub (subtraction), mul (multiplication), div (division),
mod (remainder)
round (round decimal digits)
floor (returns the largest integer that doesn’t exceed a value)
ceiling (returns the smallest integer that exceeds a value)
String operators contains, endswith, startswith, length, indexof, insert,
replace, substring, tolower, toupper, trim, concat
Date/time operators Second, hour, minute, day, month, year
Type operators Isof, cast
Here are a few simple examples of the operators you can use in your filtering expres-
sions. (I’m only showing the part of the URL that follows the service’s address, which is
http://localhost:51000/WebDataService1.svc/)
928 CHAPTER 21 BUILDING AND USING WEB SERVICES
Products?$filter=startswith(ProductName, Sir’)
Orders?$filter=month(OrderDate) eq 2
Orders?$filter=endswith(ShipName,’markt’)
ADO.NET Data Services allows you to expose (or surface) an entire database and build
client applications that use the service as the data source. The database has already been
exposed, and you can write code to exploit the data using the HttpWebRequest/HttpWeb

Response objects, as you saw in the preceding chapter. The data are exposed through the
simplest possible mechanism, namely through HTTP. But expressing conditions and statements
to retrieve our data using the URL syntax is rather awkward. There are more structured tools,
LINQ being one of them. Can you access the data exposed by your service with LINQ? The
answer is Yes! And LINQ can translate the standard query operators into the equivalent URL
syntax. This URL querying syntax is convenient for testing the service, but no one expects you
to write applications using the query syntax (not in this day and age, at least). There are tools
to use the same data as business objects from a client application, and this is what you’re about
to do next.
Building a Windows Client
In this section, I’ll guide you through the process of building a Windows client that consumes
the data of the data service. As you can guess, Microsoft didn’t go to the trouble of developing
ADO.NET Data Services so we would have to process the XML returned by the queries on our
own. There are tools that simplify the process enormously, and you’ll see them in this section.
The tool that will allow us to see the entities of the new Entity Data Model at the client is a
command-line tool. This tool is the DataSvcUtil.exe utility, which reads the service’s meta-
data and creates a class that encapsulates t he functionality of the service. Open a command
prompt window and run the DataSvcUtil.exe utility, which resides in the folder Windows\
Microsoft.NET\Framework.NET\v.4.0.xxx,wherexxx is the latest revision of the framework.
This value as of this writing is 4.0.30138, but it will most likely be a little different by the time
Visual Studio 2010 is released. Choose the most recent version of the Framework and switch
to that folder. In this folder you’ll find the DataSvcUtil.exe utility, which you must execute,
passing as an argument the URL of the data service for which you want to generate a proxy (a
proxy is a class that exposes the data model and the appropriate methods to access the service’s
data).
To generate the proxy, start the DataSvcUtil.exe utility with a statement similar to the fol-
lowing in the command prompt window:
DataSvcUtil.exe /out:"C:\Project\DataServices\NWProxy.vb"
/uri:"http://localhost:51000/NWWebDataService.csv" /language:vb
This creates the proxy file in the root folder of one of my drives, so I can locate it easily later.

Be sure to change the path to a folder that’s easy for you to locate later because you will have
to include this file with your solution.
Before you execute this command, however, you must start t he service for the new port to
take effect. Open the service in the browser as usual, and as soon as you see the list of tables
exposed by the service, close the browser. The service will continue to run in Visual Studio and
the browser is not required. The DataSvcUtil.exe utility will generate the file NWProxy.vb
in the specified folder. Move this file to the folder that contains the Windows project you’re
ADO.NET DATA SERVICES 929
building, and then add it to the same project with the Add  Existing Item command. You
could add the proxy to your current project without moving it, but then the project files will
be scattered all over your drives.
To simplify matters, I’ve added a Windows project to the solution, and its main form is
shown in Figure 21.20. This is the client application that will contact the data service to inter-
act with the Northwind database. To see the newly created data model in action, you must add
the auto-generated proxy file to the Windows project by copying the NWProxy.vb fileintothe
folder of the Windows application. Then place a button on the main form of the test project,
the Read Data From Service button, and create a variable to reference the proxy class with the
following declaration in your code (just make sure to change the port to the appropriate value):
Dim service As New NorthwindModel.NorthwindEntities(
New Uri("http://localhost:51000/NWWebDataService.svc"))
Figure 21.20
Consuming an ADO.NET
Data service through a
Windows client
Now you’re ready to access the tables in the Northwind database through the service. Your
Windows application is a client that connects to the service through the URL of a web service.
It has no direct access to the database, which means you don’t have to open any ports to SQL
Server on your database server and no credentials are stored at the client application. All it
needs is a so-called HTTP stack (basically, any computer that can access the Internet can host
the application). You can view the methods exposed by the NWProxy server (which are the

same as the methods of the underlying service) by entering the name of the service variable
and a period.
You probably recall that you granted access to all tables for all users. I’ll come back to this
topic shortly and show you how to limit the access to your service, but let’s see how the proxy
930 CHAPTER 21 BUILDING AND USING WEB SERVICES
allows you to access the data. With the service variable in place, you can access the tables in
the Northwind database as collections of typed objects.
To access a table, use a loop like the following, which iterates through the rows of the Cus-
tomers table:
For Each p In service.Products
Debug.WriteLine(p.ProductName)
Next
As you can see, the collections returned by the service are typed and so the p variable exposes
the names of the columns of the Customers table as properties.
In a similar manner, you can iterate through the Categories table:
For Each c In categories
Debug.WriteLine(c.CategoryName)
Next
If you want to access the products in each category, you must explicitly request these rows
of the Products table as you iterate through the categories. The data service doesn’t move
all data to the client. To request the matching rows of a related table, use the LoadProperty
method, which accepts two arguments: an entity name, which is a row of the current table, and
the name of the related table. To iterate through the categories and their related products, write
two nested loops like the following:
For Each c In categories
Debug.WriteLine(c.CategoryName)
service.LoadProperty(c, "Products")
For Each p In c.Products
Debug.WriteLine(p.ProductName & vbTab & p.UnitPrice.ToString)
Next

Next
Since you’re working with typed collections, you can use all LINQ operators to process your
data. The following loop selects the customers from Germany and then the orders they have
placed:
For Each c In service.Customers.Where(Function(cust) cust.Country = "Germany")
service.LoadProperty(c, "Orders")
Debug.WriteLine(c.CompanyName)
For Each o In c.Orders
Debug.WriteLine(vbTab & o.OrderID)
Next
Next
To use the data service e fficiently, you must execute the appropriate queries and retrieve
only the relevant data and not entire tables or even related rows that aren’t needed. Let’s say
you want to retrieve the orders from all customers in Germany that were placed in a specific
year. Instead of using the LoadProperty method to move all related rows from the Orders
table on the server to the client, you can use the CreateQuery method to pass a query to the
ADO.NET DATA SERVICES 931
server. You’ll specify your query in LINQ, but the data service will translate it to the appropri-
ate SQL query and execute it against the server. The following expression retrieves the rows of
the Orders table as a DataServiceQuery object, which is a typed collection:
service.CreateQuery(Of NorthwindModel.Order)("Orders")
Then you can apply any filtering on this query to limit the number of rows with the Where
operator:
Dim selOrders = service.CreateQuery(Of NorthwindModel.Order)("Orders").Where( _
Function(o) Year(o.OrderDate) = 1998 And
o.Customer.CustomerID = c)
Let’s say you need all customers in Italy ordered by their company name. To request this
information from the database, you’d write a query like the following:
Dim ItalianCustomers = From c In service.Customers
Where c.Country = "Italy"

OrderBy c.CompanyName
Select c
The preceding LINQ expression was translated into the following URI query:
{http://localhost:51000/NWWebDataService.svc/
Customers()?$filter=Country eq ‘Italy’&$orderby=CompanyName}
Your regular LINQ statements are being automatically translated into URIs that can be pro-
cessed by the service. It looks like a new variation of LINQ, LINQ to URI, but I haven’t seen
this term being used in the documentation. Anyway, the LINQ component knows how to talk
to ADO.NET Data Service s, but not always with success because not all expressions you can
use with LINQ have a counterpart in SQL. To retrieve the orders with a freight charge of more
than $20, use the following trivial LINQ query:
Dim q = From o In service.Orders
Where o.Freight > 20
Select o
The preceding LINQ query is translated into the following URI by the service:
http://localhost:51000/NWWebDataService.svc/
Orders()?$filter=cast(Freight gt 10M,’Edm.Boolean’)
In general, it’s much simpler to write queries using LINQ than to try to construct the proper
URL for any given query. The following LINQ query retrieves the products that contain the
word sauce in their name, regardless of spelling:
Dim q = From p In service.Products
Where p.ProductName.Contains("sauce")
Select p
932 CHAPTER 21 BUILDING AND USING WEB SERVICES
When this query is translated into a URI query, the service makes use of the substringof oper-
ator, as shown here:
{http://localhost:51000/NWWebDataService.svc/Products()?$
filter=substringof(’Sauce’,ProductName)}
If you’re wondering how I found the URI query for these LINQ queries, it’s really simple.
Place a breakpoint before the LINQ query you want to monitor, and when the program breaks,

add a new watch on the variable that represents the result of the query (the q variable in most
of this section’s examples). As soon as the LINQ statement is executed, the SQL statement that
will be executed against the database will appear in the Watches window.
Submitting Updates
Using an ADO.NET data service to explore the data in a database is a fairly straightforward
approach. Surfacing your data with a data service is ideal for reporting and other applications
that need not modify the database. Of course, the data service allows you to submit updates as
well, although this isn’t the best implemented feature of an ADO.NET data service — not yet,
that is. My main objection to updating a database through a data service is that the methods
that perform the updates do not return the error messages generated by the database. What
you get back is a generic error telling you that ‘‘an error occurred while processing this
request.’’ Whether the error occurred because the data violated some constraint or whether
it occurred because a field value was inappropriate for the corresponding column, the error
message is the same. If you decide to use a data service to submit updates to the database, be
prepared to validate the data as best as you can at the client, because the server won’t help
you determine the source of the update errors.
Since it’s only a question of time before Microsoft brings ADO.NET Data Services up to par
with the other data access technologies, let’s look at the process of submitting updates to the
database. Since the underlying data source is an Entity Data Model, the process of submitting
data to the service is more or less familiar to you. To insert new rows, you create new entities
at the client and submit them to the server with the service’s SaveChanges method. To update
existing rows, you just change the desired fields and then call the same method. To delete rows,
you delete entities at the client and then call the SaveChanges method.
To create a new product, for example, you must first create a Product object:
Dim prod As New NorthwindModel.Product
And then set the object’s properties with statements like the following:
prod.ProductName = "NEW PRODUCT NAME"
prod.UnitPrice = 11.2
prod.UnitsInStock = 1
prod.UnitsOnOrder = 12

Some of the properties are also entities, and they must be set to the appropriate entity. The
product category is specified in the database with the CategoryID field, but the entity Product
is related to the entity Category, so you must create a new Category entity and assign it to
the Category property of the prod variable. The same is true for the product’s supplier: You
ADO.NET DATA SERVICES 933
can set the prod.SupplierID field; you must set the Supplier property to an entity of the
Supplier type. The following statements create the Category entity by reading a row from the
Categories table and set up the Category property of the new product:
Dim cat = service.Categories.Where(Function(c) c.CategoryID = 4).First
prod.Category = cat
For the product supplier, I followed a different approach to better demonstrate the process
of updating the Data service. This time I created a new Supplier entity, which provides only
scalar properties, and then I set the new product’s Supplier property to this value:
Dim supplier As New NorthwindModel.Supplier
supplier.CompanyName = "New Supplier"
supplier.ContactName = "Contact name"
supplier.ContactTitle = "Contact title"
supplier.Address = "Supplier address"
supplier.City = "City"
supplier.Country = "Country"
service.AddToSuppliers(supplier)
service.SetLink(prod, "Supplier", supplier)
Note that this time the new entity is added to the Suppliers entity set at the client with the
AddToSuppliers method. The last statement associates the product with its supplier. This link
does not exist at the client and must be added explicitly. Note that you don’t have to specify
the fields that take part in the relationship.
At this point you can submit the new product to the database by calling the SaveChanges
method. The SaveChanges method doesn’t accept any arguments; it submits all changes to the
data service. Obviously, it will first submit the new supplier and then the new product. But this
isn’t something you have to worry about; the data service knows how to insert the rows in the

correct order.
You may have noticed that the code didn’t set the IDs of the two new rows. The IDs are
assigned by the database and the client has no way of knowing the next available ID in the
database. Other users may have inserted rows into any table and the client can’t make any
assumptions as to the next available ID. You could also use globally unique identifiers (GUIDs)
as primary keys, which would allow you to generate the primary key values at t he client, but
for most databases in use t oday this isn’t the case. Windows generates a long string that’s
unique every time you create one (as the term suggests, it’s a globally unique identifier, not in
your computer or your network).
After the SaveChanges method is called, the properties prod.ProductID and supp.Supplier
ID will have the correct values, which will be transmitted to the client by the data service. This
is why we had to explicitly add the appropriate links between the entities at the client with the
SetLink method. As a reminder, this is necessary for new rows only. If the ID of the related
row is known, as was the case with the Category entity, there’s no need to call the SetLink
method.
The Simple Updates button of the sample application contains the statements presented so
far in this chapter. Open the project to see the code that implements the simple operations, like
updates and deletions.
934 CHAPTER 21 BUILDING AND USING WEB SERVICES
Performing Transactions
Real data-driven applications perform updates in a transactional manner. As you will recall
from Chapter 15, ‘‘Programming with ADO.NET,’’ an order has to be committed to the data-
base in a transactional manner; the entire order must fail if a single row fails to be committed
to the database. Data services allow you to perform multiple updates in the context of a trans-
action as long as all the updates that belong to the transaction are submitted with a single call
to the SaveChanges method. In addition, you must set the SaveChangesDefaultOptions prop-
erty of the entity set to Batch.
Let’s consider the example of committing an order to the Northwind database, a rather
familiar example in this book. You have seen how to perform transactions with ADO.NET as
well as with data entities. To perform a transaction with a data service, you first create all the

objects that take part in the transaction: the order’s header and its detail lines. You should not
call the SaveChanges method until all entities have been created at the client, as discussed in
the preceding section. When the entire order has been set up at the client, you can submit it
to the database by calling the SaveChanges method.
Let’s review the actual code that submits a new order to the database. You can find the code
discussed in this section in the Transactional Updates button of t he DataServices project. As
usual, start with a reference to the data service, the service variable:
Dim service As New NorthwindModel.NorthwindEntities(
New Uri("http://localhost:51000/NWWebDataService.svc"))
Then, create a new Order object, the newOrder variable, and add it to the Orders table. The new
row is appended to the Orders entity set at the client. Moreover, the new order has no ID yet.
The OrderID value will be assigned by the database when the transaction is submitted to the
database. Here are the statements that create the newOrder object:
Dim newOrder As New NorthwindModel.Order
service.AddToOrders(newOrder)
Now create a customer object that represents the customer that placed the order. In a real
application, the user will most likely select the customer from a list, but since this application
doesn’t have an elaborate user interface, I retrieve a row from the Customers entity set at t he
client. You can’t simply set the newOrder.CustomerID property, even though the newOrder
variable expose s the CustomerID property. The newOrder object has a Customer property too,
and there’s a relationship between the Orders and Customers tables. To associate a customer
with the new order, we must set the order’s Customer property to a Customer object and then
establish a relationship between the two. The following statement creates the Customer object
that represents the Customer ANTON:
Dim cust = service.Customers.Where(Function(p) p.CustomerID = "ANTON").First()
Next, assign the cust object to the new order’s Customer property and then create a link
between the order and the customer:
newOrder.Customer = cust
service.SetLink(newOrder, "Customer", cust)
ADO.NET DATA SERVICES 935

Follow similar steps to associate an employee with the order:
Dim emp = service.Employees.Where(Function(p) p.EmployeeID = 3).First
newOrder.Employee = emp
service.SetLink(newOrder, "Employee", emp)
And then you can set the remaining (scalar) properties of the order with simple statements like
the following:
newOrder.OrderDate = Now
newOrder.ShipAddress = cust.Address
newOrder.ShipCity = cust.City
Now you can create the order details. Each detail is a variable of the Detail type, and each
must be added to the Order_Details property of the newOrder object. The Order_Details
property is a collection, and as such it provides an Add method. The following statement creates
anewvariable,thedtl variable, that represents an order detail:
Dim dtl As New NorthwindModel.Order_Detail
This variable must be added to the Order_Details entity set:
service.AddToOrder_Details(dtl)
You must also associate the new detail line with the new order:
dtl.Order = newOrder
Now you can set the properties of the detail line with statements like the following. Again,
the product is selected from the Products table by its ID, while in a real application you’d prob-
ably allow the user to select it from a list or another lookup mechanism:
Dim dtlProd = service.Products.Where(Function(p) p.ProductID = 31).First
Set the Product property of the new order to the dtlProduct object:
dtl.Product = dtlProd
And create a link between the detail line and the corresponding row in the Products entity set:
service.SetLink(dtl, "Product", dtlProd)
Then, you can set the remaining properties of the detail line with simple statements like the
following:
dtl.Product = dtlProd
dtl.UnitPrice = 11.2

dtl.Quantity = 9
936 CHAPTER 21 BUILDING AND USING WEB SERVICES
And finally, associate the new detail line with the new order:
service.SetLink(dtl, "Order", newOrder)
Let’s add a second detail line to the order with the following statements:
dtl = New NorthwindModel.Order_Detail
dtlProd = New NorthwindModel.Product
dtlProd = service.Products.Where(Function(p) p.ProductID = 56).First()
dtl.Order = newOrder
dtl.Product = dtlProd
dtl.UnitPrice = dtlProd.UnitPrice * 0.9
dtl.Discount = 0.1
dtl.Quantity = -5
service.AddToOrder_Details(dtl)
newOrder.Order_Details.Add(dtl)
service.SetLink(dtl, "Product", dtlProd)
service.SetLink(dtl, "Order", newOrder)
This time I’ve chosen to include a discount as well. Other than that, the process of creating a
new detail line is the same. The new detail line is linked to a Product entity because it contains
the ID of a product and to the Order entity to which is belongs.
At last, you can commit the new order to the data service by calling the SaveChanges
method of the service variable. All the objects you have created at the client will be submitted
to the database in the proper order by the data service. To ensure that all rows will be inserted
in a transactional manner, you must also set the service.SaveChangesDefaultOptions
property to the value shown in the code:
service.SaveChangesDefaultOptions = Services.Client.SaveChangesOptions.Batch
Try
service.SaveChanges()
Catch ex As Exception
MsgBox("FAILED TO COMMIT NEW ORDER")

End Try
To see for yourself that the order is submitted in a transactional manner, set the price of the
product of the first detail line to a negative value. The database will reject this value and the
entire transaction will fail. The error that will be raised by the data service will have a very
generic description, which won’t help you locate the bug in your code. With a straight ADO
transaction, you get back the error message generated by the database, which clearly states the
value that the database couldn’t accept.
Once the transaction has been committed, the data service will return the ID of the order as
well as the IDs of all entities involved in the transaction. In this case, there’s only one ID value
to be set by the database.
Through the examples in this section, it’s clear that a data service is quite trivial to build and
very easy to use for selection queries. When it comes to updating the database, however, the
data service doesn’t provide all the mechanisms you’ve come to expect with other data access
technologies. You must establish relationships between entities in your code and you can’t rely
on meaningful error descriptions to debug your code.
ADO.NET DATA SERVICES 937
Securing Your Data Service
Exposing a database as a web service on the Internet is not the best idea. To secure the
database from malicious users, you must set up some security measures. As you recall, the
SetEntitySetAccessRule method sets the access rule for the various entities exposed by the
service. So far, you allowed users to access all entities for all operations with the following
statement:
config.SetEntitySetAccessRule("*", EntitySetRights.All)
The first argument is the name of an entity and the second argument is a member of the
EntitySetRight enumeration: None, All, AllRead, AllWrite, ReadMultiple, ReadSingle,
WriteAppend, WriteDelete, WriteMerge, WriteReplace. The names o f most members are
self-descriptive. The ReadMultiple and ReadSingle members determine whether users can
read multiple rows or a single row of the corresponding entity set.
Limiting Data Access
Whenever possible, you should limit the number of rows a client application can request

because sloppy developers will retrieve an entire table to select a few rows at t he client. The
following method call enables clients to read all rows, insert new rows, and replace existing
rows in the Orders entity set:
config.SetEntitySetAccessRule("Orders", EntitySetRights.AllRead Or
EntitySetRights.WriteAppend Or EntitySetRights.WriteReplace)
Notice that multiple options are combined with the Or operator.
The SetServiceOperationAccessRule specifies the access rules for the various operations.
The first argument is a method name and the second argument is a member of the
ServiceOperationRights enumeration: All, AllRead, None, OverrideSetEntityRights,
ReadMultiple,orReadSingle.
The two methods are quite useful for specifying simple access rules but not nearly adequate
enough for implementing more advanced security rules. Typical access rules, such as allowing
users to view their own orders only, must be implemented with the appropriate procedures.
These procedures are called interceptors because they intercept a query and alter the query’s
default behavior. To intercept a selection query, for example, use t he QueryInterceptor
attribute followed by the name of an entity set in parentheses:
<QueryInterceptor("Orders")>
Public Function OnQueryOrders() As Expression(Of Func(Of Orders, Boolean))
‘ statements
End Function
The OnQueryOrders function will be called every time a client selects one or more rows from
the Orders table and it’s implemented as a lambda expression that accepts as arguments a

×