July 2009

Volume 24 Number 07

RESTful XHTML - RESTful Services With ASP.NET MVC

By Aaron Skonnard | July 2009

This article discusses:

  • REST
  • XHTML
  • ASP.NET MVC
This article uses the following technologies:
REST, XHTML, ASP.NET

Contents

XHTML: Representing Data and Links
XHTML: Representing Input with Forms
Understanding the ASP.NET MVC Architecture
Implementing the Model
Implementing the Controller
Designing URIs with Routes
Implementing the Views
Consuming the Bookmark Service
Acknowledgments

A RESTful service is a webof resources that programs can navigate. When designing a RESTful service, you have to think carefully about how your web will work. This means designing resource representations with links that facilitate navigation, describing service input somehow, and considering how consumers will navigate around your service at run time. Getting these things right is often overlooked, but they're central to realizing the full potential REST has to offer.

Today, humans navigate sites using Web browsers that know how to render HTML and other popular content types. HTML provides the syntax and semantics for establishing links between resources (<a> element) and for describing and submitting application input (<form> and <input> elements).

When a user clicks on an <a> element in the rendered page, the browser knows to issue an HTTP GET request for the target resource and render the response. When a browser encounters a <form> element, it knows how to render the form description into a user interface that the user can fill out and submit using either a GET or POST request. When the user presses a submit button, the browser encodes the data and sends it using the specified request. These two features are largely responsible for the success of the Web.

Using links in conjunction with the universal HTTP interface makes it possible to redirect requests to new locations over time and change certain aspects of security on the fly without changing the client code. A standard approach for forms means that you can add or remove input properties and change default values, again without changing the client code. Both features are very useful for building applications that evolve over time.

Your RESTful services should also somehow provide these two features through whatever resource representation you decide to use. For example, if you're designing a custom XML dialect for your service, you should probably come up with your own elements for establishing links and describing service input that will guide consumers through your web. Or you can simply use XHTML.

Most developers don't immediately consider XHTML as an option for "services," but that's actually one of the ways it was intended to be used. XHTML documents are by definition well-formed XML, which allows for automated processing using standard XML APIs. And since XHTML is also HTML, it comes with <a>, <form>, and <input> elements for modeling link navigation and service input as I described earlier. The only thing that's a little strange at first is how you model user-defined data structures—however, you can model classes and fields with <div> and <span> elements and collections of entities with <ol> and <li> elements. I'll walk through how to do this in more detail later in the article.

To summarize, there are several reasons to consider XHTML as the default representation for your RESTful services. First, you can leverage the syntax and semantics for important elements like <a>, <form>, and <input> instead of inventing your own. Second, you'll end up with services that feel a lot like sites because they'll be browsable by both users and applications. The XHTML is still interpreted by a human—it's just a programmer during development instead of a user at runtime. This simplifies things throughout the development process and makes it easier for consumers to learn how your service works. And finally, you can leverage standard Web development frameworks to build your RESTful services.

ASP.NET MVC is one such framework that provides an inherently RESTful model for building XHTML-based services. This article walks through some XHTML design concepts and then shows you how to build a complete XHTML-based RESTful service that you can download from the MSDN Magazinesite.

XHTML: Representing Data and Links

Before I dive into the details of ASP.NET MVC, let's first look at how you can represent common data structures and collections in XHTML. This approach isn't the only way to accomplish this, but it's a fairly common practice in XHTML-based services today.

Throughout this article, I'll describe how to implement a simple bookmark service. The service allows users to create, retrieve, update, and delete bookmarks and navigate a web of bookmarks in a variety of ways. Suppose you have a C# class representing a bookmark that looks like this:

public class Bookmark { public int Id { get; set; } public string Title { get; set; } public string Url { get; set; } public string User { get; set; } }

The first question is how can you represent a Bookmark instance in XHTML? One approach is to combine <div>, <span>, and <a> elements, where <div> elements represent structures, <span> elements represent fields, and <a> elements represent identity and links to other resources. In addition, you can annotate these elements with the XHTML "class" attribute to provide additional type metadata. Here's a complete example:

