Archive for 'Programming'

One of the new features in ASP.NET MVC2 is support for areas – a way to organize large projects and group code for specific sections together. For example, instead of having ~/Controllers/HomeController.cs and all the related views in ~/Views/Home/, you have ~/Areas/Home/Controllers/HomeController.cs and the related views in ~/Areas/Home/Views. This might not seem like a big deal, but the larger your project gets the more you come to appreciate the little things like this.

While this new feature is part of ASP.NET MVC2, developers using MVC1 are not out of luck; with a little bit of code (most of which has already been written for you) you can achieve the same effect. The first thing you need to do is create the folder structure. Since MVC2 uses the root 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’. Note that you can create a models directory if you want, but you should consider putting your models in a separate solution.

Lets start with the controllers. MVC doesn’t care where you place controllers – they don’t even have to be part of the project (the can be part of a referenced assembly)! One problem you might run into, however, is creating multiple controllers with the same name. For example, both our User and Admin areas will have a HomeController. You should put the controllers in different namespaces, like so:

  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}. Following this pattern, the controller for the User area is in the namespace Mvc1Areas.Controllers.User. When you create your routes, you can tell the MVC framework what namespace to look in; we will take a look at the routing configuration after views.

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 with the area. Unfortunately, this turns out to be a bit of work. Fortunately for you, all that work is already done:

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

Notice that we added search locations with a ‘{2}’ placeholder, which is filled in with the area name. Add this file anywhere in the project; I put it in the root Views directory. Note that this is not a stand-alone view engine; it is a wrapper around the default WebForms view engine. 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 for our controllers 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 a list of namespaces to give higher precedence to when choosing the right 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.

Flex and Bison are used together to create parsers, usually for programming-related tasks like parsing source files or SQL statements. While not as easy on the programmer as newer libraries like Spirit OR ANTLR, they are more efficient since they generate raw C code that can be compiled into your application. They are also capable of outputting C++ code, but there is a lack of clear documentation / examples available demonstrating this.

What I present here are my efforts over the last couple of days towards creating a program that uses the two together in C++ mode. The result is trivial – numbers are recognized and divided by five – since the focus of this program is to demonstrate using Flex and Bison. If you do not know Flex and Bison, I recommend Lex & Yacc by Tom Niemann (Flex is a clone of Lex, Bison is a clone of Yacc).

Download the example source code here.

This is an example, not a tutorial – I’m not going to take you through the code line by line. I do have some notes for you, though:

  • waffleshop.y
    • The require line tells Bison that this is meant for Bison 2.4.1. While it might work on other versions of Bison, the C++ code generated is ‘experimental’ and subject to change between versions.
    • The skeleton line specifies an alternate template to use – lalr1.cc is the C++ version.
    • The parse-param options tell Bison that we want the class to have an additional member variable – the scanner. Since Bison calls the scanner to get a token, the Bison class needs to have a reference to the scanner. The lex-param option tells Bison that when it calls yylex, it should pass the scanner as an additional argument. Our implementation of yylex invokes the passed scanner and returns the result.
    • There are two sections of code: the first is ‘%code requires’. The code inside of this block is put in the generated header file as well as the c file. The other %code block contains code that only goes in the c file, and since we don’t want anything else calling the yylex global function we make it static to the file.
  • WaffleshopParser.h
    • This is a convenience class that bundles the parser and the scanner together; it is good Object-Oriented design to do this, and is recommended in the Bison documentation.
  • WaffleshopScanner.h
    • FlexLexer.h is provided by Flex, and defines the base class that generated Flex scanner classes inherit from. The preprocessor directive surrounding it is necessary because FlexLexer.h is a mess (it says so right in the Flex generated code on line 16).
    • The yylex function has to be overloaded because Bison passes a pointer to the yylval variable. It would be nice if we could just specify this in YY_DECL, but the base Flex class has yylval with no arguments defined as pure virtual, forcing us to implement it anyway; I just went ahead and used it.

I used the following resources to construct this example:

Please post any suggestions or questions – I am no expert on either Flex or Bison and am always interested in improving my code!

Update: After you have gleaned all you can from this example, check out this followup about creating an INI file parser using Flex and Bison in C++.