Home > 2010 Fall DPS907 > Consume a WCF Data Service using XML

Consume a WCF Data Service using XML

October 11, 2010 Leave a comment Go to comments

We have learned that it is easy to create a WCF Data Service. We also learned that it is easy to use a browser to send GET requests to the service. This post teaches you to create a full-featured client (as an ASP.NET Web Forms page).


.

You can use a web browser to send GET requests to a WCF Data Service. The server will respond with XML results, by default. If you want to send PUT, POST, or DELETE requests, the browser won’t do the job. You need to create a client/consumer, or use a utility like curl, or Fiddler.

In this post, you will learn to create a full-featured client/consumer, as an ASP.NET Web Forms page. You will also learn to use curl, a command-line utility.

You can use this post to help you complete DPS907/WSA500 Lab 1, or as a standalone tutorial.

On the web, there are many good-quality tutorials that teach you to create and consume a WCF Data Service. However, it is difficult (i.e. almost impossible!) to locate a tutorial that covers the PUT, POST, and DELETE operations in any detail.

This post will serve the community as a full-featured tutorial, that covers GET, PUT, POST, and DELETE operations.

.

As you work through this post, make sure that you work incrementally, carefully think about what you are doing and the algorithms that you are using, and test your work as you make changes.

A future post will cover the task of consuming using JSON. Its working title will likely be:

Consume a WCF Data Service using JSON

.

Locating help documentation

The OData documentation is useful. Its Protocol Documentation page is the gateway to the information that you need. The following sections should be studied:

Your professor offers the following observations about the documentation: You may not find what you’re looking for when you read all or parts of it for the first time. However, as you attempt to create a full-featured client/consumer, you will probably return to specific sections of the documentation, and truly learn as you read it again.

In this post, we will highlight specific sections of the documentation, as we create a client/consumer. However, do not assume that you will need to know only the highlighted specific sections. You will need to cover all of it, and it will be your job – your responsibility – to do so.

.

Getting started – creating an ASP.NET Web Forms page

To leverage your experience with ASP.NET Web Forms programming, we will create our client/consumer using this technology.

Create a new file system web site (or add on to an existing one). We will create three components:

  1. An Entity Framework data model
  2. A WCF Data Service
  3. An ASP.NET Web Forms page

.

Entity Framework data model

It is assumed that you will be using the Northwind sample database as you create an Entity Data Model. As a DPS907/WSA500 student who is using the warp database server, you will actually be using a copy of the Northwind tables, where each table copy has a prefix of “nw” (e.g. nwProducts, nwEmployees, etc.).

Create a new ADO.NET Entity Data Model item. The item name (for the .edmx file) doesn’t really matter, but the entity container and model namespace names do matter. Please review the previous posts for guidance on naming Entity Framework components (WCF Data Services introduction, and Entity Framework introduction).

If you followed the guidance, your names will be similar to the following:

.edmx item name – <whatever>.edmx

Entity container (and connection string) – MyNorthwindEntities

Model namespace – MyNorthwindModel

.

WCF Data Service

Next, create a WCF Data Service item. Its name doesn’t matter.

As covered in previous posts, the resulting service does not work out-of-the-box. The Visual Studio template provides you with most of the code, but there are two critically-important edits that must be done. Both of these are commented out in the template.

Before making the edits, you can decide whether you want a using directive, which may save you a bit of typing while writing code.

  • If you want a using directive… add “using MyNorthwindModel;” near the top of your class source code file.
  • If you do not, you will have to fully-qualify the names of types that are in the MyNorthwindModel namespace

.

The first edit…

Now, you’re ready for the first edit: The template provides the following service class declaration:

public class MyDataService : DataService</* Entity container name */>

The DataService needs a specific type, so that can initialize correctly.

You will replace “/* Entity container name */” with the name of your entity container (either MyNorthwindModel.MyNorthwindEntities, or more simply MyNorthwindEntities).

.

The second edit…

Now, you’re ready for the second edit: The template provides a commented-out entity set access rule. You will uncomment and modify, to support the operations that you want or need.

