Development Blog

 Monday, January 29, 2007

I was trying to think of a way to introduce this post, and I realize that here at Eleutian, we hate strings. We don't hate all strings, it's mostly string literals that we hate. Not even all string literals. But most of them. A good portion of the work we do to make our code more solid involves removing string literals. Anybody familiar with MonoRail has seen lines similar to the following:

RenderView("Home", "Action");
<a href="${siteRoot}/Home/Action.rails">...</a>

${UrlHelper.LinkTo("Home", "Action")}

And so on, see all those string literals that refer to things that aren't really string literals? At first glance, we can get rid of the Controller string by doing something with typeof(HomeController).Name and chopping off the Controller suffix. That'd be an ugly static method call everywhere and still leaves "Action" to be dealt with. We want things to break if HomeController changes names and we want to know where HomeController is referenced.

One of our rules is to turn potential run-time errors into potential compile time errors if at all possible.

It's funny, but the compiler is our first test, and a good one at that. We found ourselves using those controller names, area names, and action names in a variety of situations - redirections, view rendering, generating url's in the views, etc... Anytime I see this I'm annoyed:

Redirect("AnotherAction.rails?parameter=34");

or even worse:

Dictionary query = new Dictionary();
query["parameter"] = 34;
Redirect("AnotherAction.rails", query);

You'll notice that our life would be much easier if methods were first-class objects. They aren't in C# 2.0... or even in 3.0 (without lambda functions) and so this is where we are.

Aaron and I talked things over, complaining on end about how frustrating things were. Our problem boiled down to turning the controller action's into something we could reference, so we decided to try a little code generation magic. We wanted to be able to write code like the following:

MyActions.Action().Render()

Site.AdministrativeArea.Home.Action(24, "Jacob").Redirect()

And so on. In order to do this we have a tool that runs as a pre-build step that does the following:

  • Walks the source tree, parsing *.cs files, looking for controllers. We have to parse source, we can't use reflection because the same source files we're parsing will be using this generated code.
  • Look for public methods (the actions) on the controller.
  • Generates a class (HomeControllerNode) that has a method, with the same prototype as the action, that returns a ControllerActionReference:
public partial class HomeControllerNode {        
  private IControllerServices _services;

  public HomeControllerNode(IControllerServices services) {
    this._services = services;
  }

  public virtual HomeControllerViewsNode Views {
    get {
      return new HomeControllerViewsNode(this._services);
    }
  }

  [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  public virtual ControllerActionReference SomeAction() {
    return this._services.ControllerReferenceFactory.CreateActionReference(this._services.Controller, typeof(HomeController),
      "Home", "Administrative", "SomeAction", new ActionArgument[0]);
  }

  [System.Diagnostics.DebuggerNonUserCodeAttribute()]
  public virtual ControllerActionReference AnotherAction(string name) {
    return this._services.ControllerReferenceFactory.CreateActionReference(this._services.Controller, typeof(HomeController), 
      "Home", "Administrative", "AnotherAction", new ActionArgument[] {
        new ActionArgument("name", typeof(string), name)});    }
  }
}
  • Generates a stub class for area's with properties for each controller in that area (they return HomeControllerNode instances) We introduce a default root area that all top level controllers and other areas are children of:
public partial class AdministrativeAreaNode {        
  private IControllerServices _services;
        
  private HomeControllerNode _home;

  public AdministrativeAreaNode(IControllerServices services) {
    this._services = services;
    this._home = new HomeControllerNode(this._services);
  }
        
  public virtual HomeControllerNode Home {
    get {
      return this._home;
    }
  }
}
  • Walks the Views subdirectory looking for *.brail files mapping them onto their controllers.
  • Generates a class (HomeControllerViewsNode) with methods for each view that return a ControllerViewReference.
public partial class HomeControllerViewsNode {        
  private IControllerServices _services;

  public HomeControllerViewsNode(IControllerServices services) {
    this._services = services;
  }

  [System.Diagnostics.DebuggerNonUserCodeAttribute()]         
  public virtual ControllerViewReference SomeAction {
    get {
      return this._services.ControllerReferenceFactory.CreateViewReference(this._services.Controller, typeof(HomeController),
        "Home", "Administrative", "SomeAction");
    }
  }
}
  • Generates a partial class for the controller class itself with two properties - MyViews and MyActions that return the ControllerNode and ControllerViewsNode instances for that controller.
public partial class HomeController {
  public virtual HomeControllerNode MyActions {
    get {
      return new HomeControllerNode(this.ControllerServices);
    }
  }
        
  public virtual HomeControllerViewsNode MyViews {
    get {
      return new HomeControllerViewsNode(this.ControllerServices);
    }
  }
        
  protected override void PerformGeneratedInitialize() {
    base.PerformGeneratedInitialize();
    this.PropertyBag["MyViews"] = this.MyViews;
    this.PropertyBag["MyActions"] = this.MyActions;
  }
}

In the generated code, we pass a reference to an IControllerServices implementation down through the hierarchy, which provides the ControllerReferenceFactory that creates the ControllerActionReference and ControllerViewReference objects. Our EleutianController class has a property on it - Site, that returns the RootAreaNode so the top of the site can be reached from anywhere. This is also always placed into the controller's PropertyBag, along with MyActions and MyViews. The PerformGeneratedInitialize method, that's called in our base class so that each controller can add it's own MyViews and MyActions, which don't live in the base class.

Our ControllerActionReference class has Redirect and Transfer methods as well as a Url property. So now we can do the following:

<a href="${Site.HomeController.Action(200, true).Url}">...</a>

ControllerViewReference has a Render method that does the appropriate RenderView call on the controller. Now we've accomplished a lot of things:

  1. No more string literals. If we rename something, the build fails. Our views will fail when they're opened and not when links are followed, it is pretty easy to test that a view compiles.
  2. All URLs for redirections/transfers are always well formed and include necessary parameters with proper type checking.
  3. Provided ourselves with Intellisense when building URLs in tests and controllers. (Sexy? Yes.)

Speaking of testing, in our controller tests we have a custom IControllerReferenceFactory that returns mock ControllerActionReference instances (via RhinoMocks) So now redirections, transfers and view renderings are just mocked method calls:

using (_mocks.Unordered())
{
  _controller.MyViews.AnotherView.Render();
}

_mocks.ReplayAll();
_controller.SomeActionThatRendersAnotherView();
_mocks.VerifyAll();

Which is much cleaner than string comparisons on _controller.SelectedViewName, especially if a redirect with parameters is expected in an action. This has simplified our life and given us incredible peace of mind, well worth the time to implement - which was relatively simple. Whew.

by Jacob on Monday, January 29, 2007 6:45:08 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Sunday, January 28, 2007

My team and I are whores when it comes to productivity tools. At the moment I have and use so many in concer it's kind of amusing. I've actually spent a decent amount of time working with the various tool authors to get their tools to play more friendly so that I can use them together. Here's a rundown of some of the tools I use every day:

  • CodeRush with Refactor! Pro
    • I'm pretty sure I couldn't happily write code without this, and I even disable or neglect half the features.
    • I use and customize the templates heavily. They're so customizable its stupid. You can literrally do just about anything, and if you can't do it, you can write a plugin to help you do it and integrate it with any other template. I'll post the set of plugins I wrote for CodeRush later.
    • The refactorings are great and rank high on the visual appeal, discoverability and usability scales.
  • ViEmu (Visual Studio vi/vim emulation)
    • I almost took it upon myself to write this. When I worked at Microsoft, I submitted requests to the Visual Studio team to write this. Jon came to the rescue and implemented the now near-perfect ViEmu and he will forever be my hero.
    • Vim is really so much faster than regular text editing it makes transferring code from your mind to the screen much less of a barrier. If you haven't heard of it, used it, given it a good shot, or even if you haven't fallen in love with it, I'd strongly recommend doing all of those things. Here are some resources to get you started:
    • One quick tip: use Ctrl+[ instead of escape to get out of insert mode, it'll save you at least 15 seconds a day!
  • ReSharper
    • This one's new to me. I've tried it twice in the past and it's only done bad things--crashed, played badly with CodeRush or ViEmu, etc. The latest version however, seems to play fine with everything I have installed, so it's going to stay. Yes, I use CodeRush AND Resharper. I told you I was a productivity tool whore.
    • The biggest feature for me is the Error Highlighting and QuickFixes. Most of the refactorings CodeRush does (often better) but there are a few that are pretty slick.
    • The navigation features are great too. Real fast and real usable, much better than scrolling through our 40 project solution.
by Aaron on Sunday, January 28, 2007 10:54:23 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Saturday, January 27, 2007

Previously I mentioned that we switched to MonoRail for all of our new features. Well, MonoRail doesn't make page localization any easier than Web Forms does.  As a matter of fact, it actually breaks an important part of ASP.NET's localization (something I should probably submit a patch for, heh).  Basically, the DefaultResourceFactory in MonoRail creates IResources that wrap ResourceSets, which will not resolve resources to their parent resource. In other words, if you have Res.ko-KR.resx which defines String1 and Res.resx which defines String1 and String2, then calling GetString on a ResourceSet when your locale is ko-KR with String1 will work, but String2 will fail.  Calling GetString with any other locale, will return the String2 from Res.resx. To get around this, we created an IResourceFactory that returns IResources that wrap ResourceManagers instead of ResourceSets, which are capable of properly traversing the resource tree.  I'll probably submit this code soon. 

Anyways, in order to localize with MonoRail, you still need to create your resx file, add an attribute to your controller to map that resx file to something in your view's property bag, and create, name and initialize each string in your resx file.   

What we did was create a new generator like the one mentioned in my previous post that converts mybrail files to brail files.  All it does is convert this: 

${g.Test: Global Text} <br />
${v.ViewTest: View Text} <br />
${c.ControllerTest: Controller Text} <br />



To this: 

${g.Test} <br />
${c.Localization_ViewTest} <br />
${c.ControllerTest} <br /> 

Now a few things happened here:

  1. A file called Global.resx was created.
  2. A file called Test.resx was created (Test is the name of the controller)
  3. A resource called Test was added to Global.resx and initialized with the value "Global Text"
  4. A resource called ControllerTest was added to the Test.resx and initialized with the value "Controller Text"
  5. A resource called Localization_ViewTest (Localization is the name of the view) was added to Test.resx and initialized with the value "View Text"
  6. v.ViewTest was converted to c.Localization_ViewTest

Essentially, what you end up with, is one resx for your global strings, and one resx per controller, with view specific strings being stored in the controller resx with ViewName_ prefixed to them.  There's still one piece missing to the puzzle, which is how do the views know what g and c are? Do we attribute each controller with a g resource and a c resource? No... that would be too much work for us. Instead what we do is define a new IControllerDescriptorProvider that wraps a DefaultControllerDescriptorProvider and adds appropriate ResourceDescriptors to the Resources collection of the descriptor built by the DefaultControllerDescriptorProvider.   

There is some room for improvement, like gracefully handling the case where you define and give a value for a resource twice in the same file or two seperate files, e.g. ${g.Test: Hi} ${g.Test: Hello}.  In this case, g.Test will be Hello.  In the case where they're in seperate files, it'll depend on which one was saved last (ew).  A decent way around this would be to detect this happening and pop a message box if you try to do this.

by Aaron on Saturday, January 27, 2007 2:05:36 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  |  Trackback
Hi, I am Khalil
as part of Aaron's team, I want to write a small introduction. I am the "Youngest" yet i am the oldest. I joined the Eleutian team in september 2006 (that'll explain the youngest part), and am married with two kids and as I am told right now another one is on the way! (man, I feel old)  I am a recent graduate who worked for about 7 months in a telephony company and then decided that it is time to embark on different journey called Eleutian. I guess that is about it for now. let's see what is going to happen.

by Khalil on Saturday, January 27, 2007 1:42:13 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  |  Trackback
One of the things we love to do is remove tedium from code. That subject will probably be the topic of this post, and many of our posts in the future I'm sure, as we've done a lot of things in that department.  ASP.NET is lacking in a number of areas, as I'm sure most of you know.  It's "strong point" is supposed to be its declarative nature--but that often leads to very difficult to test pages.  Not only that, but it's declarative syntax leaves much to be desired.  My primary complaint is with its verbosity.  There's nothing simple about ASP.NET syntax.  Here's an example:

<asp:HyperLink runat="server" ID="_lnkForgot" NavigateUrl='~/ForgotPassword.aspx' Text='<%$ Resources: PageStrings, Login_lnkForgot_Text %>' />

Now, let's complain a little.  First, we've got that runat="server" tag.  Who's idea was that?  I mean, I understand why it's there, but how often are you going to want <asp:Anything> to be sent to the client in that form?  Wouldn't it make more sense to just ASSUME that <anything:Anything> was runat="server" and just allow us to specify runat="client" and strip that attribute when rendering if we really want to send that to the client?

Next, we've got the ID tag.  I don't really have a problem with that, it's implemented just fine.  Then there's the NavigateUrl.  Notice the ~.  I love that.  It's something that ASP.NET did right. It makes it a breeze to make pages location agnostic... unless of course you want to use a non-asp control.  Then you've got to use this lovely syntax:

<img src='<%= ResolveClientUrl("~/Images/img.gif") %>' />

Ah yes, that just screams beautiful.  Now would it have really been that difficult to replace ~ with ApplicationPath and let ~~ by as ~ in attributes? 