<div class="bookmark"> <span class="bookmark-id">25</span> <span class="bookmark-title">Aaron's Blog</span> <a class="bookmark-url-link" href="https://pluralsight.com/aaron">https://pluralsight.com/aaron</a> <span class="bookmark-username">skonnard</span> </div>

The next question is how will consumers process this information? Since it's well-formed XML, consumers can use any XML API to extract the bookmark information. Most .NET programmers will probably find that XLinq provides the most natural programming model for consuming XHTML programmatically. In addition, you can go one step further by enhancing XLinq with some helpful XHTML-focused extension methods that make the programming model even easier.

Throughout this article, I'll use a set of XLinq extension methods that I've included in the downloadable sample code. These extensions give you a good idea of what's possible. The following code shows how to consume the bookmark XHTML shown previously using XLinq and some of these extensions:

var bookmarkDetails = bookmarkDoc.Body().Struct("bookmark"); Console.WriteLine(bookmarkDetails["bookmark-id"].Value); Console.WriteLine(bookmarkDetails["bookmark-url"].Value); Console.WriteLine(bookmarkDetails["bookmark-title"].Value);

Now, if you want to improve how this bookmark renders in a browser, you can add a Cascading Stylesheet (CSS) to control browser-specific rendering details or add some additional UI elements and text that don't compromise the consumer's ability to extract the data of interest. For example, the following XHTML will be easier for humans to process, but you can still use the previous .NET code sample to process the information without any modification:

<h1>Bookmark Details: 3</h1> <div class="bookmark"> BookmarkID: <span class="bookmark-id">25</span><br /> Title: <span class="bookmark-title">Aaron's Blog</span><br /> Url: <a class="bookmark-url-link" href="https://pluralsight.com/aaron" >https://pluralsight.com/aaron</a><br /> Username: <span class="bookmark-username">skonnard</span></a><br /> </div>

Collections of resources aren't hard to model either. You can represent a list of bookmarks with a combination of <ol>, <li>, and <a> elements as shown here:

<ol class="bookmark-list"> <li><a class="bookmark-link" href="/bookmarks/1">Pluralsight Home</ a></li> <li><a class="bookmark-link" href="/bookmarks/2">Pluralsight On- Demand!</a></li> <li><a class="bookmark-link" href="/bookmarks/3">Aaron's Blog</a></li> <li><a class="bookmark-link" href="/bookmarks/4">Fritz's Blog</a></li> <li><a class="bookmark-link" href="/bookmarks/5">Keith's Blog</a></li> </ol>

The following code shows how to print this list of bookmarks to the console:

var bookmarks = bookmarksDoc.Body().Ol("bookmark - list").Lis(); bookmarks.Each(bm = > Console.WriteLine(" { 0 }: { 1 }", bm.Anchor().AnchorText, bm.Anchor().AnchorLink));

Notice how each <li> contains an <a> element that links to a specific bookmark. If you were to navigate one of the anchor elements, you would retrieve the bookmark details representation shown earlier. As you begin to define links between resources like this, your service starts becoming a web of linked resources.

It's pretty obvious how humans can navigate between resources using a Web browser, but how about consuming applications? A consuming application just needs to programmatically locate the anchor element of interest and then issue a GET request targeting the URI specified in the "href" attribute. These details can also be hidden behind an XLinq extension method that encapsulates anchor navigation.

The following code shows how to navigate to the first bookmark in the bookmark list and then to the target bookmark URL. The resulting XHTML is printed to the console:

var bookmarkDoc = bookmarks.First().Anchor().Navigate(); var bookmarkDetails = bookmarkDoc.Body().Struct("bookmark"); var targetDoc = bookmarkDetails.Anchor("bookmark - url - link").Navigate(); Console.WriteLine(targetDoc);

Once you start thinking about building consumers that navigate your service as a web of resources, you're officially starting to think in a more RESTful way.

XHTML: Representing Input with Forms

Now let's say a consumer wants to create a new bookmark in the system. How does the consumer figure out what data to send and how to send it without WSDL? The answer is easy: XHTML forms.

The consumer first issues a GET request to the URI for retrieving the create bookmark form. The service returns a form that looks something like this:

<h1>Create Bookmark</h1> <form action="/bookmark/create" class="create-bookmark-form" method="post"> <p> <label for="bookmark-title">Title:</label> <br /> <input id="bookmark-title" name="bookmark-title" type="text" value="" /> </p> <p> <label for="bookmark-url">Url:</label> <br /> <input id="bookmark-url" name="bookmark-url" type="text" value="" /> </p> <p> <input type="submit" value="Create" name="submit" /> </p> </form>

The form describes how to build an HTTP POST request for creating a new bookmark. The form indicates that you need to provide the bookmark-title and bookmark-url fields. In this example, bookmark-id will be autogenerated during creation, and bookmark-username will be derived from the logged-in user identity. The form also tells you what you need to send and how to send it.

When this form is rendered in a browser, a human can simply fill out the form and click Submit to create a new bookmark. A consuming application basically does the same thing by submitting the form programmatically. Again, this process can be made easier by using some form-based extension methods, shown here:

var createBookmarkForm = createBookmarkDoc.Body().Form("create - bookmark - form"); createBookmarkForm["bookmark - title"] = "Windows Live"; createBookmarkForm["bookmark - url"] = "http: //live.com/"; createBookmarkForm.Submit();

When this code runs, the Submit method generates an HTTP POST request targeting the "action" URL, and the input fields are formatted together as a URL-encoded string (application/x-www-form-urlencoded). In the end, it's no different from using the browser—the result is a new bookmark.

Although today's browsers support GET and POST only for the form method, nothing is stopping you from also specifying PUT or DELETE as the form "method" when targeting nonbrowser consumers. The Submit extension method performs equally well for any HTTP method you specify.

Understanding the ASP.NET MVC Architecture

The ASP.NET MVC architecture is based on the popular model-view-controller design pattern that has been around for decades. Figure 1illustrates the various ASP.NET MVC components and how they relate to one another. ASP.NET MVC comes with a routing engine that sits in front of the other MVC components. The routing engine receives incoming HTTP requests and routes them to a controller method. The routing engine relies on a centralized set of routes that you define in Global.asax.

ASP.NET MVC Architecture

Figure 1 ASP.NET MVC Architecture

The centralized routes define mappings between URL patterns and specific controller methods and arguments. When you generate links, you use these routes to generate the links appropriately. This makes it easy to modify your URL design throughout the development process in one central location.

It's the job of the controller to extract information from the incoming request and to interact with the user-defined model layer. The model layer can be anything ( Linq to SQL, ADO.NET Entity Framework, NHibernate, and so on)—it's the layer that performs business logic and talks to the underlying database. Notice how the model is not within the System.Web.Mvc namespace. Once the controller has finished using the model, it creates a view, supplying the view with model data for the view to use while rendering the output.

In the following sections, I'll walk through the process of implementing a complete bookmark service using the ASP.NET MVC architecture. The service supports multiple users and both public and private bookmarks. Users can browse all public bookmarks and filter them based on username or tags, and they can fully manage (CRUD) their own collection of private bookmarks.

To get started, you need to create an ASP.NET MVC project. You'll find the ASP.NET MVC Web Application project template in the list of Web project types. The default project template gives you a sample MVC starter application that you can actually run by pressing F5.

Notice how the solution structure provides directories for Models, Views, and Controllers—this is where you place the code for these different components. The default template comes with two controllers: one for managing user accounts (AccountController), and another for supporting requests to the home directory (HomeController). Both of these are used in the bookmark service.

Implementing the Model

The first thing you should focus on is the model for the bookmark service. I've built a simple SQL Server database that contains three tables for managing bookmark information—Bookmark, Tag, and BookmarkTag (see Figure 2)—and they're pretty self-explanatory.

Bookmark Service Linq to SQL Model

Figure 2 Bookmark Service Linq to SQL Model

The only caveat is that the example relies on the built-in ASP.NET forms authentication and membership service, which is provided by the default AccountController that comes with the project, to manage the service user accounts, Hence, user account information will be stored in a different database (aspnetdb.mdf), separate from the bookmark information. The username is simply stored in the Bookmark table.

It's the job of the model to provide business objects and logic on top of the database. For this example, I've defined the Linq to SQL model shown in Figure 2. This model, defined in BookmarksModel.dbml, generates the C# code found in BookmarksModel.designer.cs. You'll find classes named Bookmark, Tag, and BookmarkTag. You'll also find a BookmarksModelDataContext class, which bootstraps the entities.