For learning and testing, you can configure the rule to allow ALL of your entities to support ALL operations. In other words, nwProducts, nwCategories, nwEmployees, and so on, will support GET, PUT, POST, and DELETE operations. Doing this enables you to learn what you need to, while allowing you to tighten your access rules later.

If you follow this advice, you will edit the code, writing the following access rule:

config.SetEntitySetAccessRule(“*”, EntitySetRights.All);

The * asterisk means “all entities”. If you wanted to tighten the rules, you would replace the asterisk with the name of a specific entity.

The “EntitySetRights.All” means read, write, update, and delete. If you wanted to tighten the rules, you would replace this with one or more rights; multiple rights are separated by an | “or” operator.

.

Testing your edits…

After completing these two edits, your WCF Data Service will work correctly. Try out the range of GET operations found in the OData operations documentation, in sections 2.1 and 2.2.

.

ASP.NET Web Forms page

Create an ASP.NET Web Forms page/item. It will need the following user interface objects:

  1. A text box that will enable you to enter a URI to the WCF Data Service
  2. A control to enable the HTTP method selection
  3. A control to enable the data format of the request and response
  4. An input area for the request data (that goes along with PUT and POST requests)
  5. A submit button
  6. An area to display the response (if any) that the server returns

An example of the user interface is shown below. Yours may look different.

.

In the following sections, you will learn how to code the page’s behaviour.

Incidentally, the behaviour of the form can be customized, if you want to ensure error-free operation (and your professor recommends that you do so). For example:

  • You could enable or disable the “Request data” area, based on the HTTP method selection
  • You could dynamically set the “Data format” choices, based on the HTTP method selection
  • You could wrap the request and response processing in a try-catch block, and use the “Response” area to show exception messages

Also, please note that the approach presented here could be applied to other client/consumer technologies. As noted earlier, there will be a future post that will teach you to “Consume a WCF Data Service using JSON”. In that post, you will learn how to do so by creating a JavaScript-based client/consumer.

.

Coding a web request and response in an ASP.NET Web Forms page

We are using an ASP.NET Web Forms page to host the functionality needed to make a “request” to a web resource, and process the “response” sent back to us by the server. The functionality is made possible by the System.Net.WebRequest class, and the System.Net.WebResponse class.

As you will see in the documentation, these are abstract classes.

When using System.Net.WebRequest, to create an instance of one of its concrete classes, you use the static Create() method. The string that you pass in will determine the exact type of the concrete instance. For example, if you pass in a web URI (i.e. “http://something…&#8221;), an instance of System.Net.HttpWebRequest will be returned.

After the request instance has been created, it must be configured (with the method, request headers, and so on).

After the request instance has been configured, we move on to the task of submitting the request, and processing the response from the server. The typical approach is to use the request’s GetResponse() method, which will perform both tasks, and in our situation, return a concrete instance of the System.Net.WebResponse class, which (in our situation) will be a System.Net.HttpWebResponse instance.

In summary, you can see that this a 3-step process:

  1. Create a System.Net.WebRequest
  2. Configure the request
  3. Submit the request, and process the response

The following example shows the typical coding pattern for a GET request:

.

// Create a System.Net.WebRequest instance
var request = (System.Net.HttpWebRequest)WebRequest.Create("http://www.example.com");

// Configure the request
request.Method = "GET";

// Submit the request, process the response,
// and write the result to a text box named tbResponse
try
{
    using (var response = (HttpWebResponse)request.GetResponse())
    {
        // Get the response stream
        System.IO.StreamReader sr =
            new System.IO.StreamReader(response.GetResponseStream());
        // Report the results
        tbResponse.Text = sr.ReadToEnd();
    }
}
catch (Exception ex)
{
    // Report the exception
    tbResponse.Text = ex.Message;
}

.

Do you know var and using in the code example above? If you don’t, or if you need a refresher, see the C# coding conventions section at the bottom of this post.

.

Note: Both the System.Net and the System.Web namespaces have classes named HttpWebRequest and HttpWebResponse. If you are doing this work in an ASP.NET Web Forms page, as we are doing here in this example, you will have to qualify your type names when using the typical using directives that come with a new Web Form.

.

Determine the behaviour of the operations

The first task is to determine the behaviour of the HTTP (GET, PUT, POST, and DELETE) operations. You must create a list, or a table, that clearly identifies the behaviours. There are essentially five (5) HTTP operations that you must support in this client/consumer ASP.NET Web Forms page:

  1. GET
  2. DELETE
  3. POST
  4. PUT to update a full/complete entry
  5. PUT to update a property of an entry

An “entry” is an instance of an entity. The OData documentation uses the “entry” term, and so will we.

For each HTTP operation, you must then determine the use and configuration/content of these properties:

  1. The URI
  2. The HTTP method
  3. The “accept” header in the request
  4. The “content-type” header in the request
  5. The request data to be sent

Obviously, some HTTP operations do not use all of these properties. For example, the GET method uses the URI, and the accept header. In contrast, the POST method will use all of them.

Read the OData operations documentation, and write down your findings (in a list or table).

.

GET to a WCF Data Service

Implementing GET is easy.

The code in the example above will just work. Try it with the URIs that we used in class, or the ones you will find in the OData Operations and URI Conventions documentation.

.

DELETE to a WCF Data Service

Implementing DELETE is easy.

The code in the example above will just work.

Make sure that the URI includes a specific entity identifier (e.g. “nwCategories(16)”). Additionally, your service’s entity access rule must permit deletion.

If the delete operation was successful, the server will send an empty response body (as you will learn in the OData operations documentation). So, after a seemingly-successful delete operation, try to GET the resource that you just deleted, and you will probably see a “404 not found” result, which is correct.

Some delete operations will not work, because of entity constraints that preserve data integrity. For example, if an existing product (i.e. nwProducts) entity appears on a sales order, it cannot be deleted. Therefore, the easiest way to test delete operations is to first add new entities to a collection like nwCategories or nwSuppliers, and then delete them.

.

Getting started with the Atom format

Before moving on to POST and PUT operations, you need to learn something about the Atom format. POST operations require Atom-formatted data to be submitted with the request. PUT operations, for full-entry updating, also require Atom-formatted data to be submitted with the request. Both operations return their responses as Atom-formatted data.

The Atom format, which is an XML implementation, enables the packaging of entries (i.e. entity instances) into an Atom feed. The OData Atom Format documentation provides a good introduction, and implementation details. Read sections 1, 2, 2.1, 2.2, 2.3, and 2.4, and return here for a discussion of the relevant highlights.

Welcome back.

As stated above, we need to know about the Atom format for POST and PUT operations. You have just learned that many of Atom’s XML elements and attributes are optional. Which elements are required, and which ones are optional? How can you make this easy and uncomplicated?

The following is an example of the least that’s required for a POST operation to add a new entry (i.e. entity) to the nwCategories collection, in a situation where the entry (i.e. entity) is NOT related (i.e. have a navigation property) to another. You must POST this request data to the collection URI, “/nwCategories”.

.

.

A few highlights of this data:

  • The <?xml… declaration is required
  • The <entry… element is required
  • The <content… element is required
  • The <m:properties element is required, enclosing an entry’s properties

The OData documentation tells us that missing elements are given default values. In addition, key or identity values are determined by the server, and returned in the response body.

What about a PUT operation? It turns out that the same data can be submitted with a PUT request. The request’s URI must include a specific entry/entity (e.g. “/nwCategories(18)”).

.

POST to a WCF Data Service

Now that you know the format of the request data in a POST operation, we can write the code for it. The approach is to modify the code example above, by adding more configuration data:

  • the “accept” header
  • the “content-type” header
  • the request data that will be sent

In an earlier section of this post (Determine the behaviour of the operations), you created a list or table of configuration data/settings for POST. Refer to this list, and to the code example above, and do the following:

Set the URI to the entry/entity collection (e.g. “/nwCategories”).

Configure the request’s method must be POST.

Configure the request’s ContentType and Accept properties with the correct values.

POST operations to a WCF Data Service must use the “application/atom+xml” content type header. Typically, the accept header is set to the same value.

Now, configure the request’s data. This requires some explanation.

Unlike the ContentType and Accept properties above, there’s no simple-to-use property that represents the request’s data. (Why? Well, it’s possible that the request’s data could be binary-encoded data, and not simply text-oriented string data. Therefore, we need a more general way of configuring the request’s data.)

We have to perform a 3-step procedure to configure the request with its data:

  1. Encode the data into a byte array
  2. Configure the request’s ContentLength property with the byte array’s size
  3. Stream the byte array to the request (which effectively places the data with the request)

In this post, we are working with text-oriented data – Atom-formatted XML. Therefore, we will use a text encoder, which will encode string data into a byte array.

The following is the typical coding pattern. Obviously, the following code is placed just after the code that configures the request’s properties, and just before the code that submits the request.

.

// Configure the request data

// Create an encoder
var encoding = new System.Text.ASCIIEncoding();
// Encode the string that's in the "tbInputData" text box
byte[] bytes = encoding.GetBytes(tbInputData.Text);

// Set the content length property
request.ContentLength = bytes.Length;

// Get the request's stream object
var theStream = request.GetRequestStream();
// Write the byte array data to the request's stream object
theStream.Write(bytes, 0, bytes.Length);

.

POST that includes an entry link (i.e. an entity navigation property)

Up until this point in the post, we have used simple examples, with an entry that does not link (i.e. its entity does not have a navigation property that points) to another entry. This works for nwCategories and nwSuppliers.

However, it won’t work for nwProducts, for example. Why? This entry type has a CategoryID property, and a SupplierID property. When we create a new entry for the nwProducts collection, we must provide values for these properties. How? There are typically two approaches:

  1. POST to the collection, and include “link” information in the entry’s properties
  2. POST to a URI that includes the link information

.

POST to the collection, and include “link” information in the entry’s properties

For example, assume that we want to add a new entry to the nwProducts collection. As noted above, a new product will require a category, and a supplier. We set these values in the product’s properties, as shown in the following example.

Note that SupplierID is set to 25, and CategoryID is set to 6.

.

.

POST to a URI that includes the link information

This approach may work if the link properties are optional. For example, using the example above, it may be possible to omit either or both of CategoryID and SupplierID. If that’s the case, we can post to a specific category’s products collection, and either provide a value for SupplierID, or leave it blank.

In the following example, we would POST to this URI: /nwCategories(6)/nwProducts

Note that the CategoryID property was omitted. If we wanted to, and the entry/entity definition allowed it, we could also omit the SupplierID property.

.

.

PUT to a WCF Data Service

Coding the PUT operation behaviour is similar to the POST code above.

There are two ways to support the PUT operation:

  1. The PUT request updates two or more of the entry’s properties
  2. The PUT request updates only one of the entry’s properties

.

The PUT request updates two or more of the entry’s properties

For this kind of update, the procedure is a close match to the POST operation. Here are the differences:

The URI must name the specific entry/entity to be updated (e.g. “/nwCategories(16)”).

The request’s method must be PUT.

.

The PUT request updates only one of the entry’s properties

For this kind of update, the procedure is also a close match to the POST operation. Here are the differences:

The URI must name the specific entry/entity to be updated, and its property (e.g. “/nwCategories(16)/Description”).

The request’s method must be PUT.

The request’s content type must be “application/xml” (and not the Atom variant).

The request data is a simplified XML document, that looks like the following example:

.

.

Using the curl utility

curl is a command-line utility (from the cURL Project)  that enables a user to work with URIs. Of interest to us, it supports GET, DELETE, POST, and PUT operations on WCF Data Services.

For more information, you can read the Wikipedia cURL overview, and visit the cURL web site.

Earlier in this post, you learned how to perform common HTTP operations. For POST and PUT operations, you learned how to create Atom-formatted request data. If you have the data in a file, you can easily send it with curl.

The following is a curl syntax summary for the HTTP operations in this post. Assume that we are working with the nwCategories collection of entries/entities.

Some of the commands are long, and they will wrap in this post.

.

curl GET syntax

All categories:

curl http://www.example.com/WcfDataService.svc/nwCategories

One category (#6):

curl http://www.example.com/WcfDataService.svc/nwCategories(6)

.

curl DELETE syntax

Delete category 18:

curl -X DELETE curl http://www.example.com/WcfDataService.svc/nwCategories(18)

.

curl POST syntax

Assume that we have saved the data in a file called “postdata1.xml”:

curl –request POST –header “content-type: application/atom+xml” –data “@postdata1.xml” http://www.example.com/WcfDataService.svc/nwCategories

.

curl PUT syntax

Assume that we have saved the data in a file called “putdata1.xml”. Note that the request URI specifically identifies the category to be updated:

curl –request PUT –header “content-type: application/atom+xml” –data “@putdata1.xml” http://www.example.com/WcfDataService.svc/nwCategories(19)

.

Summary

Implementing GET, DELETE, POST, and PUT operations is not very difficult or complex, provided that you have a clear understanding of the processes involved, and make use of the documentation.

Feel free to comment on this post, and/or ask questions or submit suggestions for improvements.

.

C# coding conventions

The examples above used two C# language elements that you may not be familiar with, or you may need a refresher on – var, and using. Some of the following was adapted from MSDN documentation.

.

var

var is a C# type.

Variables that are declared at method scope can have an implicit type of var.

The var keyword instructs the compiler to infer the type of the variable from the expression on the right side of the initialization statement. The inferred type may be a built-in type, an anonymous type, a user-defined type, or a type defined in the .NET Framework class library.

It is important to understand that the var keyword does not mean “variant” and does not indicate that the variable is loosely typed, or late-bound. It just means that the compiler determines and assigns the most appropriate type.

The var keyword can also be useful when the specific type of the variable is tedious to type on the keyboard, or is obvious, or does not add to the readability of the code.

It can also be useful when you are creating a specific descendant object from an abstract base class’ factory method. For example, System.Net.WebRequest is an abstract class, and you must use its Create() method to create new instances. The argument syntax will return a System.Net.HttpWebRequest if the URI starts with “http://”. (The documentation states that you cannot directly create a System.Net.HttpWebRequest.) So, in this case, using var can save clumsy syntax typing and/or typecasting.

See the following code sample:

.

// The following statements deliver the same results

System.Net.HttpWebRequest request1 = (System.Net.HttpWebRequest)WebRequest.Create("http://www.example.com");

var request2 = (System.Net.HttpWebRequest)WebRequest.Create("http://www.example.com");

// Which is easier to read?

.

using

From the C# reference documentation:

The using keyword has two major uses:

  • As a directive, when it is used to create an alias for a namespace or to import types defined in other namespaces.
  • As a statement, when it defines a scope, at the end of which, an object will be disposed.

We are very accustomed to the using directive. We write a number of using directives at the beginning of our class source code files.

The using statement provides a convenient syntax that ensures the correct use of IDisposable objects.

As a rule, when you use an IDisposable object, you should declare and instantiate it in a using statement. The using statement calls the Dispose method on the object in the correct way, and (when you use it as shown earlier) it also causes the object itself to go out of scope as soon as Dispose is called.

Our earlier example code showed a using statement, that wrapped a code block that executed a request to and response from a URI. It’s just good practice to do it this way.

.

// Example of the "using statement"...

using (var response = (HttpWebResponse)request.GetResponse())
{
    // Get the response stream
    System.IO.StreamReader sr =
        new System.IO.StreamReader(response.GetResponseStream());
    // Report the results
    tbResponse.Text = sr.ReadToEnd();
}

.


Advertisements
Categories: 2010 Fall DPS907
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: