ÿþ<?xml version="1.0" encoding="utf-8"?> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>UGData Tutorial</title> <!--<base href="http://exyus.com/articles/ugdata/" />--> <style type="text/css"> table.matrix { border: 1px solid black; } table.matrix th, table.matrix td { color:black; background-color:silver; padding: .2em; } table.matrix td { background-color:#e6e6e6; vertical-align:top; } div.section { margin: 1em 0; } div.abstract, pre { border:1px solid black; background-color:#e6e6e6; padding: .3em; margin-bottom: 1em; } div.abstract { margin-top: 1em; } blockquote { border:1px solid black; background-color:silver; color:black; padding: .3em; font-style:italic; } code { font-style:normal; } h1,h2,h3 { margin: 0; } p { margin-top:0; } address { font-size:smaller; } img { border:no; float:right; border: 1px solid black; } blockquote h4 { margin:0; padding:0; } blockquote p { margin:0; margin-bottom:.5em; padding:0; } </style> </head> <body> <h1>UGData Tutorial</h1> <address class="doc-info"> Date: 2008-03-20<br/> Author: <a href="http://amundsen.com/blog" title="mca blog" target="_blank">Mike Amundsen</a> </address> <div class="abstract"> <h2>Abstract</h2> <p> In this tutorial you'll learn how to build database-backed REST-ful applications using the <a class="exyus-site">exyus</a> engine. You'll learn how to build stored procedures for SQL Server that output XML for use with the <a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Resources/XmlSqlResource.cs" title="XmlSqlResource.cs"?>XmlSqlResource</a> class in <b>exyus</b>. You'll learn how to use the <b>XmlSqlResource</b> base class to define your own database-backed HTTP Resource. And you'll learn how to use XSL transforms and XSD Schema documents to control the processing of inbound client requests and generating outbound server responses. Finally, you'll learn how you can use simple HTML FORMs to support not just GET and POST, but also PUT and DELETE HTTP methods. </p> <p> You can test the online version of the <a href="http://exyus.com/xcs/ugdata/" title="UGData" target="_blank">UGData</a> application.</a> </p> <p> Feedback and comments are welcome via the <a class="exyus-group">Exyus Discussion Group</a>. </p> </div> <div class="menu"> <h2>Sections</h2> <ul> <li><a href="#Introduction" title="Introduction">Introduction</a></li> <li><a href="#StoredProcedures" title="SQL Stored Procedures">SQL Stored Procedures</a></li> <li><a href="#UGDataResource" title="UGData Resource">UGData Resource</a></li> <li><a href="#XHTMLRepresentation" title="XHTML Representation">XHTML Representation</a></li> <li><a href="#UGDataUpdateAndDelete" title="UGData Update and Delete">UGData Update and Delete</a></li> <li><a href="#Summary" title="Summary">Summary</a></li> <li><a href="#Author" title="Author">About the Author</a></li> </ul> </div> <div class="section"> <h2><a name="Introduction" title="Introduction">Introduction</a></h2> <p> <a href="" target="_blank"> <img src="" title="" alt="" /> </a> This article covers the details of building a simple database-backed HTTP/REST-compliant Web application using the <a class="exyus-site">exyus</a> web engine. For this application, <a href="http://www.microsoft.com/sql/default.mspx" title="SQL Server 2005">SQL Server 2005</a> is used as the database engine. The process of building a database-backed REST-ful Web application is not very different from bulding file-backed REST-ful applications. The only real differences are the details for defining the data table and stored procedures in the database. Also, the base class you use (<a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Resources/XmlSqlResource.cs" title="XmlSqlResource.cs"?>XmlSqlResource</a>) has some additional hooks for processing incoming requests and outgoing responses. These additional hooks give you more power when interacting with the database. </p> <p> Another goal of this tutorial is to show you how to implement the full range of HTTP methods (<code>GET, PUT, POST, DELETE</code>) for a Web browser using HTML FORMs without any client-side scripting or custom HTTP Headers. Since HTML FORMs only support <code>GET</code> for reads and <code>POST</code> for writes, this application will need to resort to 'overloading' the <code>POST</code> method. The pattern used for overloading is to create a new URI that includes the actual method you wish to execute (i.e. <code>/resource/1;put</code>). </p> <blockquote> <h4>NOTE:</h4> <p> For brevity, this article focuses only on an XHTML representation for the resource. The actual online version of the <a href="http://exyus.com/xcs/ugdata/" title="UGData" target="_blank">UGData</a> application</a> also supports other representations (for example, <code>application/json</code> or <code>application/xml</code>, etc.). </p> </blockquote> <a href="#top" title="top">top</a> </div> <div class="section"> <h2><a name="StoredProcedures" title="SQL Stored Procedures">SQL Stored Procedures</a></h2> <p> <a href="images/stored-procedures-full.png" target="_blank"> <img src="images/stored-procedures.png" title="stored procedures" alt="stored procedures" /> </a> The first step in building this application is defining and implementing the data model in SQL Server 2005. The application will use just one data table and five stored procedures. The table will will hold some basic information about User Group Members: id, firstname, lastname, birthdate, and experience level. The stored procedures will handle the read, list, add, update, and delete tasks for the table. </p> <p> Below is the T-SQL code that will create the needed table: </p> <pre class="code"> USE [exyus_samples] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[UGData]( [id] [int] IDENTITY(1,1) NOT NULL, [firstname] [nvarchar](50) NOT NULL, [lastname] [nvarchar](50) NOT NULL, [birthdate] [datetime] NOT NULL, [experience] [nvarchar](25) NOT NULL, CONSTRAINT [PK_UGData] PRIMARY KEY CLUSTERED ( [id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] </pre> <p> Authoring the stored procedures is the next step. The <a class="exyus-site">exyus</a> engine takes advantage of the XML support in SQL Server 2005. In fact, the base class that will be used for this example (<a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Resources/XmlSqlResource.cs" title="XmlSqlResource.cs"?>XmlSqlResource</a>) requires that all output from SQL Server be returned as a stream of XML. For that reason, all the output from these stored procedures will be an XML stream. </p> <p> Also, <a class="exyus-site">exyus</a> works best when clear error conditions are returned from the database calls. That means the stored procedure will include basic error checking and use of the <code>rasierror()</code> method in SQL Server. </p> <p>Below is the code that returns an XML list of the user group members from the UGData data table:</p> <pre class="code"> USE [exyus_samples] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Description: return list from ugdata table -- ============================================= ALTER PROCEDURE [dbo].[ugdata_list] AS BEGIN SET NOCOUNT ON; if(select count(*) from ugdata)=0 begin raiserror('records not found',16,1) return end else begin select id as '@id', firstname, lastname, birthdate, experience from ugdata order by id asc for xml path ('member'), root('member-list') end --endif END </pre> <p> Notice the last line of the procedure (<code>for xml path ('member'), root('member-list')</code>). That is the line that formats the output as an XML stream. Below is an example of the output from the <code>ugdata_list</code> procedure: </p> <pre class="code"> &lt;member-list&gt; &lt;member id="117"&gt; &lt;firstname&gt;Mary&lt;/firstname&gt; &lt;lastname&gt;Michelson&lt;/lastname&gt; &lt;birthdate&gt;1989-01-01T00:00:00&lt;/birthdate&gt; &lt;experience&gt;CallMeRoy&lt;/experience&gt; &lt;/member&gt; &lt;member id="118"&gt; &lt;firstname&gt;Marvin&lt;/firstname&gt; &lt;lastname&gt;Marrison&lt;/lastname&gt; &lt;birthdate&gt;1979-01-01T00:00:00&lt;/birthdate&gt; &lt;experience&gt;None&lt;/experience&gt; &lt;/member&gt; &lt;member id="119"&gt; &lt;firstname&gt;Mandy&lt;/firstname&gt; &lt;lastname&gt;Marvinson&lt;/lastname&gt; &lt;birthdate&gt;1969-01-01T00:00:00&lt;/birthdate&gt; &lt;experience&gt;ICanHitF1&lt;/experience&gt; &lt;/member&gt; &lt;member id="120"&gt; &lt;firstname&gt;Mark&lt;/firstname&gt; &lt;lastname&gt;Mandyville&lt;/lastname&gt; &lt;birthdate&gt;1959-01-01T00:00:00&lt;/birthdate&gt; &lt;experience&gt;WhatIsREST&lt;/experience&gt; &lt;/member&gt; ... &lt;/member-list&gt; </pre> <p> The remaining stored procedures (<code>ugdata_read, ugdata_add, ugdata_update, ugdata_delete</code>) are shown below. Note that the add and update routines also call the read procedure to return the appropriate row of data. </p> <pre class="code"> -- ============================================= -- Description: add new ugdata row -- ============================================= CREATE PROCEDURE [dbo].[ugdata_add] @firstname nvarchar(50), @lastname nvarchar(50), @birthdate datetime, @experience nvarchar(25) AS BEGIN SET NOCOUNT ON; -- create record insert into ugdata (firstname,lastname,birthdate,experience) values (@firstname,@lastname,@birthdate,@experience) -- return new record exec ugdata_read @id=@@identity END -- ============================================= -- Description: delete a row from ugdata -- ============================================= ALTER PROCEDURE [dbo].[ugdata_delete] @id int AS BEGIN SET NOCOUNT ON; if(select count(*) from ugdata where id=@id)=0 begin raiserror('id not found [%i]',16,1,@id) return end else begin delete from ugdata where id=@id end --endif END -- ============================================= -- Description: return single row from ugdata -- ============================================= ALTER PROCEDURE [dbo].[ugdata_read] @id int AS BEGIN SET NOCOUNT ON; if(select count(*) from ugdata where id=@id)=0 begin raiserror('id not found [%i]',16,1,@id) return end else begin select id as '@id', firstname, lastname, birthdate, experience from ugdata where id=@id for xml path ('member') end --endif END -- ============================================= -- Description: update existing ugdata row -- ============================================= ALTER PROCEDURE [dbo].[ugdata_update] @id int, @firstname nvarchar(50), @lastname nvarchar(50), @birthdate datetime, @experience nvarchar(25) AS BEGIN SET NOCOUNT ON; if(select count(*) from ugdata where id=@id)=0 begin raiserror('id not found [%i]',16,1,@id) end else begin update ugdata set firstname=@firstname, lastname=@lastname, birthdate=@birthdate, experience=@experience where id=@id end --endif exec ugdata_read @id END </pre> <p> Once you have created the UGData table and the five stored procedures, you are ready to move on to defining the resource class in C#. </p> <a href="#top" title="top">top</a> </div> <div class="section"> <h2><a name="UGDataResource" title="UGData Resource">UGData Resource</a></h2> <p> <a href="images/ugdata-resource-full.png" target="_blank"> <img src="images/ugdata-resource.png" title="ugdata resource code" alt="ugdata resource code" /> </a> Since this is a database-backed application, we'll start by creating a class that derives from the <a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Resources/XmlSqlResource.cs" title="XmlSqlResource.cs"?>XmlSqlResource</a> class. This class has all the details for supportng a data-bound read/write HTTP Resource built right in. all we need to do is define the URI and media-type rules and declare the parameters of the resource in the class constructor. </p> <p> For this example, we want to expose the resource at the folowoing URI: <code>/ugdata/</code>. Also, we want to allow clients to access individual resources using the resource id - which is an integer in the UGDATA table. With that information, we can construction the following URIPattern (see code below). </p> <p> To keep things simple and focused, this example will focus on the XHTML representation of the resource. However, the full version of the appication will also support XML and JSON representations for the same resource. That means the media-type attribute looks like this: </p> <p>So, with the URIPattern and MediaTypes attributes defined, the basic class declaration looks like this:</p> <pre class="code"> // user group data example [UriPattern(@"/ugdata/(?&lt;id&gt;[0-9]*)\.xcs")] [MediaTypes("text/html","text/xml","application/json")] class UGData : XmlSqlResource { public UGData() { } } </pre> <p> Now we can outline the remaining details for the resource class. First, we want to make this a read/write resource, so we will mark it to allow POST and DELETE methods. We will also set the resource to redirect clients at the end of the POST and supply a URI template to use when doing the POST redirection. Next, we need to supply the class with the database connection string pointer in the configuration file and indicate where the representation transformation files are located on disk. Also, to make things clean, let's include a default media type (<code>text/html</code>) and tell exyus to cache the results of GET requests for five minutes. Finally, we need to set the update media types (they are the <code>content-type</code> headers sent by clients) and set up the caching templates to allow exyus to refresh the internal cache whenever a PUT, POST, or DELETE are executed against this resource. </p> <p>That's all we need to code in C# for now. The resulting class constructor that holds all these details looks like this: </p> <pre class="code"> // user group data example [UriPattern(@"/ugdata/(?&lt;id&gt;[0-9]*)\.xcs")] [MediaTypes("text/html","text/xml","application/json")] class UGData : XmlSqlResource { public UGData() { this.AllowPost = true; this.AllowDelete = true; this.RedirectOnPost = true; this.PostLocationUri = "/ugdata/{id}"; this.ConnectionString = "exyus_samples"; this.DocumentsFolder = "~/documents/ugdata/"; this.ContentType = "text/html"; this.LocalMaxAge = 600; this.UpdateMediaTypes = new string[] { "text/xml", "application/x-www-form-urlencoded", "application/json" }; // set cache invalidation rules this.ImmediateCacheUriTemplates = new string[] { "/ugdata/.xcs", "/ugdata/{id}.xcs" }; } } </pre> <p >Now that the C# code is complete, we can move on to the XSL transformations needed to handle the XHTML representation of the resouce. These XSL and XSD files will be automatically used by the exyus engine to validate and transform all incoming requests and outgoing responses. </p> <a href="#top" title="top">top</a> </div> <div class="section"> <h2><a name="XHTMLRepresentation" title="XHTML Representation">XHTML Representation</a></h2> <p> <a href="images/ugdata-xhtml-full.png" target="_blank"> <img src="images/ugdata-xhtml.png" title="ugdata-xhtml" alt="ugdata-xhtml" /> </a> The <a class="exyus-site">eyus</a> engine handles all request and response services through XSL transformation files. It is possible to create a set of transformations for each media-type supported by the defined resource. In addition to the XSL transformations, XSD Schema files are used to validate incoming requests. This is the heart of the exyus engine - validation and transformation. </p> <p> Since this application will access information stored in a database, one of the tasks of the transformation services will be to convert incoming HTTP requests into valid T-SQL stored procedure calls. It is also important to point out that each HTTP method that this resource supports (GET, PUT, POST, DELETE) will needs it's own request and response transformations. Finally, each unique media-type (<code>text/html, text/xml, application/json</code>) will need a complete set of request and response transformations. As you can see, if your resource supports lots of media types, you have quite a few transformations that you must implement. </p> <p> For this article, we'll only focus on the XHTML representation (<code>text/html</code>). That means we need to write transformations for the request and response events for GET, PUT, POST, and DELETE. Below are the details of each of these transformations. </p> <h3>GET Transformations</h3> <p> HTTP GET requests actually come in two 'modes': 'List' mode (<code>/ugdata/</code>) and 'Item' mode (<code>/ugdata/{id}</code>). When we create our GET transformations, we'll need to keep this in mind. Luckily, that's pretty easy. The <a class="exyus-site">exyus</a> engine keeps track of the format of the incoming URI and we can test for the 'document id' within the transform in order to determine if we are in 'List' or 'Item' mode. </p> <p> First, we need to transform incoming requests from clients into valid T-SQL calls. Here is the one that will work with the sotred procedures we created at the start of this article. </p> <pre class="code"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;!-- get_request_html.xsl ugdata - transform inputs into valid t-sql sproc call --&gt; &lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt; &lt;xsl:output method="text"/&gt; &lt;xsl:template match="/"&gt; &lt;xsl:choose&gt; &lt;xsl:when test="//args/id!=''"&gt; exec ugdata_read &lt;xsl:value-of select="//args/id"/&gt; &lt;/xsl:when&gt; &lt;xsl:otherwise&gt; exec ugdata_list &lt;/xsl:otherwise&gt; &lt;/xsl:choose&gt; &lt;/xsl:template&gt; &lt;/xsl:stylesheet&gt; </pre> <p> The transformation needed to respond to the GET request is a bit more involved. Since our goal is to create an application that will allow common Web browsers to perform full read/write (and delete) actions for this resource, we need to include the ability to add new records using the POST method and also update and delete records. For this example, we'll include an HTML FORM for POSTing a new resource to the server whenever we respond to a 'List' mode GET request. We'll return an HTML FORM for updating and an HTML FORM for deleting whenever we get an 'Item' mode GET request. That results in the following XSL transformation for response to GETs. </p> <pre class="code"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;!-- get_response_html.xsl ugdata - transform list/item for html viewers --&gt; &lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt; &lt;xsl:output method="xml" encoding="utf-8"/&gt; &lt;xsl:param name="id" /&gt; &lt;xsl:template match="/"&gt; &lt;html&gt; &lt;head&gt; &lt;title&gt;User Group Example [HTML]&lt;/title&gt; &lt;style type="text/css"&gt; label {width:5em;display:block;float:left;font-weight:bold;} a {color:blue;} h1,h2,h3,h4 {margin-bottom:0;} ol,form {margin-top:0} &lt;/style&gt; &lt;/head&gt; &lt;body&gt; &lt;h1&gt;User Group Example [HTML]&lt;/h1&gt; &lt;xsl:choose&gt; &lt;xsl:when test="$id!=''"&gt; &lt;h2&gt;Member Record&lt;/h2&gt; &lt;div class="member-item"&gt; &lt;xsl:apply-templates select="//member" mode="item"/&gt; &lt;/div&gt; &lt;p class="back-link"&gt; [&lt;a href="./" title="back to the list"&gt;return to list&lt;/a&gt;] &lt;/p&gt; &lt;/xsl:when&gt; &lt;xsl:otherwise&gt; &lt;h2&gt;Member List&lt;/h2&gt; &lt;ol class="member-list"&gt; &lt;xsl:apply-templates select="//member" mode="list"/&gt; &lt;/ol&gt; &lt;h2&gt;New Member&lt;/h2&gt; &lt;form method="post" action="."&gt; &lt;label for="firstname"&gt;first name:&lt;/label&gt; &lt;input type="text" name="firstname" /&gt; &lt;br/&gt; &lt;label for="lastname"&gt;last name:&lt;/label&gt; &lt;input type="text" name="lastname" /&gt; &lt;br/&gt; &lt;label for="birthdate"&gt;birth date:&lt;/label&gt; &lt;input type="text" name="birthdate" /&gt; &lt;br/&gt; &lt;label for="experience"&gt;experience:&lt;/label&gt; &lt;select name="experience"&gt; &lt;option value="None"&gt;None&lt;/option&gt; &lt;option value="WhatIsREST"&gt;What Is REST?&lt;/option&gt; &lt;option value="ICanHitF1"&gt;I Can Hit F1&lt;/option&gt; &lt;option value="GoogleRocks"&gt;Google Rocks!&lt;/option&gt; &lt;option value="CallMeRoy"&gt;Call Me Roy&lt;/option&gt; &lt;/select&gt; &lt;br /&gt; &lt;input type="submit" value="Add"/&gt; &lt;/form&gt; &lt;/xsl:otherwise&gt; &lt;/xsl:choose&gt; &lt;/body&gt; &lt;/html&gt; &lt;/xsl:template&gt; &lt;xsl:template match="member" mode="list"&gt; &lt;li&gt; &lt;a href="{@id}" title="view member record"&gt; &lt;xsl:value-of select="lastname"/&gt;, &lt;xsl:value-of select="firstname"/&gt; &lt;/a&gt; &lt;/li&gt; &lt;/xsl:template&gt; &lt;xsl:template match="member" mode="item"&gt; &lt;!-- update form --&gt; &lt;form action="./{@id};put" method="post"&gt; &lt;label&gt;first name:&lt;/label&gt; &lt;input name="firstname" value="{//firstname}" /&gt; &lt;br /&gt; &lt;label&gt;last name:&lt;/label&gt; &lt;input name="lastname" value="{//lastname}" /&gt; &lt;br /&gt; &lt;label&gt;birth date:&lt;/label&gt; &lt;input name="birthdate" value="{//birthdate}" /&gt; &lt;br /&gt; &lt;label for="experience"&gt;experience:&lt;/label&gt; &lt;select name="experience"&gt; &lt;option value="None"&gt; &lt;xsl:if test="//experience='None'"&gt; &lt;xsl:attribute name="selected"&gt;true&lt;/xsl:attribute&gt; &lt;/xsl:if&gt; None &lt;/option&gt; &lt;option value="WhatIsREST"&gt; &lt;xsl:if test="//experience='WhatIsREST'"&gt; &lt;xsl:attribute name="selected"&gt;true&lt;/xsl:attribute&gt; &lt;/xsl:if&gt; What Is REST? &lt;/option&gt; &lt;option value="ICanHitF1"&gt; &lt;xsl:if test="//experience='ICanHitF1'"&gt; &lt;xsl:attribute name="selected"&gt;true&lt;/xsl:attribute&gt; &lt;/xsl:if&gt; I Can Hit F1 &lt;/option&gt; &lt;option value="GoogleRocks"&gt; &lt;xsl:if test="//experience='GoogleRocks'"&gt; &lt;xsl:attribute name="selected"&gt;true&lt;/xsl:attribute&gt; &lt;/xsl:if&gt; Google Rocks! &lt;/option&gt; &lt;option value="CallMeRoy"&gt; &lt;xsl:if test="//experience='CallMeRoy'"&gt; &lt;xsl:attribute name="selected"&gt;true&lt;/xsl:attribute&gt; &lt;/xsl:if&gt; Call Me Roy &lt;/option&gt; &lt;/select&gt; &lt;br /&gt; &lt;input type="submit" value="Update" title="update this record"/&gt; &lt;/form&gt; &lt;!-- delete form --&gt; &lt;form action="./{@id};delete" method="post"&gt; &lt;input type="submit" value="Delete" title="delete this record"/&gt; &lt;/form&gt; &lt;/xsl:template&gt; &lt;/xsl:stylesheet&gt; </pre> <p> In practice, the GET response for <code>text/html</code> clients is the most involved. The transformation tasks for POST, PUT, and DELETE are much simpler for this application. <h3>POST, PUT, and DELETE Transformations</h3> <p> Since we will be redirecting clients whenever they perform add, update, or delete actions, we do not need to create any response transformations for these actions. However, we still need to create request transformations to convert incoming requests into valid T-SQL calls. </p> <p> In the case of POST and PUT requests, clients will be sending an entity body along with the request. This entity body will contain the names and values of the fields that clients wish to add or update to the data stored on the server. To make things easy, <a class="exyus-site">exyus</a> converts incoming HTML FORM data into a simple XML document (each FORM field is an element in the document). So all we need to do is create a transformation that converts the incoming 'XML document' into a valid T-SQL call. Handling DELETE requests is even easier. We just need a transformation that takes the 'id' from the URL and passes that to the database via a T-SQL call. Below are the transformations for the remaining incoming requests. </p> <pre class="code"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;!-- post_request_html.xsl ugdata - transform HTML inputs into valid t-sql add call --&gt; &lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt; &lt;xsl:output method="xml"/&gt; &lt;xsl:param name="id" /&gt; &lt;xsl:template match="/"&gt; &lt;html&gt; &lt;body&gt; &lt;h1&gt;Record Added&lt;/h1&gt; &lt;p&gt; &lt;a href="./{$id}" title="view new record"&gt;view new record&lt;/a&gt; &lt;/p&gt; &lt;/body&gt; &lt;/html&gt; &lt;/xsl:template&gt; &lt;/xsl:stylesheet&gt; &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;!-- put_request_html.xsl ugdata - transform HTML inputs into valid t-sql add call --&gt; &lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt; &lt;xsl:output method="text"/&gt; &lt;xsl:param name="id" /&gt; &lt;xsl:template match="/"&gt; exec ugdata_update @id=&lt;xsl:value-of select="$id" /&gt;, @firstname='&lt;xsl:value-of select="//firstname"/&gt;', @lastname='&lt;xsl:value-of select="//lastname"/&gt;', @birthdate='&lt;xsl:value-of select="//birthdate"/&gt;', @experience='&lt;xsl:value-of select="//experience"/&gt;' &lt;/xsl:template&gt; &lt;/xsl:stylesheet&gt; &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;!-- delete_request_html.xsl ugdata : convert args into valid delete sproc call --&gt; &lt;xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt; &lt;xsl:output method="text"/&gt; &lt;xsl:template match="/"&gt; exec ugdata_delete &lt;xsl:value-of select="//id"/&gt; &lt;/xsl:template&gt; &lt;/xsl:stylesheet&gt; </pre> <p> Once we have all the transformations complete, we're almost finished with the application. We only have two more steps left. We need to create special HTTP Resource classes to handle updating and deleting an HTTP resource using just an HTML FORM POST. We need to implement HTTP POST overloading. </p> <a href="#top" title="top">top</a> </div> <div class="section"> <h2><a name="UGDataUpdateAndDelete" title="UGData Update and Delete">UGData Update and Delete</a></h2> <p> <a href="images/ugdata-update-full.png" target="_blank"> <img src="images/ugdata-update.png" title="ugdata-update" alt="ugdfata-update" /> </a> Supporting HTTP PUT and DELETE using common Web browsers is a challenge. Early in the development of the Web, the idea was that all resources would be editable. Instead Web browsers were built to allow only reading and creating new items (GET and POST). Ever since this mistake, PUT and DELETE actions have been difficult to properly implement without resorting to client scripting of some kind. </p> <p> An alternate to scripting clients is to 'overload' the browser POST method to convince the server that the request is really a PUT or DELETE. There are a number of ways to do this. For this application, we'll create a new resource URI that contains the requested action (<code>/ugdata/1;put</code> and <code>/ugdata/1;delete</code>). That means we need to define a new HTTP resource class to accept these requests. That resource will then have a <code>Post()</code> function that will conver the request into the proper PUT or DELETE action and execute that action on behalf of the client. In this way, the server becomes a 'proxy' for the Web client. </p> <p> Creating proxy resources in this way has some drawbacks. First, since we are defining a new resource, it is possible that some clients might attempt a GET or HEAD request using the same URI. It's also possible that advanced clients might attempt PUT or DELETE requests, too! For these reasons, our resource will explicitly handle the other HTTP methods in a safe way. </p> <p> Finally, since this resource is not, itself, making any database calls, we will not use the <a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Resources/XmlSqlResource.cs" title="XmlSqlResource.cs"?>XmlSqlResource</a> class to implement this resource. Instead we'll use the <a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Web/HTTPResource.cs" title="HTTPResource.cs"?>HTTPResource</a> base class. Below is the complete code for the UGDataUpdate class. </p> <pre class="code"> // handle form-posting for updates [UriPattern(@"/ugdata/(?&lt;id&gt;[0-9]*);put\.xcs")] [MediaTypes("application/x-www-form-urlencoded")] class UGDataUpdate : HTTPResource { Utility util = new Utility(); private string[] mediaTypes = null; private string UrlPattern; public UGDataUpdate() { this.ContentType = "application/x-www-form-urlencoded"; //get first pattern (if none set already) if (this.UrlPattern == null || this.UrlPattern == string.Empty) { this.UrlPattern = util.GetDefaultUriPattern(this); } // copy media types to make things easier mediaTypes = util.GetMediaTypes(this); } public override void Get() { Hashtable arg_list = util.ParseUrlPattern(this.Context.Request.RawUrl, this.UrlPattern); if (!arg_list.Contains("id")) { throw new HttpException(400, "Missing document id"); } string id = arg_list["id"].ToString(); this.Context.Response.Redirect("/xcs/ugdata/" + id); } public override void Post() { // validate media type string mtype = util.SetMediaType(this, mediaTypes); // validate argument Hashtable arg_list = util.ParseUrlPattern(this.Context.Request.RawUrl, this.UrlPattern); if (!arg_list.Contains("id")) { throw new HttpException(400, "Missing document id"); } // get POSTed body string data = string.Empty; using (StreamReader sr = new StreamReader(Context.Request.InputStream)) { data = sr.ReadToEnd(); sr.Close(); } // compose tgarget URL string url = string.Format("{0}://{1}{2}{3}", this.Context.Request.Url.Scheme, this.Context.Request.Url.DnsSafeHost, "/xcs/ugdata/", arg_list["id"]); // build up execution client w/ credentials HTTPClient c = new HTTPClient(); c.Credentials = util.GetCurrentCredentials(this); // validate record already exists string rtn = c.Execute(url, "head", "text/html"); string etag = c.ResponseHeaders["etag"]; // execute PUT to target c.RequestHeaders.Add("if-match", etag); c.Execute(url, "put", this.ContentType, data); c = null; // redirect to list this.Context.Response.Redirect("/xcs/ugdata/"); } } </pre> <p> Note the use of the <a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Web/HTTPClient.cs" title="HTTPClient.cs">HTTPClient</a> class. This acts as a full-featured Web client and is used to perform the actual HTTP PUT for the client. Notice that it also automatically redirects the client back to the list (with the updated information) after the PUT is executed. </p> <p> You can also see that the <code>Get()</code> function has been overridden to strip off the ";put" action from the URL and redirect the client to the *real* resource. This prevents clients from performing GETs against this 'fake' resource URI. It should also be noted that the PUT and DELETE functions for this base class automatically return HTTP Status 405 (method not allowed). </p> <p>The DELETE implementation is almost identical. You can check out the code in the <a href="http://code.google.com/p/exyus/downloads/list" title="exyus downloads">downloadable code</a>. </p> <a href="#top" title="top">top</a> </div> <div class="section"> <h2><a name="Summary" title="Summary">Summary</a></h2> <p> In this article, you learned how <a class="exyus-site">exyus</a> can be used to create database-backed resources that support full read/write capabilities. You also learned how the <a href="http://code.google.com/p/exyus/source/browse/trunk/exyus/Resources/XmlSqlResource.cs" title="XmlSqlResource.cs"?>XmlSqlResource</a> class uses XSL transformations for both requests and responses for each media type supported by the resource class you define.Finally, you learned how to use exyus to create 'proxy' resources to allow HTML FORMS clients (common web browsers) to execute PUT and DELETE actions without resorting to client-side scripting. </p> <p> There are quite a few other options and features of database-backed resource programming with <a class="exyus-site">exyus</a> including XSD validations, supporting multiple media types, and much more. Check out the online version of the <a href="http://exyus.com/xcs/ugdata/" title="UGData" target="_blank">UGData</a> application to see more on the possibilities of building data-bound applications with exyus. </p> <a href="#top" title="top">top</a> </div> <div class="section"> <h2><a name="Author" title="Author">About the Author</h2> <p> <a href="http://amundsen.com/blog/" title="mca blog" target="_blank"> <img src="http://tkfiles.storage.live.com/y1pMfQTxeoXgoyXiMtX6BY3Rv_gtQyA6X774cPln4o1tj-UKfCMHh9zMKPPhpKfnT7cnLgAYsyReFc" title="Mike Amundsen" alt="Mike Amundsen" /> </a> Mike Amundsen lives and works as a contract programmer in Kentucky, USA. He currently spends most of his time creating and supporting large-scale web sites running under Windows and ASP.NET. In the past, he spent quite a bit of time as a trainer/speaker and was involved in the writing of several books on programming with Microsoft technologies. </p> </div> <script type="text/javascript"> var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www."); document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E")); </script> <script type="text/javascript"> var pageTracker = _gat._getTracker("UA-2399548-2"); pageTracker._initData(); pageTracker._trackPageview(); </script> <script type="text/javascript"> window.onload = function() { fixExyusLinks(); } function fixExyusLinks() { var coll,i; coll = document.getElementsByTagName('a'); for(i=0;i<coll.length;i++) { if(coll[i].className.indexOf('exyus-site')!=-1) { coll[i].href="http://exyus.com"; coll[i].title="exyus web engine"; } if(coll[i].className.indexOf('exyus-code')!=-1) { coll[i].href="http://exyus.googlecode.com"; coll[i].title="exyus source code"; } if(coll[i].className.indexOf('exyus-group')!=-1) { coll[i].href="http://groups.google.com/group/exyus"; coll[i].title="exyus discussion group"; } if(coll[i].href.indexOf('http://')!=-1) { coll[i].target="_blank"; } } } </script> </body> </html>