Chapter 21. Data Access with WCF Data Services

Throughout this section of the book, we covered a wide variety of ways to get data to and from your web forms. In a standard n-tier model, the web application would talk to a middle tier containing business logic, which would in turn talk to a data tier responsible for communicating with the database.

Although this is a perfectly adequate model, to handle the scalability and performance demands of modern distributed applications, we need something a bit more flexible. In large organizations we might have dozens of services exposed by multiple departments and multiple web applications that need to consume those services. In this case, we can’t just add a reference to the Business Logic Layer (BLL) and start invoking methods.

This chapter shows you how to use WCF Data Services to facilitate high-performance, scalable, distributed applications by exposing entity models as RESTful web services that can be reused by many applications on many platforms. We provide an overview of the WCF Data Services technology and provide samples of some of the ways your ASP.NET application can consume these services.

Overview of WCF Data Services

When you see data access samples, most of them illustrate making a direct database connection from the web server (even if it’s from a data tier) to the database. Although this is fine for illustrative purposes, it’s not that practical. In many production environments, firewalls and other security systems prevent direct database connections of any kind. As previously mentioned, in many service-oriented, distributed systems, client code must go through a service to read or write any data.

In the past, developers had to manually create their own service-based facades in front of the database, generally creating headaches and extra work. WCF Data Services aims to solve some of these problems. A WCF Data Service is a standardized service that exposes underlying relational data as a RESTful Web Service. This means that there’s no WSDL, no ASMX, just a convention for building URL-based queries and using the AtomPub or JSON protocols for sending data over the wire.

It is beyond the scope of this book to go into too much detail on how to build WCF Data Services or create the underlying Entity Models exposed by WCF Data Services. For more information on these services, read the MSDN documentation at

http://msdn.microsoft.com/en-us/data/bb931106.aspx.

The following two sections show you how to allow your ASP.NET application to communicate with a WCF Data Service, either by using a Service Reference or by using the client-side DataServiceContext class directly.

Using Data Services with a Service Reference

If you’ve used Visual Studio for any length of time, you’re probably familiar with the ability to add service references to existing projects. These either discover services in the current solution, or you can enter the URL of a WCF service and the service reference creates a client-side proxy for you. Up until now, this has worked only for regular web services.

With Visual Studio 2010 and WCF Data Services, you can add a Service Reference to a WCF Data Service. This Service Reference can create a special client proxy for you, one that exposes all the entities for the service and handles all the change tracking and other heavy lifting that, as a service consumer, you just don’t want to have to deal with manually.

Figure 21.1 shows the screen you see when attempting to add a service reference to a WCF Data Service. Each of the entities exposed by the data service appear as properties on the client proxy generated.

Figure 21.1. Adding a service reference to a WCF Data Service.

image

To illustrate how we might consume a WCF Data Service from an ASP.NET application, let’s use an example that is a little bit more fun than the typical “Hello World” sample.

Let’s say that you work for a fabulously successful video game company and your most popular game, Zombie Killa, has an incredible multiplayer mode. You’ve been asked to make the kill list and scores available on the web. The team that keeps that information won’t let you access its database directly for many (very good) reasons. Instead, it set up a WCF Data Service that exposes some of the key entities that you need to create your high scores website.

The first thing to do is add a service reference to its service, which you can see in Figure 21.1. This creates a client-side proxy that enables your code to treat the entities like regular LINQ data sources. Your LINQ queries convert into the WCF Data Services URL syntax, and the data coming back from the service converts into instances of the classes generated when you added the service reference.

Now that you have your service reference, you’re ready to access the data service. For this sample, we’ve been asked to create a My Kills page that shows all the zombies a particular player has killed and the weapons she used.

We created a MyKills.aspx page and then added the code from Listing 21.1 to the Page_Load() method. In addition to enabling us to write LINQ queries against specific entities, we can also invoke custom methods made available on the service. The game team didn’t want us to try to create the My Kills filter on our own because it might change over time, so it provided a method on the service called “GetMyKills” that we can execute. The amazing thing is that the result of this custom method execution is still queryable, enabling us to further filter, page, and sort the results.

Listing 21.1. Page_Load for MyKills.aspx.cs

images

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 functionality 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 specified 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 building 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.

Figure 21.2. Displaying “My Kills” with data from WCF Data Service.

image

The 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

images

The 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 infrastructure 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

images

From 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

images

The 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.

The 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 incredible 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset