mnot is talking my language...
i read this today from Mark Nottingham and it made me very happy:
While there’s a nice internal logic to mapping HTTP methods to object methods, it doesn’t realise the power of having generic semantics...
...GET, PUT and DELETE all have well-defined semantics. So well-defined that they really shouldn’t need application-specific code; after all, they’re just manipulating state in well-known ways. In fact, I’d posit that you can specify the behaviour of any RESTful resource by describing a) the processing that POST does, and b) any side effects of PUT and DELETE.
the pseudo-code he offers up looks *very close* to the way i designed the exyus web engine. compare mark's example here:
@store_type("mysql") # tell the Resource what implements GET, PUT and DELETE @acl("choose your ACL poision") # tell who / when access is allowed, per-method and finer-grained class Person (Resource): store_format = PersonML def POST(self, representation): # operate on the store... return representation def PUT_effect(self, representation): # called IFF the presented representation is storable, # but before it is available; raising an exception will back it out return status_representation class PersonML(Format): translations = { 'application/xml': (self.to_xml, self.from_xml), 'application/json': (self.to_json, self.from_json), } def to_xml(self, native_input): # do whatever you've got to do return xml_output def from_xml(self, xml_input): # do whatever you've got to do return native_output ...
to an example of a *complete* resource class initialization for a read/write resource that supports multiple media-types:
// user group data example [UriPattern(@"/ugdata/(?<id>[0-9]*)\.xcs")] [MediaTypes("text/html","text/xml","application/json")] class UGData : XmlSqlResource { public UGData() { this.ContentType = "text/html"; this.ConnectionString = "exyus_samples"; this.LocalMaxAge = 600; this.AllowPost = true; this.AllowDelete = true; this.DocumentsFolder = "~/documents/ugdata/"; this.RedirectOnPost = true; this.PostLocationUri = "/ugdata/{id}"; 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" }; } }
no methods to define. just create the URI pattern(s) and media-types. set up some rules for caching (including invalidation templates). then build XSL/XSD transforms for the selected media-types and for validating incoming entity bodies. slick, clean, and stable.
check out the XSLT that returns JSON (if requested by the client):
<?xml version="1.0" encoding="utf-8"?> <!-- 2008-03-02 (mca) ugdata - transform list/item for JSON clients --> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="text" encoding="utf-8"/> <xsl:param name="id" /> <xsl:template match="/"> <xsl:choose> <xsl:when test="$id!=''">{"member" : <xsl:apply-templates select="//member" mode="item"/>}</xsl:when> <xsl:otherwise>{"members" : [<xsl:apply-templates select="//member" mode="list"/>]} </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template match="member" mode="list"> <xsl:if test="position()!=1">,</xsl:if> { "id" : "<xsl:value-of select="@id"/>", "lastname" : "<xsl:value-of select="lastname"/>", "firstname" : "<xsl:value-of select="firstname"/>" } </xsl:template> <xsl:template match="member" mode="item"> { "id" : "<xsl:value-of select="$id"/>", "firstname" : "<xsl:value-of select="firstname"/>", "lastname" : "<xsl:value-of select="lastname"/>", "birthdate" : "<xsl:value-of select="birthdate"/>", "experience" : "<xsl:value-of select="experience"/>" } </xsl:template> </xsl:stylesheet>
you can check out the details @ exyus.com and exyus.googlecode.com.