Grouping Controllers with ASP.NET MVC

asp.net, asp.net mvc 0 comments suggest edit

UPDATE: I updated the prototype to work against the ASP.NET MVC 1.0 RTM. Keep in mind, this is *NOT* a backport of the the ASP.NET MVC 2 feature so there may be some differences.

A question that this. The funny part with things like this is that I’ve probably spent as much time writing this blog post as I did working on the prototype, if not more!

The scenario that areas address is being able to partition your application into discrete areas of functionality. It helps make managing a large application more manageable and allows for creating distinct applets that you can drop into an application.

For example, suppose I want to drop in a blogs subfolder, complete with its own controllers and views, along with a forums subfolder with its own controllers and views, into a default project. The end result might look like the following screenshot (area folders highlighted).

areas-folder-structure

Notice that these folders have their own Views, Content, and Controllers directories. This is slightly similar to a solution proposed by Steve Sanderson, but he ran into a few problems we’d like to resolve.

  • URL generation doesn’t take namespaces into consideration when generating a URL. We want to be able to easily generate URLs to other areas.
  • When you are within one area, and you call Html.ActionLink to link to another action in the same area, you’d like to not have to specify the area name. You’d also like to not be forced to specify a route name.
  • You still want to be able to link to another area by specifying the area name. And, you want to be able to have controllers of the same name within the same area.
  • You also want to be able to link to the “root” area, aka the default HomeController that comes with the project template that is not located in an area.

The prototype I put together resolves these problems by adopting and enforcing a few constraints when it comes to areas.

  • The area portion comes first in the URL.
  • Controller namespaces must have a specific format that includes the area name in the namespace.
  • The root controllers that are not in any area have a default area name of “root”.
  • When resolving a View/Partial View for a controller within an area, we search in the area’s Views folder first. If not found there, we then look in the root Views folder.

Overridable Templating

This last point bears a bit of elaboration. It is a technique that came about from some experimentation I did on a potential new way of skinning for Subtext.

In the Blogs area, I have a partial view called LoginUserControl.ascx. In the Forums area, I don’t have this partial view. Thus when you go to the Forums area, it falls back to the root Views directory in order to render this partial view. But in the Blogs area, it uses the one specified in the area. This is a convenient way of implementing overridable templating and is reminiscent of ASP.NET Dynamic Data.

If you run the sample, you’ll see what I mean. When you hit the Blogs area, the login link is replaced by some text saying “Blogs don’t need no stinking login”, but the Forums area still has the login link.

Note that all of these conventions are specifically for this prototype. It would be very easy to relax these constraints to fit you’re own way of doing things. I just wanted to show how this could be done using the current ASP.NET MVC bits.

Registering Routes

The first thing we do is call two new extension methods I wrote to register routes for the areas. This call is made in the RegisterRoutes method in Global.asax.cs.

routes.MapAreas("{controller}/{action}/{id}", 
    "AreasDemo", 
    new[]{ "Blogs", "Forums" });

routes.MapRootArea("{controller}/{action}/{id}", 
    "AreasDemo", 
    new { controller = "Home", action = "Index", id = "" });

The first argument to the MapAreas method is the Routing URL pattern you know and love. We will prepend an area to that URL. The second argument is a root namespace. By convention, we will append “.Areas.AreaName.Controllers” to the provided root namespace and use that as the namespace in which we lookup controller types.

For example, suppose you have a root namespace of MyRootNamespace. If you have a HomeController class within the Blogs area, its full type name would need to be

MyRootNamespace.Areas.Blogs.Controllers.HomeController.

Again, this is a convention I made up, it could be easily changed. The nice thing about following this convention is you don’t really have to think about namespaces if you follow the directory structure I outlined. You just focus on your areas.

The last argument to the method is a string array of the “areas” in your application. Perhaps I could derive this automatically by examining the file structure, but I put together this prototype in the morning and didn’t think of that till I was writing this blog post. ;)

The second method, MapRootArea, is exactly the same as MapRoute, except it adds a default of area = “root” to the defaults dictionary.

Registering the ViewEngine

I also wrote a very simple custom view engine that knows how to look in the Areas folder first, before looking in the root Views folder when searching for a view or partial view.

I wrote this in such a way that it replaces the default view engine. To make this switch, I added the following in Global.asax.cs in the Application_Start method.

ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaViewEngine());

