ASP.NET MVC: Using RESTful Architecture
- blog
- Thursday, December 06, 2007
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.
- ScottGu's Introduction to ASP.NET MVC, which includes what Controllers and Views are all about. This is a BIG read.
- Routes and Urls in ASP.NET MVC - Scott explains how URLs are now "part of the programming" of your application, and how MVC moves away from the concept of "delivering pages" and moves more towards RPC.
- Moving data from Controller to View: Scott explains how you can work with data in your view that's been created in your Controller
- Using the MVC Toolkit - I go over all the neat UI methods you can use with the forthcoming CTP of MVC.
- Using ASP.NET Ajax with ASP.NET MVC - that's a mouthful, but here's a great post on how to get the two to work together.
- 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)
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 clientThis 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?).
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":
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:
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
- 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"
- 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
...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:
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:
- 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
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!):
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
Your application is completely transportable using this architectureA 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.
-
- Tweet
-