|
|
-
The Desert Mountain Team has been hard at work setting up MSDN events. As you may have heard, the number of events sponsored by Microsoft Across America has been greatly reduced. Our goal on the Desert Mountain Team is to ensure that you still have the same access to events as in years past. In fact, in many cases, you will have greater access. We are scheduling events known as "MSDN Events Unleashed". There are a few differences between these events and those in years past. Many of these events will not be held in movie theatres. In order to schedule more of these, we are working with community leaders to find other venues (read less expensive) in the various cities. Another difference is that we will have greater control over the content. You can read into this any way you want, but the way I see it is that I will make sure the sessions and demos rock! Here is a listing of the events that we have set up so far in September and October (including the traditional events): Albuquerque – September 19, 2008 Albuquerque Garden Center 10120 Lomas Blvd NE Albuquerque New Mexico 87112
Check-in: 12:30 pm Start: 1:00 pm End: 5:00 pm Register: https://msevents.microsoft.com/CUI/EventDetail.aspx?culture=en-US&EventID=1032386885
Las Vegas – September 23, 2008 Renaissance Las Vegas Hotel 3400 Paradise Rd Las Vegas Nevada 89169-2770 Check-in: 12:30 pm Start: 1:00 pm End: 5:00 pm Register: http://msevents.microsoft.com/cui/EventDetail.aspx?culture=en-US&EventID=1032385361 Phoenix – September 25, 2008 Sheraton Crescent Hotel 2620 W Dunlap Ave Phoenix AZ 85021
Check-in: 12:30 pm Start: 1:00 pm End: 5:00 pm Register: https://msevents.microsoft.com/cui/EventDetail.aspx?culture=en-US&EventID=1032385363 Colorado Springs – September 30, 2008 Configuresoft, Inc. 7450 Campus Drive Colorado Springs Colorado 80920
Check-in: 12:30 pm Start: 1:00 pm End: 5:00 pm Register: http://msevents.microsoft.com/CUI/EventDetail.aspx?culture=en-US&EventID=1032386882
Denver – October 1, 2008 Greenwood Plaza 12 8141 E Arapahoe Rd Englewood Colorado 80112 Check-in: 12:00 pm Start: 1:00 pm End: 5:00 pm Register: http://msevents.microsoft.com/CUI/EventDetail.aspx?culture=en-US&EventID=1032386880 Fort Collins – October 2, 2008 Sunset Event Center 242 Linden Street Fort Collins Colorado 80524 Check-in: 12:30 pm Start: 1:00 pm End: 5:00 pm Register: http://msevents.microsoft.com/CUI/EventDetail.aspx?culture=en-US&EventID=1032386884 We are planning on holding an event in Salt Lake City in October. We are also working to finalize the date for Billings. Please send me a note or add a comment if you have any feedback... Thanks, Rob
|
-
So far in this series (click here for an index of the complete series, as well as supporting screencasts), I have illustrated how to develop both a LO-REST, AJAX-Friendly service, as well as HI-REST services adhering to the unified API of HTTP. In the very first post, I touched on some aspects of REST, but I haven't spent much time on the benefits of following a RESTful architectural style. I made mention of the fact that RESTful services follow the "way of the web". As it turns out, this proves to be quite powerful. The first 2 sentences of Section 13 of the HTTP 1.1 Specification highlight this point quite well: "HTTP is typically used for distributed information systems, where performance can be improved by the use of response caches. The HTTP/1.1 protocol includes a number of elements intended to make caching work as well as possible." What can be gleaned from this is that HTTP, the underlying protocol used by the web, is explicit about how to support caching responses. Clients (such as browsers), proxies and web servers all participate in caching responses, providing the scalability required by applications running on the web. RESTful service architectures seek to take advantage this infrastructure for their services. ******* Click here for a Screencast Illustrating Supporting Caching and Conditional GET ******* Why Not SOAP? You might be thinking to yourself: traditional web services run over HTTP, wouldn't they take advantage of this caching infrastructure, as well? The answer lies in how SOAP was designed. The authors of SOAP had more than HTTP in mind when they built the specification. One of the goals of SOAP was to be able to pass SOAP messages over differing protocols. That dream is alive today... just take a look at WCF services running over TCP, Named Pipes or MSMQ. In order to make this work, SOAP acts in a protocol independent manner. Take the following SOAP service using the basicHttpBinding: I used SvcUtil to create a client proxy for this service in a console application and called SayHello. Below you will see a screenshot of Fiddler, showing the HTTP traffic for this call: I underlined some key aspects of this call. First of all notice that, although our intent is to fetch some information, we have issued an HTTP POST. More about that later. How did I know what our intention was? I had to look in the body of the message. I underlined where the method name is embedded in the body of the message. In SOAP services, the intention of the call is the method. Lastly, notice that scope of what I want to fetch is also embedded in the message. Is this a bad thing? Not at all. Embedding this information in the message makes SOAP protocol independent. The same message could be sent over TCP and work all the same. However, that very nature is what precludes this SOAP service from easily taking advantage of the webs caching infrastructure. Keep this information in mind while you read the next few sections and it should become clear. GET (and HEAD) is/are special in HTTP HTTP GET and HEAD are considered special. The HTTP specification is explicit that unless the server prohibits caching, GET and HEAD shouldn't have any side effects that would adversely effect the caching of the response (GETs and HEADs with querystring parameters are an exception). In other words, these two verbs are set up for caching. Section 9.1.1 of the HTTP 1.1 Specification states "In particular, the convention has been established that the GET and HEAD methods SHOULD NOT have the significance of taking an action other than retrieval. These methods ought to be considered "safe". This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested." Section 9 of the HTTP 1.1 Specification further gives credence that GET is special with regards to caching. Within this section it is clear that responses to PUT and DELETE are not cacheable. Further, by default, responses to POST are not cacheable. However, the response to an HTTP GET is cacheable as long as it meets the caching requirements outlined in Section 13 of the HTTP 1.1 Specification. Supporting Client Caching with a REST Service As you may have gathered from the previous sections, caching is prevalent on the web. When you are authoring RESTful services, you need to be mindful of this. It is in your best interest to take advantage of caching, where possible, while further providing the clients to your service the appropriate hints on whether or not to cache and for how long. In this example, I will use IE as my client, as it is both prevalent and exhibits typical behavior. I will start with illustrating what the behavior is if you do not provide any caching information. Default Behavior Below is the default implementation of my GetWine service. As you can see, there are no cache-related HTTP headers set in the implementation. I am again going to use Fiddler to view the traffic over HTTP, as I issue 3 consecutive GET requests to this service from IE. Below are screenshots of IE, as well as Fiddler after 3 requests: As you can see, only one request went to the server. Given no other information from the original response, IE cached it. The 2 subsequent requests were sourced from this cache. Below, I have included the raw request and response below for reference: No Cache In certain cases, you may want to direct your client not to cache any responses. For example, the data may be updated very often or the non-tolerance for stale data may preclude you from allowing the caching of responses. In this case, you need only add one or two HTTP headers to let your clients know not to cache. The first header to set is the Cache-Control to no-cache. In order cover your bases, it is probably best to set a second header, Pragma to no-cache, as well. This will cover you for HTTP 1.0 caches that do not implement Cache-Control. WCF offers a helper class called WebOperationContext that provides convenient access to the properties of the response (and the request, as well). You can use this class to set the Cache-Control and Pragma HTTP headers. See the updated implementation code below: I used IE to again make 3 successive calls. Below you can see the Fiddler screenshots: You will notice that this time all 3 requests were sent to the server. The response from the original (and each subsequent) call shows the Cache-Control and Pragma headers. The IE client rightfully abided by these directives and did not cache the response. Expiration Hints In the previous code, we told the client not to cache. While in certain cases this is the right thing to do, it does adversely effect the scalability of our solution. Perhaps we want to let our clients cache the response, but we want to give them a hint of how long the cache is likely to be good for. We can do this by setting an Expires header to a date and time. A date and time in the past essentially invalidates the cache. A date and time in the future provides guidance to the client that their cache is likely good until that date and time. In the code below I set the Expires HTTP header to a time of 10 seconds in the future (GetFormattedCacheTime is simply a helper method I wrote): In this demo, I make 3 successive calls within 10 seconds. I then wait 12 seconds and make another 2 successive calls. Here are the Fiddler screenshots: You will notice that, although I made 6 calls, only 2 made it to the server and 4 were sourced from cache. I made 3 calls before the cache expired. The fourth call went to the server and re-established the cache for 10 more seconds. Conditional GET You might have noticed the use of some traditionally weak words in the description of the previous example. I noted that we want to view the client a "a hint of how long the cache is likely to be good for". The key words here are hint and likely. We are not guaranteeing that the cache will be good. Rather, we are stating that it will likely be good. The only way to ensure that the cache is good is to make a call to the server and check. However, if we make a call to the server, what is the benefit of the cache? We can look to some of the verbage in Section 13 of the HTTP 1.1 Specification for the answer. The second paragraph begins with: "Caching would be useless if it did not significantly improve performance. The goal of caching in HTTP/1.1 is to eliminate the need to send requests in many cases, and to eliminate the need to send full responses in many other cases." As you might have gathered, a safer, albeit less performant, solution is to issue what is known as a Conditional GET. In the case where the client cannot tolerate a stale cache and a "hint" is not good enough, they may issue a request to the server, if the cache is still fresh, the server will send a 304 "Not Modified" response with no entity body. The client can then safely source the data from it's cache. Another scenario is the case where the client abides by the caching hint, but the cache is expired. As opposed to simply throwing away the cache, the client can issue a Conditional GET to see if the cache is still good. I am going to discuss a Conditional GET from the perspective of authoring a service that supports Conditional GET. There are 2 HTTP headers that are commonly used for Conditional GETS: If-None-Match and If-Modified-Since. As a service author, if you want to support Conditional GET, you should support both of these headers. The If-None-Match header set by the client contains an entity tag that the server can compare against it's entity tag to see if the data has changed. Essentially, an entity tag represents the state of the entity at a point in time. Some folks use a hash of the data as their entity tag, while others maintain a GUID for each state of the data. In the second case, every time the data is updated a new GUID is generated to represent that state. The server checks the provided If-None-Match header value against it's entity tag and if they are the same, it sends a 304 "Not Modified" with no entity body. If there is no match, the server sends the full response (including the entity body). The If-Modified-Since header set by the client contains the date that the data in the client cache was last updated. The server checks this date against the date it has for when the data was last modified. If the data has not been modified since the date the server is provided, the server sends a 304 "Not Modified" with no entity body. If the data has been modified since that date, the server sends the full response (including the entity body). You are now probably asking yourself where the client gets the data it sends in the If-Modified-Since and and If-None-Match headers. Before I answer that, one thing needs to be clear: a Conditional GET can only be called if a previous request was made. This makes sense in that we are calling to the server to see if our cache is valid. We would have to have received a response previously that we could cache. With that said, the client headers are sourced from the server's response to the previous request. The client would use the ETag header from the response for the If-None-Match header and the Last-Modified for the If-Modified-Since header. The following is a step-by-step on how the whole process works: - The client makes the original request to the server with no special HTTP headers
- The server sends a 200 response code along with either an ETag and a Last-Modified HTTP header
- The client makes a subsequent request to the server, passing the value from the ETag as an If-None-Match header and the value from the Last-Modified for the If-Modified-Since header.
- The server checks to see if the data has been modified since the date provided in the If-Modified-Since header. If it has, the server sends the full response, complete with the entity body.
- The server checks to see if the If-None-Match header matches it's entity tag. If it does not match, the server sends the full response, complete with the entity body. If it does match, the server sends a 304 "Not Modified" status code to the client and suppresses the entity body. The client is thus notified that it can source the data from it's cache.
Let's take a look at how we can support this with code. Here is the updated code: As you can see, for every request, the service sets an ETag and Last-Modified header. You will also notice that the service calls 2 helper methods to check the headers against the server data. Here are the helper methods: The helper methods simply call the SupressEntityBody method on the OutgoingWebResponseContext and set the HTTP status code to 304 "Not Modified" if the ETags do not match or the data has been updated since the date provided in the HTTP header. You may be wondering how I implemented the ETag and LastModifiedDate for the Wine entity in the first place. What I chose to do is to maintain them as columns within the database table. I chose to use a trigger to maintain them. Essentially if a record is inserted the ETag and LastModifiedDate are set. If a record is updated, a new ETag is generated and the LastModifiedDate is reset. Here is a screenshot of the table and the trigger: Let's run our same example again, using Fiddler to illustrate the traffic. I will again make 3 successive calls, wait 12 seconds and make 3 more successive calls. Remember that I still have the Cache-Control header set to 10 seconds in the future. Here is the HTTP traffic: The first request and response looked like this: Notice that the response to the original request includes a Last-Modified header and an ETag. After the cache went stale (the 12 second wait), the request and response looked like this: Notice that the If-Modified-Since and If-None-Match headers were sent. If you look at the previous response, you will note that the values match between the ETag and If-None-Match and between the Last-Modified and the If-Modified-Since. Because the data was not modified, the dates matched, as well as the entity tags. The service then rightfully returned a 304 and suppressed the entity body. IE then was free to source the data from it's cache. Also note that the cache was then re-established fresh for another 10 seconds. I hope this post was helpful in understanding how and why to support caching and Conditional GETs in your RESTful services with WCF. It is important to also note that you can take advantage of the ASP.NET caching infrastructure from your WCF service. You simply need to turn on Asp.NET compatibility mode. Specifically all the caching specific APIs at HttpContext.Current.Response can be used from your WCF service. ******* Click here for a Screencast Illustrating Supporting Caching and Conditional GET *******
|
-
(click here for an index of the complete series, as well as supporting screencasts) (click here for a screencast illustrating "Controlling the URI") Resources in REST Arguably, the most fundamental concept in REST is that of a resource. It is best to think of it as a conceptual representation of an entity or entities. Blah, blah, blah, conceptual, blah blah blah, representation, blah, blah, blah entity. What does all of that mean anyway? Think of it this way, if you can create a link to it, it is a resource. Now what does conceptual representation mean? Essentially, it means that the resource is an abstraction. It is not the underlying entity itself, rather a mapping to that entity... and that mapping may change. Whoa, the mapping may change? How is that? Consider the example that you wanted to expose the top 10 best selling books at your online store. The resource would be those top 10 best selling books. It is clear that those would change over time. The need for the abstraction should be clear. In our previous example, we don't really care what the back end was that stored the top 10 best selling books. It was, more than likely, a database system. It could have been Microsoft Sql Server, or it could have been Oracle or Informix or something altogether different. We don't really care because we are not interested in the entity itself, rather the representation. If we were to switch back ends out after a period of time, it would be meaningless to us. The representation would be unaffected (although, as we argued earlier the representation might have changed). Further, imagine the complexity of the system where we were not mapping to the concept, but to the actual underlying entities. URIs in REST The way in which you identify a resource is through a URI. Section 6.2.1 of Fielding's dissertation points out that "The definition of resource in REST is based on a simple premise: identifiers should change as infrequently as possible." He further states that "authors need an identifier that closely matches the semantics they intend by a hypermedia reference..." This points out the importance of the structure of the URI. Many folks try to create what I refer to as "Hackable" URIs. These are URIs that are easily remembered and whose structure and semantics are clear enough to manipulate. Take for instance the following URI: 'http://www.someuri.com/Product/Flange'. It is pretty clear the the structure is: the http scheme and the hostname ('http://www.someuri.com') followed by 2 segments: Product and ProductName. A template for this might look like the following: 'http://www.someuri.com/Product/{ProductName}' where {ProductName} represents a variable. You might argue that this is bad design, in that if the name of the product changes, so must the URI and that clearly violates the premise that a resource is based on. Another approach would be to use the ProductID as the final segment. I'll leave that up to you. What I want to discuss in this post is what your options are for controlling the URI, assuming you have already designed how the URI should look. The approach of this Post If you have ever seen 'What About Bob' with Bill Murray, you will understand the following reference (if you haven't, close your notebook, go to Blockbuster, rent 'What About Bob', come home, put it in the DVD player, open a beer - if that is your thang - and watch it). We are going to take baby steps to controlling the URI. I am going to start by outlining the default URI behavior when using the webHttpBinding along with the webHttp endpoint behavior. I will then show you what your options were in manipulating the URI structure via the UriTemplate. I'll then move on to some additional functionality available in SP1. Lastly, I'll illustrate how you can take complete control over the structure of the URI using IIS7 and the Url Rewrite Module. The Sample Below you will see the service contract and operation contract of our sample service. As you can see, the operation is named GetWine and takes in a string of wineID. You will also note that I have decorated the operation contract with a WebGetAttribute, making this callable via an HTTP GET. I have omitted the implementation, as well as the configuration, as they adds no value to this discussion. [ServiceContract] public interface IRESTWineService { [OperationContract] [WebGet(ResponseFormat = WebMessageFormat.Xml)] WineData GetWine(string wineID); } One other point to make is that address to this web is http://localhost/RESTControllingUri. The Default Behavior It may not be clear, but this operation does have a unique URI associated with it. The default URI scheme is the path to the *.svc file, followed by a segment with the operation name, followed by querystring parameters for each operation argument. The following represents the default structure of the URI: [protocol]://[hostname][:port][/path][/svc filename][?argument1=value1[&argument2=value2 ...]] In the case of our example, it looks like this: The UriTemplate When we released the webHttpBinding and the webHttp endpoint behavior in WCF 3.5, we provided the ability to define a UriTemplate. Aptly named, the UriTemplate allows you to define a template for a set of URIs that follow a similar structure or pattern. The UriTemplate has 2 parts: the path and the query. The path is a series of segments. The segments can contain literals, parameters or variables and wildcards. Here are some examples: - literal: /products
- literal: /products/sportinggoods
- literal and variable: /product/{productId} (Where productId is a parameter that is mapped to an operation argument)
- wildcard: /products/*
The query contains name-value pairs that represent the querystring parameters collection. Here are some examples: - literal in query: /products?type=json
- variable in query: /products?index={index}&pageSize={pageSize}
You can set a UriTemplate through code or you can pass it as a parameter to a WebGetAttribute or WebInvokeAttribute. Here is an example of the latter: [OperationContract] [WebGet(UriTemplate="/products?index={index}&pageSize={pageSize}")] WineData GetProducts(int index, int pageSize); * One important thing to note. In the current release (SP1), if your variables are part of the query, such as above, we will do simple type conversions for you. So, in the previous example, note that GetProducts takes in integers, but clearly the querystring parameters are strings. We will take care of this simple conversion for you. However, if the variables are in the path, we will not. There is no reason for this, other than it was done for the query, not the path. It is my understanding that support for type conversions in the path is on the list for a future release. Just thought you might like to know. As you can see, the UriTemplate is useful in controlling the URI. However, there are limitations. As my might have noticed, the template is defined for the path and querystring after the path to the svc file. It does not provide any help for manipulating that portion of the URI. (see this article on MSDN for more information on UriTemplates) Given this information, we could define a UriTemplate to clean up the URI for our GetWine operation. Perhaps we want the path to be something like this: wine/17 (where 17 is an actual wine id). We could simply change our code to look like this: [OperationContract] [WebGet(UriTemplate="wine/{wineID}", ResponseFormat = WebMessageFormat.Xml)] WineData GetWine(string wineID); The call would look like this: UriTemplate Enhancements in WCF 3.5 SP1 The WCF team added some enhancements to UriTemplates when Visual Studio 2008 SP1 (and Fx 3.5 SP1) was released. These included support for default values for parameters in paths and compound template segments. It is simple to define a default value. Simply set the variable to the default value with an '='. Remember that you cannot assign defaults to variables in the query portion of the template. Here is what our example would look like, setting the default value of wineID = 22: [OperationContract] [WebGet(UriTemplate="wine/{wineID=22}", ResponseFormat = WebMessageFormat.Xml)] WineData GetWine(string wineID); You can now omit the id segment of the URI like this: Compound template segments allow you to combine both variables and literals within one segment. If you are familiar with ADO.NET Data Services, you will note that the ID for entities uses compound template segments. The IDs look like this: Entity(id). ADO.NET Data Services runs on WCF and the webHttpBinding, so you might rightly assume that this functionality was added for this purpose. However, you can take advantage as well. If I wanted to implement a similar ID scheme, it would look like this: [OperationContract] [WebGet(UriTemplate="wine({wineID})", ResponseFormat = WebMessageFormat.Xml)] WineData GetWine(string wineID); Here is what it looks like: Completely Controlling the URI All of the examples we have seen so far only allow us to template the URI after the path to the svc file. If you want to take full control of the URI, you have to do some kind of URL Rewriting. There are a variety of means to accomplish this. ScottGu's blog post Tip/Trick: Url Rewriting with ASP.NET illustrates 4 approaches, including implementing HttpModules, using Request.PathInfo and taking advantage of 3rd party ISAPI filters. John Flanders' blog post Using WCF WebHttpBinding and WebGet with nicer Urls illustrates how you can build an HttpModule with IIS7 for URL Rewriting. In this post, I will illustrate how you can take advantage of the new URL Rewrite Module for IIS 7.0 CTP1. This module allows you to simply add rules that define pattern matches (either Regular Expressions or WildCards) and rewrite to a specified URL pattern. Actually the module does more than that. To see the full functionality, check out Bill Staples' blog post on the subject. For our purposes, we simply want to define a pattern for the incoming URL using Regular Expressions. This pattern will represent what we want the "Hackable" URI to look like. We will then take this URI and rewrite it to the actual path that we defined earlier ([protocol]://[hostname][:port][/path][/svc filename]). An example will probably best illustrate our goal and how we can accomplish it. Taking the last example we had, the URI looked like this: As my annotations clearly point out, we may not want the ".svc" as part of our URI. That is really a technical implementation detail and does not really have a place in the "hackable" URI. So, for this simple example I will illustrate how we can use the Url Rewrite Module to remove the svc extension. Here is the process: - Make sure you have installed Vista SP1
- Make sure you have installed the CTP of the Rewrite Module
- Open IIS 7
- Navigate to your application and dbl-click on the URL Rewrite Module Icon
- Click on add rule in the upper right hand corner
- Fill out the form with the following data (we will describe the patterns in detail in a moment):
- Name: Remove Svc Extension (or any name you want)
- Requested URL: Matches the pattern
- Using: Regular Expressions
- Pattern: ^([0-9a-zA-Z\-]+)/([0-9a-zA-Z\-\.\/\(\)]+)
- Ignore Case: Checked
- Action Type: Rewrite
- Rewrite URL: {R:1}.svc/{R:2}
- Append QueryString Checked
- Click Apply
You are ready to go. But before we test it, I want to dissect the pattern we used for matching. The match pattern was: ^([0-9a-zA-Z\-]+)/([0-9a-zA-Z\-\.\/\(\)]+) Here is the breakdown of the pattern: - The "^" indicates the beginning of the line or string
- The parenthesis "()" allow you to define a numbered capture group, meaning that you can reference everything inside the parens later by index. In our example, we have 2 numbered capture groups 1 and 2. You refer back to these later with the syntax: {R:1} and {R:2}
- The pattern [0-9a-zA-Z\-]+ indicates any character in 0-9, lowercase a-z, uppercase A-Z or "-", one or more repetitions.
- The / is a straight match to a slash "/". In other words, the first named capture group will capture 0-9,a-z, A-Z and "-" prior to the first "/" and you will be able to reference them with {R:1}
- Then we have another named capture group
- The pattern [0-9a-zA-Z\-\.\/\(\)] matches any character in 0-9, lowercase a-z, uppercase A-Z or "-", ".", "/", "(", ")", one or more repetitions.
The rewrite URL is simply everything in the first named capture group (everything prior to the first /) which should be the svc file, without the svc extension. We then append an svc extension and a slash. We then append everything in the named capture group. Let's see it in action. Here is our new svc-less request:  Eliminating the ".svc" extension is just the tip of the iceburg. Hopefully, you can see how you can take full control of the URI.
|
-
As some of you know, I am in the midst of a blog series on REST in WCF. Further, I have been hard at work on a series of screencasts on the same subject (in conjunction with Ron Jacobs). My colleague Tim Heuer relayed to me that I didn't have a single post that we can point a person to that provides links to all of the posts and screencasts. I will keep this post updated with all of the info: Blog Series: Screencast Series: Regards, Rob
|
-
I recently loaded the Visual Studio 2008 SP1 Beta and have been playing around with all of the new features. Picture a 6 year old on Christmas Morning. The major difference being that I did not wake up my wife to ask her if it was ok to download it yet. Well, I digress. If you played with ADO.NET Data Services in the olden days (like 9 months ago) when it was Astoria, you will remember that it had a dependency upon the ADO.NET Entity Framework. It would only expose Entity Data Models from the EF. Well, with the latest CTP, as well as the new SP1, that limitation has been lifted. You now have the ability to expose any data source, as long as there is a class that contains properties that implement IQueryable<T> (IUpdatable is required for writebacks). So, given these new capabilities, I thought I'd start by exposing a LINQ To SQL data source. I ran through the usual steps: 1. First, used the LINQ To SQL Template and designer to create a LINQ To SQL Data Source. This is pretty straightforward: 2. I then added the required tables to the designer (see below). I am using the Coho database in this example. I have made it available here. Notice that I added Wines2Accolade and Accolade tables. The Wines2Accolade table creates a many-to-many relationship between the Wines table and the Accolades table. Using the ADO.NET Entity Framework, we would simply model this table away in the entity model. If you had and instance of a wine, you could navigate to Accolades via an Accolades property. The EF allows for such higher order modeling. However, LINQ to SQL does not. Essentially, it allows you to create a 1:1 model between tables and objects. I am willing to live with that behavior, so I move on... 3. I then use the ADO.NET Data Services Template to add a new data service: 4. I then set the data source for my Data Service: 5. I should be done and should be able to test my new services. So that is what I did. I opened a browser and navigated to my svc file. What I saw was not the friendly list of all of my entity sets that I had exposed. Rather, I got an error. Not only that, ADO.NET Data Services swallowed my exception. That is by design. If you have looked into REST, you know that HTTP status codes act as the error handling mechanism in REST. 6. However, when developing, such as now, I have a need to see the actual error (without looking through log files). So my next step (thanks to Phani Raj on the Astoria Team) was to decorate my Data Service class with a ServiceBehaviorAttribute and pass in IncludeExceptionDetailInFaults = true: [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class CohoDS : DataService<CohoDbModel.CohoDbEntities> When I refreshed the browser, I saw the actual error: 7. What I found out was that theWines2Accolades table was causing issues. As it turns out, ADO.NET Data Services requires that each entity have a key defined. By default, a dual key in a many-to-many table is not supported. What I needed to do was to give it a hint. In other words, I needed to explicitly tell ADO.NET Data Services that there is a dual key. I do this by decorating the appropriate type (in this case my Wines2Accolade type) with a DataServiceKey attribute, passing in either a single key name or a string array containing all of the keys. I could have simply decorated the Wines2Accolade type in the generated CohoLTS.designer.cs file, but my change would be overwritten each time I regenerated the model. What I decided to do was to add a partial Wines2Accolade class in a separate file (Wines2Accolade.cs): using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Data.Services.Common; [DataServiceKey(new string[] { "WineID", "AccoladeID" })] public partial class Wines2Accolade { } As you can see, I simply delcared the dual key. 8. Lastly, I was able to test (successfully): Hope that helps...
|
-
In Thursday's post (Part VII), I illustrated how to implement insert/update functionality in a HI-REST service operation. In this post, I will illustrate implementing a delete. Unlike the previous post, there is little debate how to implement a RESTful delete. Further, the lessons we learned in the previous few posts will make implementing the delete trivial. I'm going to start motoring through...
The Service Contract
[OperationContract]
[WebInvoke(Method = "DELETE",
UriTemplate = "product/{productName}")]
void DeleteProduct(string productName);
You will notice that again, I am decorating my operation contract with the WebInvokeAttribute. Remember that this is the attribute that allows me to coerce the HTTP verb to any verb but GET (actually you can use WebInvoke for GETs, as well). All you need to do is set the Method parameter to the HTTP Verb you want. In this case, it is DELETE.
The next thing you will notice is that there is no RequestFormat or ResponseFormat set here. We are neither passing any payload or returning anything. The last thing to notice is that we are again passing in a UriTemplate. Requests with URIs that match this template (and HTTP Verbs that match the verb set in this method) will be routed to this operation. The UriTemplate allows you to declare variables by surrounding them with curly braces. If they are named appropriately, the values will be passed to the appropriate parameter of the service operation. There are more details about UriTemplates in Part V of this series.
The Uniform Interface
You may have heard the term 'Uniform Interface' with regards to REST. If you think of a typical RPC API, there are endless method names possible: GetProducts, GetProductsAll, GetMyProducts, DeleteProducts, DeleteProduct, DeleteMyProduct, etc. With REST, the API is defined by the HTTP verbs, typically GET, PUT, DELETE and POST. The scoping is taken care of by the URI. What does that mean? Take, for example, our DeleteProduct method in an RPC API. It may take in a product ID as a parameter. The signature might look something like this: void DeleteProduct(int productID). The scope of what is deleted is the parameter productID. With our RESTful API, the scope of what is deleted is part of the URI. In the case of our delete, it is defined in the segment of the URI containing our productName variable. In the uniform interface, the intent of what we want to do is the verb, while the scope is found in the URI.
The Implementation
public void DeleteProduct(string productName)
{
WebOperationContext ctx = WebOperationContext.Current;
try
{
using (CatalogDataContext catalog = new CatalogDataContext())
{
Product product = catalog.Products.SingleOrDefault(p => p.ProductName == productName);
if (product == null)
{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
return;
}
catalog.Products.DeleteOnSubmit(product);
catalog.SubmitChanges();
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.OK;
return;
}
}
catch
{
ctx.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.BadRequest;
return;
}
}
Most of this code is just plain vanilla LINQ to SQL. However, you will note here that we are again setting the status code for failure and success, essentially alerting the caller to whether the delete was successful or not.
Consuming our Service with an AJAX Client
I am going to simply wire our delete functionality into our AJAX management application:
When the 'Delete Product' button is clicked, we simply get the name of the product to delete from the listbox. We then create the URI, containing the product name to delete. Lastly, we simply call into the restInvoke AJAX method I wrote and introduced in an earlier post.
function delProduct_clicked()
{
var productName = $get('productList').value;
var proxy = new Sys.Net.WebServiceProxy();
var url = "../RESTCatalogService.svc/product/" + productName;
proxy.restInvoke(url, "DELETE", null, "delProduct_clicked", ProductDeletedEventHandler, ErrorEventHandler);
}
function ProductDeletedEventHandler(result) {
clearUI();
loadProducts();
alert('Product Deleted');
}
Please see the Starter Solution for the implementations of clearUI and loadProducts, etc. That just about does it for this example. In future posts I will illustrate calling our HI-REST API from a Silverlight 2 client, as well as some other, more advanced topics like fully controlling our URI (i.e. getting rid of that blasted .svc from the URI).
Regards...
|
-
Introduction In parts I - VI, I illustrated exposing fetch functionality in a LO-REST, AJAX-Friendly manner, as well as in a HI-REST manner. I further illustrated how to consume both via an AJAX client. In this post I am going to discuss and illustrate how to implement insert and update functionality in a HI-REST manner. If you remember back to the point I made in Part I of this series, I suggested that there are many definitions of REST and put forth the idea of a REST continuum. This is a healthy manner to discuss REST, in that it allows for many interpretations. If I have learned anything over my years of coding is that there are always many ways to solve a business problem with code. Not only are there many ways, but there are many correct ways. I re-introduced the REST continuum in the last paragraph because I am now about to go down a certain path in my RESTful implementation. If you ardently disagree with my implementation and thought process, I trust that you will at least respect that it does fall on the continuum (as always, I would love your feedback, as well). Modeling Insert and Update the HTTP way Most folks I talk to view the HTTP POST as in insert and PUT as an update. However, I have met more than a few that see things the other way around. I have even seen some folks pull out the verb PATCH. Part of the challenge with this seemingly unanswerable debate is that HTTP was developed to deal with documents and when we discuss these verbs we are usually thinking about data (rows and columns). At least that is my thought. Well, if I am going to provide an implementation in this post, I am going to have to make a decision on how I want to handle the HTTP verbs. To start, I am going to need a bit of help from the HTTP specs. Here are some excerpts from POST and PUT: PUT- "The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request." (1) POST - "The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions: - Annotation of existing resources; - Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles; - Providing a block of data, such as the result of submitting a form, to a data-handling process; - Extending a database through an append operation."(1) The way I read this, in the event that a resource does not exist, a PUT will perform an insert. In the event that a resource already exists at the designated URI, the resource is modified (or updated). With regards to a POST, it is designed for operations that I would describe as append. Again, by simply writing this passage, I have opened myself up to endless debate. There are those that will state that if the resource exists, what really is happening is a delete and an insert and that is != to an Update. In my (probably naive) opinion, as long as you have a logical basis for your API design and you are consistent, you will be able to defend its efficacy. So, given this explanation, I have decided that I will use PUT for both insert and update in my implementation. The Service Contract [OperationContract] [WebInvoke(Method="PUT", UriTemplate = "product/{productName}", RequestFormat = WebMessageFormat.Json)] void PutProduct(string productName, ProductData productData); This service contract is quite similar to the service contract for our HI-REST GET. There are, however, a few differences: WebInvoke As opposed to decorating our service operation with a WebGetAttribute, we decorate it with a WebInvokeAttribute. The default HTTP VERB associated with this attribute is POST. As I pointed out, I want to use a PUT. The WebInvokeAttribute is the attribute we use for every action other than GET. Method="PUT" The WebInvokeAttribute takes a Method parameter that allows you to explicitly set the HTTP verb. As I stated before, if you do not set anything, the default is POST. We want to use a PUT, so it is set accordingly. No ResponseFormat I have a void return type, so there is no need to set the response format. RequestFormat=WebMessageFormat.Json We are sending a payload with the request. Here I am setting the format to JSON or JavaScript Object Notation. If you look back to Part VI of this series, you will see that the restInvoke AJAX function I wrote assumes JSON serialization, so it is in line with this decision. The Implementation public void PutProduct(string productName, ProductData productData) { WebOperationContext ctx = WebOperationContext.Current; System.Net.HttpStatusCode status = System.Net.HttpStatusCode.OK; try { using (CatalogDataContext catalogCtx = new CatalogDataContext()) { Product product = catalogCtx.Products.SingleOrDefault( prod => prod.ProductName == productName); if (product == null) { product = new Product(); catalogCtx.Products.InsertOnSubmit(product); status = System.Net.HttpStatusCode.Created; } product.ProductDescription = productData.Description; product.Price = productData.Price; product.ProductImage = productData.ProductImage; product.ProductName = productData.ProductName;
| |
|