The code for the AreaViewEngine is fairly simple. It inherits from WebFormViewEngine and looks in the appropriate Areas first for a given view or partial view before looking in the default location. The way I accomplished this was by adding some catch-all location formats such as ~/{0}.aspx and formatted those myself in the code.

If that last sentence meant nothing to you, don’t worry. It’s an implementation detail of the view engine.

Linking to Areas

In the root view, I have the following markup to link to the HomeController and Index action of each area.

<%= Html.ActionLink("Blog Home", "Index", new { area="Blogs" } )%>
<%= Html.ActionLink("Forums Home", "Index", new { area="Forums" } )%>

However, within an area, I don’t have to specify the area when linking to another action within the same area. It chooses the current area by default. For example, here’s the code to render a link to the Blogs area’s Posts action.

<%= Html.ActionLink("Blogs Posts", "Posts") %>

That’s no different than if you weren’t doing areas. Of course, if I want to link to the forums area, I need to specify that. Also, if I want to link to an action in the root, I need to specify that as well.

<%= Html.ActionLink("Forums", "Index", "new {area="forums"}") %>
<%= Html.ActionLink("Root Home", "Index", "new {area="root"}") %>

As you click around in the sample, you’ll notice that I changed the background color when in a different area to highlight that fact.

Next Step, Nested Areas

One thing my prototype doesn’t address are nested areas. This is something I’ll try to tackle next. I’m going to see if I can clean up the implementation later and possibly get them into the MVC Futures project. This is just some early playing around I did on my own so do let me know if you have better ideas for improving this.

Download the Sample

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

58 responses

  1. Avatar for Elijah Manor
    Elijah Manor November 4th, 2008

    Very nice... I look forward to using this approach.
    Is there anything about supporting sub-controllers in the works too!?!

  2. Avatar for Steve Mason
    Steve Mason November 4th, 2008

    Looks great, though does this support the use of the Html.ActionLink<>(Expression) extensions method in your views?

  3. Avatar for haacked
    haacked November 4th, 2008

    @Elijah we have an old prototype, but it needs to be updated.
    @Steve I doubt it. I didn't revisit those methods. They'd have to know how to convert the full type name of the controller into an area string in order to work. It's doable by following the conventions. If this work makes it into the futures, I can look into that.

  4. Avatar for Andrew Rimmer
    Andrew Rimmer November 4th, 2008

    Excellent work!
    I think the method of building a website out of seperate applications (modules) really needs to be a strong convention. I really enjoy building websites with ASP.NET MVC in comparison to WebForms.
    However, I recently tried Django to build a couple of apps, so that I could feed my experience of using a more mature mvc-like app into my asp.net mvc projects. What came across was that you could build a website out of multiple applications very easily.
    Building a website out of multiple apps in ASP.NET MVC has been a bit of a chore so far. The extensibility points are there, so if I wanted to do my a website, and roll my own blog, forums it would be easy enough. However what we really need, is to be able to drop in applications that have already been developed.
    So, if I want to develop a social community website I would drop in open source components/apps/areas like SubText MVC, Forums MVC. I don't see an easy way of doing this right now, although I know its early days.

  5. Avatar for Jeff Sheldon
    Jeff Sheldon November 4th, 2008

    Phil, you're awesome!
    I was looking at this exact issue this afternoon and was contemplating how I'd tackle it on my drive home.

  6. Avatar for Tony Bunce
    Tony Bunce November 4th, 2008

    Looks great Phil. I was just looking into this a few hours ago wondering if it was possible.
    This should make it much easier to migrate "class" webform apps to MVC.
    I also want to second Andrew's idea of making it easy to combine multiple MVC applications to make a single site. In addition to the Blog/Forum situation I think it would be a good way for commercial web apps to offer plug ins or modules.

  7. Avatar for Justice~!
    Justice~! November 4th, 2008

    We did something similar to this on my current contract re: overridable views and areas, as our application essentially needs to "reinvent" parts itself each year (different set of business rules, different potential flow, different additions/subtractions to or from the form elements).
    To Steve Mason: I have it working with the strongly typed Actions so if you want to chat further about it just drop me a twitter or something.

  8. Avatar for haacked
    haacked November 4th, 2008

    I really like the idea of additively composing a single app from multiple apps. That's something we've talked about a lot internally and I'll try to continue developing this prototype towards that end.

  9. Avatar for Steve
    Steve November 4th, 2008

    Excellent, thanks Phil!

  10. Avatar for Ervin Ter
    Ervin Ter November 4th, 2008

    Thanks for sharing this. This is better way to organized into more modular way.

  11. Avatar for Torkel &#214;degaard
    Torkel &#214;degaard November 4th, 2008

    Nice solution. I like the convention based approach :)

  12. Avatar for Shiju Varghese
    Shiju Varghese November 4th, 2008

    Phil,
    Great work. Thanks for sharing this.

  13. Avatar for Crocod
    Crocod November 4th, 2008

    Thanks for sharing.
    How does it relate to Subcontroller's idea?

  14. Avatar for Donovan Hide
    Donovan Hide November 4th, 2008

    Hi,
    it is good to see that the direction of writing reusuable "sub-applications" is being followed. The pluggability and extensibility of applications within a project is the one of the key selling points of Django as can be seen from the thriving collection of Google Hosted Projects. http://code.google.com/p/django-tagging/ is probably the most-used of them all.
    It would be interesting to know if you are examining an install of Django for inspiration with your work. I'm working on an MVC app right now, and severely missing the reusable Form abstraction that exists in Django. You are some way there with the model binding, but there needs to be better support for iterating over fields in a form, for instance. I'm writing a lot of repetitive code at the moment and the HTML Form Helpers are more of a burden than a help.
    Remember imitation is the highest "form" of flattery!

  15. Avatar for Steve Sanderson
    Steve Sanderson November 4th, 2008

    Nice one! This is the neatest solution to app areas that I've seen so far in ASP.NET MVC.
    It gave me a few ideas about how I'd tweak this design, which I've written up at blog.codeville.net/.../app-areas-in-aspnet-mvc-...
    @People posting about subcontrollers: It's not really related. The name "subcontrollers" tends to make everyone jump to a wide range of false assumptions about what that mechanism is about. Maybe a more specific name is needed.

  16. Avatar for Steve
    Steve November 4th, 2008

    A co-worker and I were talking and wondered about the following:
    Is it possible to change the formatting of the URL for areas to something like:
    http://localhost/{area}/{controller}/{action}
    Where the area comes before the controller?

  17. Avatar for Mike
    Mike November 4th, 2008

    Hi,
    I prefer to have "areas" in the controllers and views folder, I think it's more beautiful instead of have a folder called areas with the tree?
    Example there admin is the "area":
    /Controllers/Admin/UsersController.cs
    /Views/Admin/Users/Index.aspx
    /Views/Admin/Users/*.aspx

  18. Avatar for haacked
    haacked November 4th, 2008

    @Steve in my prototype, the area does come before the controller. But you can change it in any way you like.

  19. Avatar for justin
    justin November 5th, 2008

    A month or two ago I had created a view engine that would render embedded views from an assembly. You could override the view by placing the same folder/viewname in the main projects (root) view. I didn't get very far on it as you would need to write some HtmlHelpers to get embedded javascript/css to override properly, and I got really busy with work. That said I would _love_ it if asp.net mvc supported this out of the box. It would potentially create an entire community of premade plugin websites which are easy to customize.

  20. Avatar for dmitry
    dmitry November 5th, 2008

    Areas are very usefull

  21. Avatar for Jayson
    Jayson November 5th, 2008

    Phil,
    This is something that I've been thinking of a way to do.
    One hurdle that I have in mind when I think of extensibility is referencing master pages from an outside assembly... we all want the pages to inherit from the same layout, right?
    So for example, if i wanted to add "forums" functionality into my "base" mvc site, i would just drop the forums.dll into the bin, and configure the site to know about it. All views and controllers are within the forums assembly but the views would somehow inherit from the master page that the "base" site is using.
    Is this something that is possible, now?

  22. Avatar for sliderhouserules
    sliderhouserules November 6th, 2008

    This is something I was working through almost a year ago...
    forums.asp.net/p/1213639/2146611.aspx#2146611

  23. Avatar for sliderhouserules
    sliderhouserules November 6th, 2008

    Oops, meant to include this one...
    http://forums.asp.net/t/1206611.aspx
    I think it's ironic that your examples continue to be "blog engines".
    Not hatin', just sayin'!

  24. Avatar for Kyle LeNeau
    Kyle LeNeau November 6th, 2008

    Nice post Phil. This is something I have been interested in for awhile and happened to stumble upon it thanks to ScottGu's Blog. What is the likely hood and/or possibility of making these "Areas/Modules" bin deploy-able? I would think that you would have to use a compilable views and a view engine like Spark. This would seem to make the inclusion of "Areas/Modules" extremely easy.

  25. Avatar for Tommy
    Tommy November 7th, 2008

    Thank for this great post Phil. One question. Can I still map routes in Global.asax? Like:
    routes.MapRoute(
    "Kontrakt-Details",
    "Kontrakt/{kontraktId}",
    new { controller = "Kontrakt", action = "Details" }
    );
    This controller is located in the root namespace. Should I point it to the namespace or what? And how?

  26. Avatar for Andrea Balducci
    Andrea Balducci November 12th, 2008

    I did it in an ASP.Net Framework i'm writing in my spare time.
    You can create several different applications each one deployed in a set of custom assemblies (xcopy deployment). Every assembly can implemente one or more application ad every application can add new routes. By now is much more a proof of concept but it works since Asp.Net preview 3.
    I plan to release some "real" framework (maybe with some help) in Q1 2009.
    You can grab some sources from http://code.google.com/p/ecosystem15/

  27. Avatar for Andrea Balducci
    Andrea Balducci November 12th, 2008

    I forgot to say that the whole app is compiled (static resources, scripts, views, controllers, masterpages) and deployed as embedded resources. By now i've implemented a virtualpathprovider to mix filesystem and embedded resources, so you can easily create a portal just mixing the apps you want to deploy from an application library (or in the future from an application store :)

  28. Avatar for tc
    tc November 13th, 2008

    Any chance of a VB version?

  29. Avatar for mikenz
    mikenz November 13th, 2008

    I think this would be a very useful feature to have built in to the MVC framework. I am quite surprised it wasn't already included.

  30. Avatar for ucin
    ucin November 15th, 2008

    I try your demo and add some parameter to controller action, but when I request the url such as http://127.0.0.1/admin/security/users/phill" title="http://127.0.0.1/admin/security/users/phill">http://127.0.0.1/admin/security/users/phill (phill is {id}), I always get the parameter value null. Can give you some explanation..? while http://127.0.0.1/admin/security/users get user list (as expected),
    And how this effect the action attribute such AcceptVerbs?
    Thanks,
    Ucin Shihab

  31. Avatar for Neil
    Neil November 19th, 2008

    Hi Phil,
    Just a quick question as to how this effects deployment on iis6 and the .mvc routes?
    Thanks
    Neil

  32. Avatar for Dave
    Dave December 5th, 2008

    Very cool, but the lambda expressions really drive me nuts!

  33. Avatar for Dave
    Dave December 5th, 2008

    Is there a reason that the Views namespace is like this:
    namespace AreasDemo.Views.Forums.Home
    But the controllers is like this:
    namespace AreasDemo.Areas.Forums.Controllers
    Shouldn't the views namespace be like this:
    namespace AreasDemo.Areas.Forums.Views.Home

  34. Avatar for haacked
    haacked December 5th, 2008

    It was just the way I prototyped it. Nothing intentional.

  35. Avatar for Amin
    Amin December 7th, 2008

    gr88888!
    This is what I was looking for in MVC
    Thanks phil

  36. Avatar for Kosmo
    Kosmo December 19th, 2008

    Any idea why my areas work when running from Visual Studio by pressing F5? The site comes up as http://localhost:1134. The root area shows and my link to my areas work also.
    However, if I go directly to http://localhost/mysite, the root area comes up but I am unable able to pull up my other areas under the Areas folder.
    Kosmo

  37. Avatar for John Morales
    John Morales December 23rd, 2008

    This really should be out of the box...not having support for multi level site organization is a pretty big drawback.

  38. Avatar for Jeroen Landheer
    Jeroen Landheer January 3rd, 2009

    Hi Phil.
    I've used your sample, but what I noticed is that views and controllers in the "root" area can be used in any other area. For example, if the root area contains a "Document" controller, and I have a area "Customer", you can either navigate to /Document/SomeAction or /Customer/Document/SomeAction, while the Customer area does not have a DocumentController class.
    I've trying to prevent this by adding some constraints to the root area, but I haven't found a way to prevent this behaviour. Any ideas?

  39. Avatar for Ronald Rogers
    Ronald Rogers January 7th, 2009

    For some reason I thought that by "areas" you meant portions of the screen. My experience to web development is limited to ASP.NET.
    So I wander off-topic in wonder whether a document can be portioned out to be handled by different controllers -- some thing like content areas that could each be individually routed to...?

  40. Avatar for STGF
    STGF January 16th, 2009

    I tried implementing Phil's Areas Demo in my project haacked.com/.../areas-in-aspnetmvc.aspx.
    I appended the Areas/Blog structure in my existing MVC project and I get the following error in my project.
    The controller name 'Home' is ambiguous between the following types:
    WebMVC.Controllers.HomeController
    WebMVC.Areas.Blogs.Controllers.HomeController

    this is how my global.asax looks.
    public static void RegisterRoutes(RouteCollection routes)
    {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapAreas("{controller}/{action}/{id}",
    "WebMVC.Areas.Blogs",
    new[] { "Blogs", "Forums" });
    routes.MapRootArea("{controller}/{action}/{id}",
    "WebMVC",
    new { controller = "Home", action = "Index", id = "" });
    //routes.MapRoute(
    // "Default", // Route name
    // "{controller}/{action}/{id}",// URL with parameters
    // new { controller = "Home", action = "Index", id = "" }
    // // Parameter defaults
    //);
    }
    protected void Application_Start()
    {
    String assemblyName = Assembly.GetExecutingAssembly().CodeBase;
    String path = new Uri(assemblyName).LocalPath;
    Directory.SetCurrentDirectory(Path.GetDirectoryName(path));
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new AreaViewEngine());
    RegisterRoutes(RouteTable.Routes);
    // RouteDebug.RouteDebugger.RewriteRoutesForTesting(RouteTable.Routes);
    }
    If I remove the /Areas/Blogs from routes.MapAreas , it looks at the Index of the root.

  41. Avatar for RAS
    RAS January 28th, 2009

    Hi Phil,
    Is there any workaround so we can make the new "scaffolding" features in RC1 work with areas?
    Thank you,
    Julian

  42. Avatar for Joe
    Joe January 29th, 2009

    Hi Phil,
    I got the new RC1 assemblies and applied them in my project adn updated the code to handle the extra "useCache" parameter in hte overrides of FindView and FindPartialView.
    It seems however that the viewengine is not being utilized. I still have the ViewEngines.Engines.Clear and adding a new instance of AreaViewEngine in the Application_Start, but the methods FindView and FindPartialView are not getting called.
    Any ideas on how to get areas working again with RC1?
    Thanks,
    Joe

  43. Avatar for Ron
    Ron February 8th, 2009

    The controller grouping you came up with has certainly fit into the size of the project I'm working on. There are distinct areas of concern that are best broken up this way, and it's been working great.
    I managed fix the AreaViewEnging issues raised in the upgrade to the Release Candidate, but I would like to use the RC feature of no code-behind files.
    If I run without a code-behind, the base.FindView method returns a null reference exception.
    Here's the aspx delaration I'm using. Any pointers would be appreciated.
    <%@ Page Title="" Language="C#" MasterPageFile="~/Areas/AdSales/Views/Shared/AdSales.Master" Inherits="System.Web.Mvc.ViewPage<Media>" %>
    Thanks in advance.
    Ron

  44. Avatar for Mike
    Mike February 9th, 2009

    Phil, did you ever get this developed further? Right now I'm looking at several separate MVC web apps in my solution, but sharing masterpages and css across projects is a real pain. (I do it with a Subversion trick.) I think areas and nested areas are the best way to do what I want. Thanks!

  45. Avatar for Ron
    Ron February 12th, 2009

    Turns out my issue was only with strongly typed views. I finally found the error, with a little hint from here.
    With the RC release, there is some new settings in the web.config file under /Views. If you copy that file under the /Areas/Area/Views folder, then the strongly typed views with no code behind works perfectly.
    Good luck!

  46. Avatar for Todd
    Todd February 19th, 2009

    One of the first things I wanted to do with my MVC project was add sub-folders to the controllers and views folders!
    I modified your approach a bit so that I could have subdirs without the Areas top-level dir - I think it is a cleaner approach for most people. The Areas approach prob. should be handled with external modules.
    Thanks for the great article!

  47. Avatar for Todd
    Todd February 23rd, 2009

    Further to my last post:
    Since I believe that eventually (VS2010) we will see ASP.NET MVC, Unity and CAB (Prism for ASP.NET MVC) come together, I took the code bits and put them together myself.
    This allows me to xcopy modules (MVC applications) to a specific area in the host MVC application (Modules subdir). The Prism modularity code then allows the modules to be loaded at start-up.
    With a bit of tweaking to Phil's code (module initialization), I now have a great composite application platform for my web designs.

  48. Avatar for Mitch
    Mitch March 12th, 2009

    We're using Phil's area code (and are liking it), but prefer the typed ActionLink methods from the Futures project, so here's a quick couple of extension methods to use if you're like me and don't like using strings so much to allow easier refactoring:
    public static string ActionLinkArea<TController>(this HtmlHelper helper, Expression<Action<TController>> action, string linkText, object routeValues) where TController : Controller
    {
    return helper.ActionLinkArea(action, linkText, routeValues, null);
    }
    public static string ActionLinkArea<TController>(this HtmlHelper helper, Expression<Action<TController>> action, string linkText, object routeValues, object htmlAttributes) where TController : Controller
    {
    var routingValues = ExpressionHelper.GetRouteValuesFromExpression(action);
    return helper.ActionLink(linkText, routingValues["action"].ToString(), routingValues["controller"].ToString(), routeValues, htmlAttributes);
    }
    And example usages are:
    Html.ActionLinkArea<Web.Controllers.HomeController>(p => p.Index(), "home page", new {area="root"})
    Html.ActionLinkArea<Ave.Web.Controllers.HomeController>(p => p.SignUp(), "Sign Up", new {area="root"}, new {behavior="dialog"})

  49. Avatar for $5830036
    $5830036 April 1st, 2009

    Thanks Phil! One thing though; forms. The Html.BeginForm() helper no longer works as it doesn't look in the area for the controller action.
    Do you have anything for this?

  50. Avatar for Jeremy Caney
    Jeremy Caney April 3rd, 2009

    Hey Phil - I'm very happy to see this. This has been one of my primary concerns with the MVC approach from the beginning since my firm tends to work on large websites which often contain multiple applications. I also appreciate that your approach solves this at a relatively fundamental level thus hiding the implementation details from (most of) the application; I had come up with a work around for this problem but it was implemented at a higher level and, therefore, was more invasive in terms of the actual application code.

  51. Avatar for wireless internet cards for la
    wireless internet cards for la December 17th, 2009

    I've never tried grouping these controllers before.

  52. Avatar for JP Lopes
    JP Lopes December 17th, 2009

    Mr. Haack I can't download your updated prototype!
    Could you please solve this?
    Thank you!

  53. Avatar for AdamG
    AdamG December 27th, 2009

    Please update zip sample cause it seems corrupted.
    Thanks in advance.

  54. Avatar for PRIYA
    PRIYA November 24th, 2010

    if we use areas and if we have to publish only one area, so that users who are using another area are not kicked off session when publishing the other area.Can we publish each area independently without affecting other?

  55. Avatar for sujith
    sujith January 5th, 2011

    Hey Phil, I’m having a doubt
    Suppose I have a User Control inside the Areas-> Blogs -> Views -> Admin and I want to use this user control in Areas-> Blogs -> Views ->Home -> Index.aspx
    So what path I need to pass inside the Html.RenderPartial method inside the Index.aspx.

  56. Avatar for Dmitriy
    Dmitriy January 27th, 2011

    Hi!
    Could you tell me if i use this approach to redirect each logged user to his own page.
    Example:
    www.test.com/klinton
    www.test.com/bill
    www.test.com/jonny
    www.test.com/katrina
    All of user are going to use the same page but with its own data.
    Thank you!

  57. Avatar for Greivin Britton
    Greivin Britton August 27th, 2013

    Finally a folder structure in ASP.Net that makes sense!

  58. Avatar for jane
    jane March 8th, 2015

    Thanks so much for this, been waiting since beta was released to figure out how to do this!!