ASP.NET MVC: Using RESTful Architecture

There's a lot of information out there on how to do specific things with ASP.NET MVC, but not much in the way of architectural approaches that you can use to leverage the new MVC option.

There's a lot of information out there on how to do specific things with ASP.NET MVC, but not much in the way of architectural approaches that you can use to leverage the new MVC option. Indeed, there are many ways to set your application up using MVC, but one of them (in my mind) has some added benefits that you may want to consider. MVC platforms (such as Rails) have embraced RESTful architecture as a way to simplify and scale your application nicely, with the concept of web services built right in to the very core structure. In this post I'll discuss how you can architect your MVC application using a RESTful approach, and also how you can partition out your "logical bits" so you DRY (don't repeat yourself). In The Beginning, Scott Said "Let There Be MVC"... ASP.NET MVC release is very close, and there are a lot of very informative posts on how you can explore/get up to speed with the new option for building out ASP web pages. Before we get started with architecture, here's a recap for ya: ...And On The Seventh Day... REST is all the rage these days - but the concept has been around for a very long time. People like to equate REST and SOAP in the same sentence, as if it's an option for web service architecture. The truth is, REST is an architectural principle in and of itself that is as old as the web and, In fact, the Web itself is the REST poster child. More accurately: REST is a state of mind. As you build your site out you need to put your best Zen Master hat on and imagine every aspect of your application represented by a clean URL and well-defined operation. The definition of REST is pretty broad (italics mine):
  • Application state and functionality are divided into resources
  • Every resource is uniquely addressable using a universal syntax for use in hypermedia links
  • All resources share a uniform interface for the transfer of state between client and resource, consisting of
    • A constrained set of well-defined operations
    • A constrained set of content types, optionally supporting code on demand
  • A protocol that is:
    • Client-server - (this is the web, so this is simple)
    • Stateless - (again, the web)
    • Cacheable - (this is not so easy. It's hard to cache pages that are built with session-specific data and cookies)
    • Layered - (don't pipe everything through Default.aspx - use "discoverable" urls)
What these bullet points are saying is that the resources of your application - in other words the "pages" - should be discoverable and partitioned into logical buckets. Also - you should throw the notion of "pages" away, since these "resources" should be consumable by whatever client is calling on them (so the information might be an XML or JSON stream). To summarize: stop thinking "pages", start thinking "Remote Procedure Call". If you're a human, a "RESTful" Url for a site might be: http://www.mysite.com/catalog/show/myproduct which defaults to serving up HTML for you, as a human, to read. If you're a machine, the Url might be http://www.mysite.com/catalog/show/myproduct.xml which sends back XML, which you, as a robot, can digest and use properly.
The main concept here is that you Urls need to act as an "API" of sorts, uniquely identifying every aspect of your site, and offer the proper formatting for your client
This gets us away from the "ugly URL" concept, such as this one: http://www.amazon.com/RESTful-Web-Services-Leonard-Richardson/dp/0596529260/ref=pd_bbs_1?ie=UTF8&s=books&qid=1196976220&sr=8-1 If you follow this convention, and if you're able to offer up the proper format for the request, then Web Services will be built right into the fabric of your application. ...And MVC Was Without Form And Void - Setting Up Your Project Moving from a "file-based" delivery mechanism to more of a Remote Procedure Call approach (which goes hand-in-hand with REST) requires a shift in thinking in terms of setting up your application. The important thing to remember at all times is that you are building an instruction set for the web - a collection of consumable "Methods" that are displayed via "Views". When you download and install the new ASP.NET Extensions, which MVC will be part of, you'll get the option in your New/Project dialog to create two new projects: ASP.NET MVC Web Application and ASP.NET MVC Web Application and Test (in a window that has some interesting sorting logic - haven't we fixed this yet?). archpost I'll select the one without the test for now. This is what you see when your project opens: mvctree The structure here is again indicative that you are programming an "application" - not a "site" that serves up page. I want to keep reiterating that point :) cause it's important. ...Let There Be Firmament - Building Our Your Application We have our application, now let's add some context. For the rest of this post I'll use a forums application (again). The first thing I want to do is create a place for shared View Logic - what Rails calls "Helpers" - so I'll add a folder called Helpers. In addition I'm going to add another controller called "App" which will handle logic that is common between controllers. This shouldn't normally happen - but in some cases you need to do this. I'm also going to rename my project to "Forums": forums1 Notice also that I've added some folders for Theming, Services, and Scripts. These folders are unique to my application and I want them at the root so they're easy to find and customize. The "Services" folder is akin to the Rails "Vendor" or "Plugin" folder. I don't like the name "Vendor" and the application won't have a "Plugin" structure so I think that would be misleading. In this folder I'm going to add things like Akismet code (to fight spam), Gravatar code (for icons), Formatting and BBCode helpers, etc. ... And Bring Forth an Abundance of Code Separation This is where we start discussing REST and how it applies to your application. For the forums I have a very typical data structure, and I'm going to build out from there: forumdb Now is the time that I need to consider how a user is going to "Transition State" through my application - or in other words what URLs I'm going to use :). This is very important in that it will help you understand your controller structure and views. Here's my user case:
  • User comes to the site and sees a group of forums to view. User selects a forum and clicks on the title
  • User navigates to forum, and sees a list of topics (with stats) listed underneath.
  • User selects a topic and navigates to a page with a list of posts for that topic (paged) in ascending order
There are lots of other use cases - but for now this is enough to work with. Let's construct our URLs! The first thing to consider here is that I want the user to always know they are "in" the forums application. This complies with REST in that I'm keeping this a "well-defined operation" - in other words a link to "/Home" shouldn't navigate a user to the forum group page - that may make sense to you, but not to someone reading a URL. To that end, I want every URL to contain "/Forum" as the first element. Next, as the user "Transitions" or moves through the application, I want the URL to mirror what they are doing. Before we get to the hardcore bits of the site's URLs, we should discuss "RESTful" endpoints - or what you would consider "pages" in Web Forms. The endpoint needs to be something meaningful, and Rails uses a nice convention that divides the endpoints into 7 main bits:
  • Index - the main "landing" page. This is also the default endpoint.
  • List - a list of whatever "thing" you're showing them - like a list of Products.
  • Show - a particular item of whatever "thing" you're showing them (like a Product)
  • Edit - an edit page for the "thing"
  • New - a create page for the "thing"
  • Create - creates a new "thing" (and saves it if you're using a DB)
  • Update - updates the "thing"
  • Delete - deletes the "thing"
Normally the last 3 are "action only " and don't have a view associated with them. So if you "create" a Product (from the New view, using Create as the action on the form), you'd just redirect then to the List or Edit views. Likewise if you Update a Product from the Edit page (using Update as the action on the form) you might want to go back to the Edit view and show a status update. Using this naming convention, you now have reusable and convention-based naming for your View pages and your Controller actions. You'll see more of this below. Now that I have my user experience working for the forums, I can construct my desired URLs:
  • http://mysite/forum/group/list - shows all the groups in my forum
  • http://mysite/forum/forums/show/1 - shows all the topics in forum id=1
  • http://mysite/forums/topic/show/20 - shows all the posts for topic id=20
Now in the last URL you might be thinking "why didn't you use http://mysite/forum/posts/list/20" and the answer is that I could have - but the standard is that if you see a number after "list" - it's a page number. More importantly: the URL is a lot more clear in terms of what I'm showing and in fact almost reads like a sentence. ... And Routes Were Setup To Rule Over the Controllers And Views Now that I know how my URLs will work, I need to setup the Routes. I do this in the Global.asax and add the following rules (note there is an order of precedence here - meaning whichever applicable route is "discovered first" - that's what will be used. Be careful how you set these up): routes ...And They Called The DRY Logic... "Controller" It's worth noting here that with RESTful design, your Controller "should" only have 7 actions - if you find yourself adding more, you should revisit your Use Cases and core design - sticking to this design (or trying to) is at the core of RESTful design. But if you come upon a reason, feel free to break these "rules" - in fact I'll show you a reason right here :) cause I like breaking the rules. I've added my Controllers to my site, and also added my needed views: forums2 Note the lack of an "Admin" directory? That's RESTful design baby! You secure each view/action using whichever tool you like - FormsAuth or your own - and allow editing by appropriate users via the same URL mechanism. This keeps your application structure nice and tight, and allows your dev team to know exactly where everything is. Also - notice that in the "Topic" folder there is an additional view called "Reply"? Yes indeedy I broke the rules here because, in addition to creating a New topic, users will Reply to one, and that needs it's own logic and it's own view. ...Let The Lights Of Day Be Separated, and Let The View Logic Be Gathered Into One Place In my last few posts RE inline scripting, people were pretty freaked out by "spaghetti code" and the idea that logic will be lumped into the UI if you lose Code-behind. This can very-well happen, but keep in mind that just because your Code is "behind" your page - it's still spaghetti :). The mantra here is "don't repeat yourself" (DRY) and this means that if you should suspect any time you write an "if" or "new" in your View page. Ideally everything you do is a "one-liner" and if it's not, it belong in a helper. I've created four helpers for my forum so far: Helpers
  • AppHelper: This is a global helper file that all views, regardless of Controller, can access
  • Notify: This is a special helper that handles messaging on the pages. So when you see an error, this helper is used.
  • ProfileHelper: This helper is for the Profile cotnroller (Users)
  • TopicHelper: This set of methods does things like format posts (alternating colors), show if a user is online, etc
Let's see what's in these helpers: apphelper You can see how this logic - including the Theming bits - are contained in one place. Also, check out the "GetPagingList()" method - this method is used to set up paging for a list, like a list of topics, and returns the appropriate link based on the passed in controller/action for the current or desired page. I use this method on our User Profile page (which is a list of methods) - this page looks like this (notice the URL - pretty groovy!): members1 Here's the code for this page:
<h1>Members</h1>
<fieldset>
    <legend>Find a User</legend>
    <br />
    <%using (Html.Form<Forums.ProfileController>(x => x.Find())) { %>
        Search: <%=Html.TextBox("query",ViewData.CurrentSearch,50)%>
        <%=Html.SubmitButton("cmdGo","go") %>
    <%}%>
</fieldset>
<table width="95%" align="center">
    <tr>
        <td><b>Username</b></td>
        <td><b>Posts</b></td>
        <td><b>Joined</b></td>
        <td><b>Last Login</b></td>
    </tr>
    <%foreach (Forums.UserProfile p in ViewData.UserProfileList) { %>
    <tr>
        <td><%=Html.ActionLink<Forums.ProfileController>(x=>x.Show(p.UserName),p.UserName)%></td>
        <td><%=p.Posts %></td>
        <td><%=p.CreatedOn.ToShortDateString() %></td>
        <td><%=p.LastLogin.ToString() %></td>
    </tr>
    <%}%>
    <tr>
        <td colspan="4">
        <%=AppHelper.GetPagingList(Html,"Profile","List",
            ViewData.CurrentPage,ViewData.ProfilePageCount) %>
        </td>
    </tr>
</table>
You can see in the last line there that I've streamlined the logic to use the helper, and that all my method calls are contained in "one line". I like this approach for coding the UI because:
  • it's clean and there's no logic to speak of
  • it's very easy to maintain
  • it's easy to understand
...Be Fruitful and Make Some Killer MVC Apps Yep - another long post, but hopefully one you can refer back to when deciding how to setup your application. There's one point I didn't cover that I think I need to - and that is that
Your application is completely transportable using this architecture
A lot of people talk about "Subwebbing" applications - such as this here forum app - and that concept doesn't really apply to MVC. The reason is that it's NOT A SITE - it's not a collection of pages. It's an application and as such, the logic can be plugged into other MVC applications. If you move this to a new MVC Application, or combine it with another, you just reset some routes in your Global.asax and drop in your Views (these are static content pages) - and you're all set! I hope this was helpful, and as always I look forward to your feedback. PS: if you've made it this far into the post, yes these are the new SubSonic forums. I know I said I'd never do it again, but then it occurred to me that whatever MVC love I build into SubSonic better be based on a real mission-critical application or else it's just arm-waving. I also have a great core group of committers lined up to help me :). Besides, MVC needs a good reference app - so here it is! PPS: Yes, it will be open-sourced as soon as the CTP is live.