At this point, you can decide to work directly with the Linq to SQL classes as your MVC model layer, or you can go a step further by defining a higher-level repository class that defines the logical business operations and shields the controller/view from even more of the underlying data manipulation details. Figure 3shows the definition for the BookmarksRepository class used in the bookmark service.

Figure 3 BookmarksRepository Class

public class BookmarksRepository { // generated Linq to SQL DataContext class BookmarksModelDataContext db = new BookmarksModelDataContext(); // query methods public IQueryable < Bookmark > FindAllBookmarks() { ... } public IQueryable < Bookmark > FindAllPublicBookmarks() { ... } public IQueryable < Bookmark > FindBookmarksByUser(string username) { ... } public IQueryable < Bookmark > FindPublicBookmarksByUser(string username) { ... } public IQueryable < Bookmark > FindBookmarksByTag(string tag) { ... } public Bookmark FindBookmarkById(int id) { ... } public IQueryable < string > FindUsers() { ... } public IQueryable < Tag > FindTags() { ... } public Tag FindTag(string tag) { ... } // insert/delete methods public void AddBookmark(Bookmark bm) { ... } public void AddTag(Tag t) { ... } public void AddTagForBookmark(string tagText, Bookmark bm) { ... } public void DeleteBookmark(Bookmark bm) { ... } // persistence public void Save() { ... } }

Implementing the Controller

The controller is the piece of code responsible for managing the HTTP request life cycle. When a request arrives, the ASP.NET MVC routing engine determines which controller to use (based on the URL) and then routes the request to the controller by calling a specific method on it. Hence, when you write a controller, you're writing the entry points that will be called by the routing engine.

For the bookmark service, we want to allow authenticated users to create, edit, and delete bookmarks. When creating bookmarks, users should be able to mark them as public (shared) or private. All users should be able to browse public bookmarks and filter them by username or tags. Private bookmarks, however, should be visible only to the owner. Consumers should also be able to retrieve the details for a particular bookmark, assuming they are authorized to do so. We should also make it possible to browse all users and tags in the system as a way to navigate the public bookmarks associated with them.

Figure 4shows the methods the BookmarkController class needs to support the service requirements I just described. The first three query methods make it possible to retrieve all public bookmarks, bookmarks by user, or bookmarks by tag. The class also includes methods for retrieving users and tags and for retrieving the details of a specific bookmark instance. All these methods respond to HTTP GET requests, but each one will be bound to a different URI template when the routes are defined.

Figure 4 BookmarkController Class

[HandleError] public class BookmarkController: Controller { // underlying model BookmarksRepository bmRepository = new BookmarksRepository(); // query methods public ActionResult BookmarkIndex() { ... } public ActionResult BookmarksByUserIndex(string username) { ... } public ActionResult BookmarksByTagIndex(string tag) {... } public ActionResult UserIndex() { ... } public ActionResult TagIndex() { ... } public ActionResult Details(int id) { ... } // create boomark [Authorize] public ActionResult Create() { ... } [Authorize][AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(FormCollection collection) { ... } // update bookmark [Authorize] public ActionResult Edit(int id) { ... }[Authorize][AcceptVerbs(HttpVerbs.Put | HttpVerbs.Post)] public ActionResult Edit(int id, FormCollection collection) // delete bookmark [Authorize] public ActionResult Delete(int id) { ... }[Authorize][AcceptVerbs(HttpVerbs.Delete | HttpVerbs.Post)] public ActionResult Delete(int id, FormCollection collection) { ... } }

The rest of the methods are for creating, editing, and deleting bookmark instances. Notice that there are two methods for each logical operation—one for retrieving the input form, and another for responding to the form submission—and each one of these methods requires authorization. The Authorize attribute ensures that the caller is authenticated and authorized to access the controller method. (The attribute also allows you to specify users and roles.) If an unauthenticated or unauthorized user attempts to access a controller method annotated with [Authorize], the authorization filter automatically redirects the user to the AccountController's Logon method, which presents the "Logon" view to the consumer.

You use the AcceptVerbs attribute to specify which HTTP verbs a particular controller method will handle (the default is GET). A single method can handle multiple verbs by ORing the HttpVerb values together. The reason I've bound the second Edit method to both PUT and POST is to accommodate browsers. This configuration allows browsers to invoke the operation using POST, while nonbrowser consumers can use PUT (which is more correct). I've done something similar on the second Delete method, binding it to both DELETE and POST. I still have to be careful that my method implementations ensure idempotency, which is a requirement for both PUT and DELETE.

Let's look at how a few of these methods have been implemented. First is BookmarkIndex:

public ActionResult BookmarkIndex() { var bms = bmRepository.FindAllPublicBookmarks().ToList(); return View( "BookmarkIndex", bms); }

This implementation simply retrieves the list of public Bookmark entities from the repository and then returns a view called BookmarkIndex (passing in the list of Bookmark entities). The view is responsible for displaying the list of Bookmark entities supplied to it by the controller.

The Details method looks up the target bookmark and returns a 404 Not Found error if it doesn't exist. Then it ensures that the user is authorized to view the bookmark. If so, it returns the Details view, supplying the identified Bookmark entity. Otherwise it returns an Unauthorized response to the consumer.

public ActionResult Details(int id) { var bm = bmRepository.FindBookmarkById(id); if (bm == null) throw new HttpException(404, "Not Found"); if (!bm.Shared) { if (!bm.Username.Equals(HttpContext.User.Identity.Name)) return new HttpUnauthorizedResult(); } return View("Details", bm); }

As a final example, let's look at the two Create methods. The first is actually quite simple—it returns the Create view to present the form description for creating a new bookmark:

[Authorize] public ActionResult Create() { return View("Create"); }

Figure 5shows the second Create method, which responds to the form submission request. It creates a new Bookmark entity from the bookmark information found in the incoming FormCollection object and then saves it to the database. It also updates the database with any new tags that were associated with the bookmark and then redirects users to their lists of personal bookmarks to indicate success.

Figure 5 The Create Method That Responds to a Form Submission Request

[Authorize][AcceptVerbs(HttpVerbs.Post)] public ActionResult Create(FormCollection collection) { try { Bookmark bm = new Bookmark(); bm.Title = collection["bookmark-title"]; bm.Url = collection["bookmark-url"]; bm.Shared = collection["bookmark-shared"].Contains("true"); bm.LastModified = DateTime.Now; bm.Username = HttpContext.User.Identity.Name; bm.Tags = collection["bookmark-tags"]; bmRepository.AddBookmark(bm); bmRepository.Save();... // create any new tags that are necessary return RedirectToAction("BookmaryByUserIndex", new { username = HttpContext.User.Identity.Name }); } catch { return View("Error"); } }

I don't have space to cover the entire controller implementation, but these code samples should give you a taste for the kind of code you write in the controller.

Designing URIs with Routes

The next thing you need to do is define URL routes that map to the various BookmarkController methods. You define your application routes in Global.asax within the RegisterRoutes method. When you first create an MVC project, your Global.asax will contain the default routing code shown in Figure 6.

Figure 6 Default Routing Code

public class MvcApplication: System.Web.HttpApplication { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute("Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller="Home", // Parameter defaults action="Index", id="" } ); } protected void Application_Start() { RegisterRoutes(RouteTable.Routes); } }

The single call to MapRoute creates a default routing rule that acts like a catchall for all URIs. This route outlines that the first path segment represents the controller name, the second path segment represents the action name (controller method), and the third path segment represents an ID value. This single rule can handle the following URIs and route them to the appropriate controller method:

/Account/Logon /Bookmark/Create /Bookmark/Details/25 /Bookmark/Edit/25

Figure 7shows the routes I'm using for this bookmark service example. With these additional routes in place, consumers can browse to "/users" to retrieve the list of users, "/tags" to retrieve the list of tags, or "/bookmarks" to retrieve the list of public bookmarks. Consumers can also browse to "/tags/{tagname}" or "/users/{username}" to filter bookmarks by tag or username, respectively. All other URIs are handled by the default route shown in Figure 6.

Figure 7 Bookmark Service Routes

public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); // customer routes routes.MapRoute("Users", "users", new { controller = "Bookmark", action = "UserIndex" }); routes.MapRoute("Tags", "tags", new { controller = "Bookmark", action = "TagIndex" }); routes.MapRoute("Bookmarks", "bookmarks", new { controller = "Bookmark", action = "BookmarkIndex" }); routes.MapRoute("BookmarksByTag", "tags/{tag}", new { controller = "Bookmark", action = "BookmarksByTagIndex", tag = "" }); routes.MapRoute("BookmarksByUser", "users/{username}", new { controller = "Bookmark", action = "BookmarksByUserIndex", username = "" }); // default route routes.MapRoute("Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" }); }

Implementing the Views

Up to this point, most of what we've done applies to both MVC "sites" and "services." All MVC applications need models, controllers, and routes. Most of what's different about building MVC "services" is found in the view. Instead of producing a traditional HTML view for human consumption, a service must produce a view that's appropriate for both human and programmatic consumption.

We're going to use XHTML for our default service representation and apply the techniques described earlier for mapping bookmark data to XHTML. We'll map data entities to <div> and <span> elements, and we'll represent collections using a combination of <ol> and <li>. We'll also annotate these elements with the "class" attribute to provide consumers with additional type metadata.

ASP.NET MVC "views" are just .aspx pages that define a view template. The .aspx pages are organized by controller name within the Views directory. Each view can be associated with an ASP.NET master page to maintain a consistent template. Figure 8shows the master page for the bookmark service.

Figure 8 Bookmark Service Master Page

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html > <head runat="server"> <title> <asp:ContentPlaceHolder ID="Title" runat="server" /> </title> </head> <body> <div style="text-align:right"><% Html.RenderPartial("LogOnUserControl"); %> </div> <h1> <asp:ContentPlaceHolder ID="Heading" runat="server" /> </h1> <hr /> <asp:ContentPlaceHolder ID="MainContent" runat="server" /> <hr /> <div class="nav-links-footer"><%=Html.ActionLink("Home", "Index", "Home", null, new { @class = "root-link" } )%> | <%=Html.ActionLink("Public Bookmarks", "BookmarkIndex", "Bookmark", null, new { @class = "public-bookmarks-link" } )%> | <%=Html.ActionLink("User Bookmarks", "BookmarksByUserIndex", "Bookmark", new { username = HttpContext.Current.User.Identity.Name }, new { @class = "my-bookmarks-link" })%> | <%=Html.ActionLink("Users", "UserIndex", "Bookmark", null, new { @class = "users-link" } )%> | <%=Html.ActionLink("Tags", "TagIndex", "Bookmark", null, new { @class = "tags-link" } )%> </div> </body> </html>

The master page defines three placeholders: one for the page title, another for the <h1> heading, and another for the main content area. These placeholders will be filled in by each individual view. In addition, the master page displays a login control at the top of the page, and it provides a footer containing the root service links to simplify navigation. Notice how I'm using the Html.ActionLink method to generate these links based on the predefined routes and controller actions.

Figure 9shows the main Bookmark Index view you get back when you browse to "/bookmarks". It displays the list of bookmarks using a combination of <ol>, <li>, and <a> elements. The <ol> elements are annotated with class="bookmark-list", and each <a> element is annotated with class="bookmark-link". This view also provides a link to retrieve the Create bookmark form description (right above the list). If you navigate to the link, the Create view shown in Figure 10comes into action.

Figure 9 Bookmark Index View

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site. Master" Inherits="System.Web.Mvc.ViewPage <IEnumerable <MvcBookmarkService.Models.Bookmark>>" %> <asp:Content ID="Content1" ContentPlaceHolderID="Title" runat="server"> Public Bookmarks</asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="Heading" runat="server"> Public Bookmarks</asp:Content> <asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server"><%= Html.ActionLink("Create bookmark", "Create", "Bookmark", new { id = "" }, new { @class = "create-bookmark-form-link" } )%> <ol class="bookmark-list"><% foreach (var item in Model) { %> <li><%= Html.ActionLink(Html.Encode(item.Title), "Details", "Bookmark", new { id = item.BookmarkID }, new { @class = "bookmark-link" })%> </li><% } %> </ol> </asp:Content>

Figure 10 Bookmark Create View

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage <MvcBookmarkService.Models.Bookmark>" %> <asp:Content ID="Content1" ContentPlaceHolderID="Title" runat="server"> Create Bookmark</asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="Heading" runat="server"> Create Bookmark</asp:Content> <asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server"><% using (Html.BeginForm("Create", "Bookmark", FormMethod.Post, new { @class = "create-bookmark-form" } )) {%> <p> <label for="Title">Title:</label> <br /><%= Html.TextBox("bookmark-title")%> </p> <p> <label for="Url">Url:</label> <br /><%= Html.TextBox("bookmark-url")%> </p> <p> <label for="Tags">Tags:</label> <br /><%= Html.TextBox("bookmark-tags")%> </p> <p> <label for="Shared">Share with public: </label><%= Html.CheckBox("bookmark-shared")%> </p> <p> <input type="submit" value="Create" name="submit" /> </p><% } %> </asp:Content>

The Create view produces a simple XHTML form, but the <form> element is annotated with class="create-bookmark-form", and each <input> element has been given a contextual name/ID value that identifies each bookmark field. This form gives consumers a complete XHTML description of how to programmatically create a new bookmark using our service (by simply submitting the form).

As a final example, Figure 11shows the beginning of the Bookmark Details view. Here I'm using a <div> element to represent the bookmark structure (class="bookmark") along with <span> and <a> elements to represent the bookmark fields. Each carries a "class" attribute to specify the field name.

Figure 11 Bookmark Details View

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage <MvcBookmarkService.Models.Bookmark>" %> <asp:Content ID="Content1" ContentPlaceHolderID="Title" runat="server"> Bookmark Details: <%= Model.BookmarkID %> </asp:Content> <asp:Content ID="Content2" ContentPlaceHolderID="Heading" runat="server"> Bookmark Details: <%= Model.BookmarkID %> </asp:Content> <asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server"> <br /> <div class="bookmark"> BookmarkID: <span class="bookmark-id"><%= Html.Encode(Model.BookmarkID) %> </span> <br /> Title: <span class="bookmark-title"><%= Html.Encode(Model.Title) %> </span> <br /> Url: <a class="bookmark-url-link" href="<%= Html.Encode(Model.Url) %>"><%= Html.Encode(Model.Url) %> </a> <br /> Username: <%=Html.ActionLink(Model.Username, "BookmarksByUserIndex", "Bookmark", new { username=Model.Username }, new { @class="bookmark-username-link" }) %> <br /> ...

Again, I don't have space to look at all the view examples in detail, but I hope this illustrates how you can produce clean XHTML result sets that are easy for both applications and humans to consume.

Consuming the Bookmark Service

The easiest way to consume the bookmark service is through a Web browser. Thanks to the XHTML design, you should be able to browse to the service's root URL and begin navigation from there. Figure 12shows what the browser looks like when you browse to the root of the bookmark service and log in. You can click Public Bookmarks to navigate to the list of all public bookmarks, and then navigate to a specific bookmark in the list. If you click Edit, you can actually edit the bookmark details (see Figure 13). The service is fully usable from any Web browser.

Browsing to the MVC Bookmark Service

Figure 12 Browsing to the MVC Bookmark Service

Editing a Specific BookmarkBrowsing to the MVC Bookmark Service

Figure 13 Editing a Specific Bookmark

While you're browsing around the service, select View Source occasionally in your browser, and you'll notice how simple the resulting XHTML looks, which again makes it easy to program against.

Figure 14shows the code for a complete .NET client application that consumes the bookmark service. It uses the set of XLinq extension methods I described earlier to simplify the XHTML and HTTP processing details. What's interesting about this sample is that it acts more like a human—it needs only the root URI to navigate to everything else of interest exposed by the bookmark service.

Figure 14 Writing a .NET Client to Consume the Bookmark Service using XLinq

class Program { static void Main(string[] args) { // navigate to the root of the service Console.WriteLine("Navigating to the root of the service..."); Uri uri = new Uri("https://localhost:63965/"); CookieContainer cookies = new CookieContainer(); var doc = XDocument.Load(uri.ToString()); doc.AddAnnotation(uri); // navigate to public bookmarks Console.WriteLine("Navigating to public bookmarks..."); var links = doc.Body().Ul("nav-links").Lis(); var bookmarksLink = links.Where(l = > l.HasAnchor("public-bookmarks-link")).First(); var bookmarksDoc = bookmarksLink.Anchor().Navigate(); bookmarksDoc.AddAnnotation(cookies); // display list of bookmarks Console.WriteLine("\nPublic bookmarks found in the system:"); var bookmarks = bookmarksDoc.Body().Ol("bookmark-list").Lis(); bookmarks.Each(bm = > Console.WriteLine("{0}: {1}", bm.Anchor().AnchorText, bm.Anchor().AnchorLink)); // navigate to the first bookmark in the list Console.WriteLine("\nNavigating to the first bookmark in the list..."); var bookmarkDoc = bookmarks.First().Anchor().Navigate(); var bookmarkDetails = bookmarkDoc.Body().Struct("bookmark"); // print the bookmark details out to the console window Console.WriteLine("Bookmark details:"); Console.WriteLine("bookmark-id: {0}", bookmarkDetails["bookmark-id"].Value); Console.WriteLine("bookmark-url-link: {0}", bookmarkDetails["bookmark-url-link"].Value); Console.WriteLine("bookmark-title: {0}", bookmarkDetails["bookmark-title"].Value); Console.WriteLine("bookmark-shared: {0}", bookmarkDetails["bookmark-shared"].Value); Console.WriteLine("bookmark-last-modified: {0}", bookmarkDetails["bookmark-last-modified"].Value); // retrieving login form Console.WriteLine("\nRetrieving login form..."); Uri logonUri = new Uri("https://localhost:63965/Account/Logon"); var logonDoc = XDocument.Load(logonUri.ToString()); logonDoc.AddAnnotation(logonUri); logonDoc.AddAnnotation(cookies); // logging on as skonnard Console.WriteLine("Logging in as 'skonnard'"); var logonForm = logonDoc.Body().Form("account-logon-form"); logonForm["username"] = "skonnard"; logonForm["password"] = "password"; logonForm.Submit(); Console.WriteLine("Login successful!"); // create a new bookmark as 'skonnard' var createBookmarkDoc = bookmarksDoc.Body().Anchor("create-bookmark-form-link").Navigate(); createBookmarkDoc.AddAnnotation(cookies); var createBookmarkForm = createBookmarkDoc.Body().Form("create-bookmark-form"); createBookmarkForm["bookmark-title"] = "Test from console!"; createBookmarkForm["bookmark-url"] = "https://live.com/"; createBookmarkForm["bookmark-tags"] = "Microsoft, Search"; createBookmarkForm.Submit(); Console.WriteLine("\nBookmark created!"); } }

The client starts by navigating to the root address, and then it looks for the link to the public bookmarks. Next it navigates to the public bookmark list and identifies a specific bookmark of interest (in this case, the first one). Next it navigates to the bookmark details and displays them to the console window. Then it retrieves the login form and performs a login using a set of credentials. Once logged in, the application retrieves the create bookmark form, fills it out, and submits a new bookmark to the system.

There are a few key observations to make at this point. First, the console application is capable of doing everything a human can do through the Web browser. That's the killer feature of this XHTML design style. Second, consumers need only to be hard-coded against the root URIs exposed by the service. All other URIs are discoverable at run time by navigating links found within the XHTML. And finally, processing XHTML structures isn't that much different from anything else—it's just data. Plus, this type of code only gets easier as you move toward dynamic languages in future versions of .NET.

Ultimately, ASP.NET MVC provides an inherently RESTful framework for implementing a web of XHTML-based resources that can be consumed by both humans and applications simultaneously. You can download the entire sample application shown in this article from the MSDN MagazineWeb site.

Acknowledgments

Thanks to both Tim Ewald and Craig Andera, whose creative thinking in this area provided fuel for my article. Tim also provided the XLinq extension methods found in the accompanying sample application.

**Aaron Skonnard **is a cofounder of Pluralsight, a Microsoft training provider offering both instructor-led and on-demand developer courses. These days Aaron spends most of his time recording Pluralsight On-Demand! courses focused on Cloud Computing, Azure, WCF and REST.