Creating a RESTful Web Service Using ASP.Net MVC Part 14 – Connectedness

December 31, 2008 15:33 by admin

In their book RESTful Web Services, Leonard Richardson and Sam Ruby take Roy Fielding’s phrase ”hypermedia as the engine of application state” and explain that it means that the responsibility for tracking and changing application state lies with the client rather than the server. The server indicates how the client can change the application state by providing URIs that point to other possible states. The phrase they prefer to use in place of Roy Fielding’s phrase is “Connectedness.”

A well connected service allows the client to move through the application with minimal prior knowledge. A client that uses a web service with low connectedness must construct URIs itself using predefined rules.

I don’t want this example web service to show which URIs a web service should inject into its resource representations. What I do want to explore is how to inject URIs, therefore I’ll only be adding a few URIs.

So how can we add URIs to our web service? We have three main formats, XHTML, XML and JSON. XHTML is the easiest to add links to. There is already a standard way to add links to an XHTML document, through the <a /> element. There is also a simple way to generate links from within the ASPX views used in the web service. The following inline code uses Html.ActionLink to generate the URIs:

<ul class="guids">

<% foreach (var product in ViewData.Model)

   { %>

    <li>

        <dl id="<%= product.Id.ToString().ToUpper() %>" class="product">

            <dt>id</dt>

            <dd><%= Html.ActionLink(product.Id.ToString().ToUpper(), "Item",
            "Products", Context.Request.Url.Scheme, Context.Request.Url.Authority, null,
            new RouteValueDictionary { { "categoryString", "" },
            { "idString", product.Id.ToString().ToUpper() } },
            new RouteValueDictionary { { "class", "id" } })%></dd>

            <dt>name</dt>

            <dd><%= product.Name %></dd>

            <dt>category</dt>

            <dd><%= Html.ActionLink(product.Category.ToString(), "List",
            "Products", Context.Request.Url.Scheme, Context.Request.Url.Authority, null,
            new RouteValueDictionary { { "categoryString", product.Category.ToString() } },
            new RouteValueDictionary { { "class", "category" } })%></dd>

        </dl>

    </li>

<% } %>

</ul>

Produces the following XHTML:

<ul class="guids">

    <li>

        <dl id="0E588C0E-EDA4-4C02-87C1-C74719DC9755" class="product">

            <dt>id</dt>

            <dd>
               <a class="id"
               href="http://localhost/ShouldersOfGiants/RESTfulMVCWebService/Version14/
               Products/0E588C0E-EDA4-4C02-87C1-C74719DC9755">

               0E588C0E-EDA4-4C02-87C1-C74719DC9755
               </a>
            </dd>

            <dt>name</dt>

            <dd>clothing1</dd>

            <dt>category</dt>

            <dd>

               <a class="category"

                href="http://localhost/ShouldersOfGiants/RESTfulMVCWebService/Version14/

                Products/Clothing">Clothing</a>

            </dd>

        </dl>

    </li>

</ul>

 

Using ActionLink means we avoid hard coding URIs into the ASPX pages, instead the routing information embedded in Global.asax.cs is reused. So if the routing changes, we don’t need to work through every ASPX file to update the URIs.

You may notice that the above code uses one of ActionLink’s more heavily parameterised overloads. I used an overload that takes protocol and hostName parameters as this forces ActionLink to output an absolute URI rather than a relative one. Hopefully this behaviour will not change with newer versions of MVC because I think it is better for a web service to return the full URI, saving the client from having to prepend the correct protocol and host name.

So XHTML can be supported pretty much out of the box, but what about XML. I thought of four ways to inject the URIs:

  1. Hardcode the URIs, or their constituent parts, into the XSLT used to transform between internal and external XML representations;
  2. Append the URIs to the internal representation before executing the XSLT;
  3. Add URI properties to the Model classes and populate them before serialising the model to the internal XML and transforming it;
  4. Have the XSLT call something similar to the ActionLink method used by the ASPX pages when creating an XHTML representation.

A fifth solution would be to drop the entire XsltResult approach and have XML generated by an ASPX page. I dismissed this out of hand.

Option 1 would have all the same drawbacks mentioned for ASPX files if ActionLink wasn’t used (i.e. routing maintenance would become difficult).

Appending URIs (option 2) would probably have to be undertaken in the controller. I think this would tie the Controller to the model and the view too much. The Controller would have to understand which URIs were appropriate for each Model class. Also, it would have to append all possible URIs, unless it knew which a particular View needed.

Having the URIs embedded in the Model (option 3) has all the disadvantages of option 2 with the added issue of the Model having properties tacked on just to workaround deficiencies in the View. Why should a Model class, which may be used for other purposes and applications, have properties just to support our web service?

This leaves option 4, dynamically creating the URIs within the View in much the same way as the ASPX page does. How can we call ActionLink from within the XSLT?

Firstly we don’t want to call ActionLink as that creates an <a /> element. We want to call UrlHelper.Action which returns just the URI. As for calling Action from within the XSLT, an XslCompiledTransform object allows us to add extension objects to it before a transform is executed. The transform can call out to the extension objects in much the same way it can call any of the standard XSLT functions.

So I created an extension class:

public class XsltExtension

{

    public virtual string Action(string actionName,

                                 string controllerName,

                                 string serializedRouteValueDictionary)

    {

        JavaScriptSerializer serializer = new JavaScriptSerializer();

        RouteValueDictionary routeValueDictionary = new RouteValueDictionary

        (serializer.Deserialize<Dictionary<string, object>>

        (serializedRouteValueDictionary));

 

        RequestContext requestContext = new RequestContext(new HttpContextWrapper

        (HttpContext.Current), new RouteData());

 

        return new UrlHelper(requestContext).Action(actionName, controllerName,

        routeValueDictionary, HttpContext.Current.Request.Url.Scheme, null);

    }

}

This class exposes a single method Action which takes three strings and returns a string. The first two parameter strings are passed verbatim to the UrlHelper.Action method. The third is assumed to be a JSON serialised Dictionary which is deserialised then used as a constructor for a RouteValueDictionary object. The RouteValueDictionary object is also passed into the UrlHelper.Action method. Again the fuller overload is used to ensure an absolute URI is created.

This means that an XSLT can pass routing parameters to this method and receive back an absolute URI . This XSLT:

<xsl:template match='int:Product'>

    <xsl:element name='Product'>

        <xsl:attribute name="href">

            <xsl:value-of select="soge:Action('Item', 'Products',

               concat('{&quot;idString&quot;:&quot;', @id,

                      '&quot;,&quot;categoryString&quot;:&quot;', @category,

                      '&quot;}'))"/>

        </xsl:attribute>

        <xsl:attribute name="id">

            <xsl:value-of select="@id"/>

        </xsl:attribute>

        <xsl:attribute name="id">

            <xsl:value-of select="@id"/>

        </xsl:attribute>

        <xsl:attribute name="name">

            <xsl:value-of select="@name"/>

        </xsl:attribute>

        <xsl:attribute name="category">

            <xsl:value-of select="@category"/>

        </xsl:attribute>

    </xsl:element>

</xsl:template>

Calls XsltExtension:Action with the parameters:

  • Item
  • Products
  • {"idString":"0e588c0e-eda4-4c02-87c1-c74719dc9755","categoryString":"Clothing"}

Which returns:

Resulting in this XML:

<Product href="http://localhost/ShouldersOfGiants/…/0e588c0e-eda4-4c02-87c1-c74719dc9755" id="0e588c0e-eda4-4c02-87c1-c74719dc9755" name="clothing1" category="Clothing" />

As you can see, the xsl:value-of select="soge:Action…” has resulted in a URI being created and inserted into the outgoing representation (without any changes to the Controller or the Model).

Two notes on the above:

  1. I have used an href attribute above. That was just for convenience and speed. Since this is custom XML, I could have used any attribute name… or I could have used xlink as it is a fairly widely understood standard (albeit only a recommendation at the moment);
  2. Originally I tried to have the XSLT build a JSON representation of a RouteValueDictionary. However, it failed to deserialise giving:

    System.Xml.Xsl.XslTransformException

    {"An error occurred during a call to extension function 'Action'. See InnerException for a complete description of the error."}

    System.InvalidOperationException

    Type 'System.Web.Routing.RoutesValueDictionary' is not supported for deserialization of an array.

    A dictionary deserialises nicely and since RouteValueDictionary can be constructed from a Dictionary, I fell back to the simpler object.

Once the extension was added automatically to every transform (see the changes to xslt.cs) it is possible to inject URIs into our XML representations.

That just leaves JSON to tackle! I will look a JSON in my next post. In the meantime the latest version provides Connectedness for XHTML and XML representations: RESTfulMVCWebService14.zip (87.90 kb)

kick it on DotNetKicks.com


Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading