One of the new features that was added to ASP.NET MVC2 was support for areas – a way to organize large projects and group code for specific sections together. This is definitely a welcome addition – the larger a project becomes, the more important good organization is.

While areas are a great organizational tool, they are a new feature in MVC2 – meaning anyone using MVC1 is out of luck. At least until they read this article =) You can easily achieve the same effect using the process outlined here, and it is relatively pain-free. The following example is written with the MonoDevelop IDE, so the exact wording might be slightly different in Visual Studio.

The first thing you need to do is create a folder for you to put areas in. Right-click on your solution, Add → New Folder. Since MVC2 uses the folder ‘Areas’, I will too. Inside this folder, create a sub-folder for each of your areas; for this example I chose to create a folder for ‘User’ and ‘Admin’. Inside each of your area folders, you will need to create folders for ‘Views’ and ‘Controllers’.

When you create controllers, you will need to create each controller inside of its own namespace. This way, you can have controllers with the same name, but different areas. For example, the following is a bare-bones controller used in the Admin area:

  1. using System;
  2. using System.Web.Mvc;
  3.  
  4. namespace Mvc1Areas.Controllers.Admin
  5. {
  6.     public class HomeController : Controller
  7.     {
  8.         public ActionResult Index ()
  9.         {
  10.             ViewData["Message"] = "Welcome to ASP.NET MVC on Mono!";
  11.             return View ();
  12.         }
  13.     }
  14. }

Mvc1Areas is the base namespace used in my example solution, Mvc1Areas. All controllers go in Mvc1Areas.Controllers.{AreaName}. For example, the controller for the User area is in the namespace Mvc1Areas.Controllers.User. Since it is in a different namespace, there is no problem creating a class named HomeController for the Users area and Admin area.

Now create a new MVC View Page in Areas/User/Views/Home named Index.aspx. Unfortunately, the MVC framework will not find it yet. View files are found by the View Engine, which has a list of search paths it will look in to try and find the requested view. If you use the .NET Reflector to inspect System.Web.Mvc.dll, check out WebFormViewEngine:

  1. base.MasterLocationFormats = new string[] { "~/Views/{1}/{0}.master", "~/Views/Shared/{0}.master" };
  2. base.ViewLocationFormats = new string[] { "~/Views/{1}/{0}.aspx", "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.aspx", "~/Views/Shared/{0}.ascx" };

. This is the default list of locations searched. {1} will be replaced by the controller name, and {2} by the action. We need to edit this list to support another parameter {2} which can be filled in by the action. Unfortunately, this turns out to be a bit of work. Fortunately for you, all that work is already done for you. I present the derived view engine that is area aware:

  1. using System;
  2. using System.Globalization;
  3. using System.Web.Mvc;
  4. using System.Linq;
  5.  
  6. namespace Mvc1Areas
  7. {
  8.     public sealed class AreaAwareViewEngine : VirtualPathProviderViewEngine
  9.     {
  10.         private const string _cacheKeyFormat = ":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:";
  11.         private const string _cacheKeyPrefix_Master = "Master";
  12.         private const string _cacheKeyPrefix_Partial = "Partial";
  13.         private const string _cacheKeyPrefix_View = "View";
  14.         private static readonly string[] _emptyLocations = new string[0];
  15.        
  16.         public AreaAwareViewEngine()
  17.         {
  18.             MasterLocationFormats = new string[] {
  19.                 "~/Views/{1}/{0}.master",
  20.                 "~/Views/Shared/{0}.master"
  21.             };
  22.            
  23.             ViewLocationFormats = new string[] {
  24.                 "~/Areas/{2}/Views/{1}/{0}.aspx",
  25.                 "~/Views/{1}/{0}.aspx",
  26.                 "~/Views/Shared/{0}.aspx"
  27.             };
  28.            
  29.             PartialViewLocationFormats = ViewLocationFormats;
  30.         }
  31.        
  32.         protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
  33.         {
  34.             return new WebFormView(partialPath, null);
  35.         }
  36.        
  37.         protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
  38.         {
  39.             return new WebFormView(viewPath, masterPath);
  40.         }
  41.        
  42.  
  43.         private string CreateCacheKey(string prefix, string name, string controllerName, string area)
  44.         {
  45.             return string.Format(CultureInfo.InvariantCulture, _cacheKeyFormat, new object[] { base.GetType().AssemblyQualifiedName, prefix, name, controllerName, area });
  46.         }
  47.        
  48.        
  49.         public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
  50.         {
  51.             string[] strArray;
  52.             if (controllerContext == null) {
  53.                 throw new ArgumentNullException("controllerContext");
  54.             }
  55.             if (string.IsNullOrEmpty(partialViewName)) {
  56.                 throw new ArgumentException("Value cannot be null or empty.", "partialViewName");
  57.             }
  58.            
  59.             string requiredString = controllerContext.RouteData.GetRequiredString("controller");
  60.             object area;
  61.             controllerContext.RouteData.Values.TryGetValue("area", out area);
  62.            
  63.             string str2 = this.GetPath(controllerContext, this.PartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, requiredString, (string)area,  "Partial", useCache, out strArray);
  64.             if (string.IsNullOrEmpty(str2)) {
  65.                 return new ViewEngineResult(strArray);
  66.             }
  67.             return new ViewEngineResult(this.CreatePartialView(controllerContext, str2), this);
  68.         }
  69.        
  70.         public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
  71.         {
  72.             string[] strArray;
  73.             string[] strArray2;
  74.             if (controllerContext == null) {
  75.                 throw new ArgumentNullException("controllerContext");
  76.             }
  77.             if (string.IsNullOrEmpty(viewName)) {
  78.                 throw new ArgumentException("Value cannot be null or empty.", "viewName");
  79.             }
  80.            
  81.             string requiredString = controllerContext.RouteData.GetRequiredString("controller");
  82.             object area;
  83.             controllerContext.RouteData.Values.TryGetValue("area", out area);
  84.            
  85.             string str2 = this.GetPath(controllerContext, this.ViewLocationFormats, "ViewLocationFormats", viewName, requiredString, (string)area,  "View", useCache, out strArray);
  86.             string str3 = this.GetPath(controllerContext, this.MasterLocationFormats, "MasterLocationFormats", masterName, requiredString, (string)area, "Master", useCache, out strArray2);
  87.             if (!string.IsNullOrEmpty(str2) && (!string.IsNullOrEmpty(str3) || string.IsNullOrEmpty(masterName))) {
  88.                 return new ViewEngineResult(this.CreateView(controllerContext, str2, str3), this);
  89.             }
  90.             return new ViewEngineResult(strArray.Union<string>(strArray2));
  91.         }
  92.        
  93.         private string GetPath(ControllerContext controllerContext, string[] locations, string locationsPropertyName, string name, string controllerName, string areaName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
  94.         {
  95.             searchedLocations = _emptyLocations;
  96.             if (string.IsNullOrEmpty(name)) {
  97.                 return string.Empty;
  98.             }
  99.             if ((locations == null) || (locations.Length == 0)) {
  100.                 throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, "The property '{0}' cannot be null or empty.", new object[] { locationsPropertyName }));
  101.             }
  102.             bool flag = IsSpecificPath(name);
  103.             string key = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, flag ? string.Empty : areaName);
  104.             if (useCache) {
  105.                 string viewLocation = this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);
  106.                 if (viewLocation != null) {
  107.                     return viewLocation;
  108.                 }
  109.             }
  110.             if (!flag) {
  111.                 return this.GetPathFromGeneralName(controllerContext, locations, name, controllerName, areaName, key, ref searchedLocations);
  112.             }
  113.             return this.GetPathFromSpecificName(controllerContext, name, key, ref searchedLocations);
  114.         }
  115.        
  116.         private string GetPathFromGeneralName(ControllerContext controllerContext, string[] locations, string name, string controllerName, string areaName, string cacheKey, ref string[] searchedLocations)
  117.         {
  118.             string virtualPath = string.Empty;
  119.             searchedLocations = new string[locations.Length];
  120.             for (int i = 0; i < locations.Length; i++) {
  121.                 if (string.IsNullOrEmpty(areaName) && locations[i].Contains("{2}")) {
  122.                     continue;
  123.                 }
  124.            
  125.                 string str2 = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { name, controllerName, areaName });
  126.                 if (this.FileExists(controllerContext, str2)) {
  127.                     searchedLocations = _emptyLocations;
  128.                     virtualPath = str2;
  129.                 this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
  130.                     return virtualPath;
  131.                 }
  132.                 searchedLocations[i] = str2;
  133.             }
  134.             return virtualPath;
  135.         }
  136.  
  137.         private string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey, ref string[] searchedLocations)
  138.         {
  139.             string virtualPath = name;
  140.             if (!this.FileExists(controllerContext, name)) {
  141.                 virtualPath = string.Empty;
  142.                 searchedLocations = new string[] { name };
  143.             }
  144.             this.ViewLocationCache.InsertViewLocation(controllerContext.HttpContext, cacheKey, virtualPath);
  145.          return virtualPath;
  146.         }
  147.        
  148.         private static bool IsSpecificPath(string name)
  149.         {
  150.             char ch = name[0];
  151.             if (ch != '~') {
  152.                 return (ch == '/');
  153.             }
  154.             return true;
  155.         }
  156.     }
  157. }

Add this file anywhere in the project; I put it in the root Views directory. Now you need to tell the framework to use this view engine instead of the default one; add the following to the end of your RegisterRoutes function:

  1. ViewEngines.Engines.Clear();
  2. ViewEngines.Engines.Add(new AreaAwareViewEngine());

This removes the default view engine and adds ours.

So we have our view engine that is aware of our views, but how do we tell it what area to look in? And where do the namespaces come into play? Take a look at the following code used to register our routes:

  1. routes.MapRoute ("Admin Default",
  2.                  "Admin/{controller}/{action}/{id}",
  3.                  new { controller = "Home", action = "Index", id = "", area = "Admin" },
  4.                  new[] { "Mvc1Areas.Controllers.Admin" }
  5. );
  6. routes.MapRoute ("Default",
  7.                  "{controller}/{action}/{id}",
  8.                  new { controller = "Home", action = "Index", id = "", area = "User" },
  9.                  new[] { "Mvc1Areas.Controllers.User" }
  10. );

You will notice that we are adding an area property in the third parameter. This property is read by our modified view engine and used to determine where to look. Also notice the fourth parameter, which you might not have had need to use before. This parameter lets you specify the namespace for the controller.

And there you have it! The AreaAwareViewEngine is based on code found on a relevant StackOverflow question, so kudos to Aaronaught for laying the foundation for this. You can download an example project. Please leave a comment with any suggestions, feedback, etc etc. I would love to hear from anyone who finds this useful!

This is a follow-up to my previous posting about using Flex and Bison together in C++ mode. This new example actually does something useful: parses an INI file and gives easy access to the values. Just like the last example, this is not a tutorial. Download the source code and give it a read – I tried to make it as straightforward as possible.

Download the source code.

This example uses the parser and the scanner to extract data from a stream into data structures. Once the stream has been parsed, there is no more need for the parser / scanner. Instead of having them as member variables of the class, they have been moved to local variables in the function that parses the stream so that we aren’t wasting memory keeping those class instances in memory when they no longer serve a purpose.

This example also keeps track of locations using the yylloc variable so we can tell where an error occurs. If an error occurs, an exception (of type std::string) is thrown that says what the error is and where it occured (line number, character number).

The Makefile for this example is greatly improved; builds are now incremental (files that have not changed are not recompiled).

I am NOT an expert on Bison, Flex, C++, Makefiles, etc. and have coded this as a learning experience. It is provided in the hopes that others will be able to learn from it. If you notice anything that can be improved on, please leave a comment so that other visitors as well as myself can benefit. This code is provided under the WTFPL license with no warranty.

One of my favorite things about Windows 7 was the Aero Snap feature, which let you easily maximize / restore windows and resize them to take up half of the screen. There is plenty of room to have two windows side by side – especially since widescreen monitors have become commonplace – and one often has the need to compare the contents of two windows.

If you are running Gnome and Metacity, then you too can have an Aero Snap like feature using these commands. Metacity comes with support for assigning commands to keyboard shortcuts, and I have formulated a command that will read the current resolution of your screen and resize the current window to be either on the left half or the right. You will need the following programs installed: grep, awk, xwininfo, and wmctrl; all except wmctrl are probably already installed if you are running a mainstream distro.

There are a few drawbacks to this implementation. One is that the windows are resized, and cannot be reverted to their previous size by repeating the keyboard shortcut. Although I have not tested this on a multiple screen setup, I doubt it would work correctly. And finally, these commands will not resize a maximized window – you must restore it first.

The following commands will set this up for you if you are running Gnome / Metacity; just paste them into a terminal. The commands themselves are not Metacity specific and can be used with just about any window manager, but if you are not using Metacity you won’t be able to use gconftool-2 to apply them.

gconftool-2 --type string --set /apps/metacity/global_keybindings/run_command_1 "<Super>Left"
gconftool-2 --type string --set /apps/metacity/keybinding_commands/command_1 "wmctrl -r :ACTIVE: -e 0,0,0,`xwininfo -root | grep Width | awk '{ print (($2/2)-6)}'`,`xwininfo -root | grep Height | awk '{ print $2 }'`"

gconftool-2 --type string --set /apps/metacity/global_keybindings/run_command_2 "<Super>Right"
gconftool-2 --type string --set /apps/metacity/keybinding_commands/command_2 "wmctrl -r :ACTIVE: -e 0,`xwininfo -root | grep Width | awk '{ print (($2/2)+5) ",0," (($2/2)-6) }'`,`xwininfo -root | grep Height | awk '{ print $2 }'`"

Notice that I subtract 6 pixels from half the width of the screen – this is probably due to the border around the window. Six works for my theme, but you might have to adjust this value if you get a gap or overlap with your windows. The default keybinding I chose was the same as on Windows 7: Win+← and Win+→ – you can change this to whatever you want in the Keyboard Shortcuts management app.

If you want to add a couple more Windows 7 style keyboard shortcuts, feel free to pick from this list:

gconftool-2 --type string --set /apps/metacity/window_keybindings/minimize "<Super>Down"
gconftool-2 --type string --set /apps/metacity/window_keybindings/toggle_maximized "<Super>Up"
gconftool-2 --type string --set /apps/metacity/global_keybindings/show_desktop "<Super>D"
gconftool-2 --type string --set /apps/metacity/global_keybindings/run_command_terminal "<Super>T"
gconftool-2 --type string --set /apps/gnome_settings_daemon/keybindings/home "<Mod4>e"

Note that for some reason, Win+E to open a Nautilus window is odd – you have to use Mod4 instead of Super, and e has to be lowercase.