OK, let's move on to my favorite subject--Globalization/Localization.  ASP.NET 2.0 has come a long way on this front.  It's much easier to localize your pages the way ASP.NET 2.0 wants you to than the way ASP.NET 1.0 wanted you to, especially with the new meta:resourcekey attribute and automatic generation of resources.  Unfortunately, it's still not as simple and elegant as it could be.  If you don't want to use the meta:resource attribute (because you have a shared string) then you have to use the lovely Resources: syntax seen up above, and you have to manually create your resx file, and manually name and fill in your default locale value.  If you want to localize a literal, you get to use wonderfully verbose <asp:Localize> control. 

Another complaint that isn't demonstrated in the example is with Databinding.  The databinding syntax is pretty clean, I don't have any complaints about that, but its implementation is a bit poor, mainly because of its use of uncached reflection.  It's been a while since I benchmarked it, but if my memory serves me (and often it doesn't) then it took about 30% longer to databind using the reflexive <%# Field %> syntax than it did to use <%# ((Class)Container.DataItem).Field %>.  Who wants to write all of the latter, especially with autocomplete in aspx pages being flaky at best?

So how could ASP.NET be less verbose and more elegant? How could WE make it better without being able to extend the many internal and sealed classes that make up the webforms engine?

Preprocessing!

Here's an overview of what you do.
  1. Create a generator that extends Microsoft.CustomTool.BaseCodeGeneratorWithSite for .aspx files and do your preprocessing here.
  2. Create a wizard that extends Microsoft.VisualStudio.TemplateWizard.IWizard to set up a file hiearchy like this:
    • Page.myaspx
      • Page.aspx
        • Page.aspx.cs
        • Page.aspx.designer.cs
  3. Create an ItemTemplate for your item that contains these files and sets your custom tool to the generator you created in step one.
  4. Add a new myaspx to your project and make sure you don't ever modify the aspx directly (this takes some getting used to)
So what are some things you could filter/generate/preprocess?
  • Add runat="server" tags to all <anything:anything> nodes that don't have it.
  • Replace "~/blah" with an appropriate call to ResolveClientUrl
  • Do some localization magic

<asp:HyperLink ID="_lnkForgot" NavigateUrl='~/ForgotPassword.aspx' Text="$:Forgot your Username or Password?" />

          This is actually the markup that we wrote to generate the markup at the beginning of this post.  See the $:?  That tells the generator to create a resource in PageStrings.resx with the format Page_ID_Property that has the value that appears after the $:.  You never need to touch that resx file.  You can just write your markup, define your strings, and not be interrupted, or even have to name your strings.  If you want to name your strings, say something global.  Just do "$Name: This is a string" and if you want to use it later just use "$Name".
  • Make databinding strongly typed.  This requires a bit of help, as you can't easily infer the type being bound by just inspecting the source.  Our solution was to add a DataType="Class" attribute to whatever the repeating control was.  For example:


    <asp:Repeater ID="repeater" DataType="BoundClass">
      <ItemTemplate>
        <asp:Label ID="textBox" Text='<%#@Name%>' />
      </ItemTemplate>
    </asp:Repeater>

Notice the @ in the <%#%> tag.  That gets replaced with ((BoundClass)Container.DataItem), so you can do things like <%# String.Format("{0} {1}", @FirstName, @LastName) %>

So that's how we made ASP.NET more manageable.  Now, there are still several issues with ASP.NET WebForms that caused us to eventually switch to MonoRail, which also benefits from preprocessing, but I'll post on that later.

by Aaron on Saturday, January 27, 2007 8:47:03 AM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, January 26, 2007
Aaron said I should post an introduction or something on his blog. I've been with Eleutian for a little less than Aaron. I am one of the developers that he "leads" and recently moved to Seattle to work full time. Before Eleutian, I worked at the University of California Riverside as a Systems Programmer/Administrator for the Computer Science department. Like Aaron, I intend to see what happens.

by Jacob on Friday, January 26, 2007 3:38:14 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  |  Trackback
I've been with Eleutian now for almost a year.  I left Microsoft to help develop this English eLearning solution that is SpeakENG.  I'm now leading a small team of developers in Seattle.  We started this blog to talk about some of the things that we've been doing here as well as other random things that're just on our minds.  So that's about it, we'll see what happens.

by Aaron on Friday, January 26, 2007 3:29:53 PM (Pacific Standard Time, UTC-08:00)  #    Disclaimer  |  Comments [0]  |  Trackback