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.
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. 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?).

I'll select the one without the test for now. This is what you see when your project opens:

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
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):
...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
Let's see what's in these helpers:

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
...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.
if (i != currentPage) {
sb.Append(link);
} else {
sb.Append("" i.ToString() "";
}
can be reduced to
if (i != currentPage)
sb.Append(link);
else
sb.Append("" i.ToString() "";
See, isn't that easier to read :)
In general I like to keep braces in - it's my preference as it doesn't require people to "double take" your code if their unaware of the sugaryness there. But yah - thanks for saving me the braces.
You know, I gotta stop writing these long architecture posts - geeks focus on the weirdest things. I'm surprised that Rahda guy hasn't chimed in about my blog's stylesheet yet! :).
Glad you had a reason though... didn't think you actually knew that you could do that (I think it's cool... with VB we have to use a bunch of keywords just to start and end an if statement!).
Wow... I really am random :)
Really appreaciate your efforts with this Rob, its all coming together nicely now and I can't wait for the downloads (both MVC and the Forums sample App).
Sorry if this is a silly question but are you actually using Subsonic within the new MVC/Forums App?
Now you want to reuse said DB form app as the front end of multiple different parts of the overall application. Specifically you need to have various forms that plug into several sub-apps in the solution. Each sub-app has it's own business logic and workflow. This is also a multitenant solution where each tenant can customize the forms.
Obviously you must stay DRY with the form definition, rendering, answer storage, repopulation the form with old answers, etc. or you won''t have any hope of the solution even working, much less stay sane.
I work on a system that already does this, but it's not REST. I'm having trouble seeing how to accomplish this sort of composition using a REST architecture.
Also - I should have been more clear in terms of "REST standards" - the methods I showed are the Rails way of doing things, and if convention exists that works and makes sense - why not keep doing it? :). I think if you come up with a DRY system - it's all good and you can call your endpoints what you like.
@James: RE SubSonic and the new forums... but of course!
@Yex: An example situation where Contollers "share logic" is in the case of saving and retrieving a Post. You can do this from the Topic controller (when they're viewing a list of posts associated with a Topic), or from a Post/Show, where they want to reply. There might be better ways of doing this - this is just an example.
In addition - security things might go here. Things like "GetUserName()" or "GetCurrentUser()" or "CanUserEditPost()" etc.
@Erik Lane - you got it!
@Craig Quillen: I don't know if I followed your example to the letter -but let me see if I understand. You have a survey app that is pretty "horizontal" - meaning that you can use it just about anywhere. You want to be able to plug this in to other apps as-needed, yes?
This is the thing I'm struggling with RE ASP.NET Membership right now and what I came up with was a Controller and a set of UserControls to make it portable. I made a set of pages that route to /Membership but you can toss that if you want. For instance we also have /Forum/Profile/Show for each user - if you're an admin you should be able to edit the Membership stuff here. To keep it DRY, all the membership user stuff is in a UserControl that I use in the /Membership view as well as /Profile/Show. Hope this answers your Q.
@JD: Too funny - I just changed that last night :). Good point though, and I agree - ID's don't belong UNLESS it conveys something that's needed.
I cannot, by any chance, see "the light" in the samples you are posting here that all the other commenters chime hallelujah at! What is so "readable" by the samples shown, and how is it easier to maintain than a good, thought-through ASP.Net design using codebehind?!? Don't tell me it's easier to achieve this good, thought-through design by using MVC - a good architect/designer/developer SHOULD manage very well without it. Mind you, I'm not against any technology evolution, and MVC beeing a such step - but I think it's signification is heavily exaggerated and it will for sure let many semi-experts create a horrifying meal of spaghetti for any other to maintain...
>>>The BIG showoff in all MVC-samples is the new URL "notation" handled by controllers, and the NEW way of separating UI and view from business stuff<<<
Not really- the big showoff is being able to test your logic using TDD. Can't do it that way with WebForms can ya?
>>Anyone with a little sense and sanity left should be able to write code that manages well both from a users point of view<<
It would seem - but that's not the point of this article. Although I'm sure that many people who consider themselves "sane" are still having some issues yes? Not you of course...
>>What is so "readable" by the samples shown, and how is it easier to maintain than a good, thought-through ASP.Net design using codebehind?!?<<
These helpers are in one place, reusable everywhere. CodeBehind is not. Agreed that you can do this with CodeBehind, but the general architectural preference there is to use ServerControls, and code them in CodeBehind - thus breaking DRY on every other page you want to do this on.
>>Don't tell me it's easier to achieve this good, thought-through design by using MVC<<
It's actually easier to achieve this good, thought-through design by using MVC. Oops! :):) I think "easier" isn't the word - it's actually more of a "lends itself to" because you don't have server controls and you're forced to think in terms of HTML, scripting, UI separation.
Also - the whole TDD thing really pushes people to write better code. We've made that a bunch easier, so hopefully testing will become more ubiquitous. But then again...
>>> think it's signification is heavily exaggerated and it will for sure let many semi-experts create a horrifying meal of spaghetti for any other to maintain<<<
Is this a function of the platform, or the dev? As I've always said: Spaghetti is as Spaghetti does. MVC makes it more difficult to do this - but not impossible yes?
It's not for everyone, and if you want to keep using WebForms please do! Microsoft isn't telling everyone this is the new sliced bread - just an option. And a nice one if you ask me :).
I am getting confused with the GetPagingList helper method versus the controller ;-)
-- What params should be passed to the helper method from the controller in the ViewData object?
-- Also can i put the pagedlist class inthe mvc toolkit and buid?
-- Also what about sorting a list?
This Viewdata will hold a lot of stuff!
I may be confused cause i just watched scott's presentation @ Alt.Net :-)
Thanks for your great work in 2007 and keep it up for 2008!
Vanof
"Now I'll stop developing in Rails and come back to ASP.NET (MVC). I think this is a very fresh, elegant, attractive way to develop a web application: I very much missed it. And now I'll finally get complete control over my HTML. Thank you guys."
He likes it. Its great for him. For me, the important factors are:
1) Am I getting the job done?
2) Is the stuff I wrote maintainable?
If I change my mind set and start developing in a similar fashion as Robs example, eventually I get really proficient with it and will get the job done. But if I'm the only one on my team that subscribes to the arch, it won't be easily maintained by others on staff and I'll be stuck maintaining it till I . die. Rob's not gong to convince anyone that this arch lends itself to a better way of testing or a more elegant attractive way to develop a web application if it doesn't make sense to them. And the only way to make sense of any technology is to try it. I'll probably try it just so I can better understand it and make an informed decision on using it or not.
NOLS Wilderness Medicine Institute
http://cnn.com/2002/WORLD/asiapcf/central/03/31/gen.afghan.government.ap/index.html
????
Instead it uses part of URL to identify which verb is referred to. Thus there is no one-to-one mathcing between URL and resources.
Additionally, I was wondering can we make ASP.NET MVC to behave in complete accordance with REST??
Can we somehow introduce HTTP verbs in routing module so as soon as in routing the appropriate controller action will be invoked?