Development Blog

 Tuesday, February 20, 2007
« We're in the Press! | Main | Our CodeGenerator in CastleContrib! »

I spent yesterday and today refactoring the code for the Controller Action/View map generator tool. I made some improvements, wrote some tests and I'm pleased to announce the first release. I saw Brian had implemented his as an MsBuild task and I liked that idea, so that's how ours runs now. First, things aren't as "drop in and go" as I would like because it's still evolving in and around our, already large, code base. It's my hope that over time the tool evolves as peoples needs change, etc... we can add some more configuration, that sort of thing. I'm more than willing to hear suggestions and such. Anway, here we go...

So in order to get yourself up and running, you'll need to do a few things. First, you'll need to add the task to your MsBuild. Simply open your csproj and add the following:

<UsingTask TaskName="Eleutian.Tools.CodeGenerator.MsBuild.GenerateMonoRailSiteTreeTask" AssemblyFile="Eleutian.Tools.CodeGenerator.dll" />
  <ViewSources Include="../YourViewsDirectory/**/*.brail">
  <ControllerSources Include="Controllers/**/*.cs">
<Target Name="BeforeBuild" Inputs="@(Compile)" Outputs="$(ProjectDir)\SiteMap.generated.cs">
  <GenerateMonoRailSiteTreeTask File="SiteMap.generated.cs" Namespace="YourNamespace.SiteMap" ControllerSources="@(ControllerSources)" ViewSources="@(ViewSources)">

You'll notice a few things in here. ViewSources is just the collection of view files (be it .vm or .brail, etc..). It will use the paths of those files to add the View nodes into the map. ControllerSources is the C# source files for your controllers only, specifying this helps speed up parsing. I'll get to why in a bit. And for the Sources attribute on the task you should specify all the sources in the assembly, which @(Compile) works great for.

With the task added, you should be able to build and have a SiteMap.generated.cs file, now you'll need to tweak your "base" Controller class. If you're like us, you have a Controller class that you made yourself that all of your Controller's inherit from. In that class you'll need to add some code like the following:

private ICodeGeneratorServices _services;

public ICodeGeneratorServices CodeGeneratorServices
  get { return _services; }

public RootAreaNode Site
  get { return new RootAreaNode(this.ControllerServices); }

protected virtual void PerformGeneratedInitialize()
  _services = new DefaultCodeGeneratorServices(
    new DefaultControllerReferenceFactory(),
    new AspDotNetRedirectService()
  _services.Controller = this;

First is the member variable, _services. You can do this however you like, but it's consumed by the ControllerAction/ViewReference classes to do various likes. CodeGeneratorServices is a property the partial controller code uses to create the MyActions and MyViews node instances. Site is just an easy way to get the top, root level node. PerformGeneratorInitialize is overridden in the partial controller classes to add the MyViews and MyActions nodes to the PropertyBag so they are available in the views. So there you go. If I'm not missing anything, that should do the trick. Let me know if I did and I'll append to this post.

Oh, the reason for specifying the ControllerSources is to cut down on the number of classes/types we visit when generating the internal "tree" that the source code is generated from. At first glance it seems like you should just be able to parse the ControllerSources only. We coudln't really do that because we needed to gather information on the other types in the assembly so we'd be able to use them in the arguments of our actions, etc... 

I have to stress that the code is relatively new, having undergone a major refactor to isolate it from the rest of our project. I make no promises that I won't have to upload a new copy, with a fix. :)

Source and Binaries

Wednesday, February 21, 2007 7:11:44 AM (Pacific Standard Time, UTC-08:00)

Awesome! I knew if enough of us built our own versions it would force you to publish. :) I'll grab a copy today and put it through a few tests. This might be worth a Castle.Contrib project.

Also, any reason why you don't create a partial for the base class as well to get rid of the need for adding the integration point? (At least averything except for the implementation of PerformGeneratedInitialize) That's what I ended up doing in my generator.
Wednesday, February 21, 2007 7:23:48 AM (Pacific Standard Time, UTC-08:00)
Yeah this is excellent. I'm having a few problems in the TypeResolver at the moment though. It's not finding the WizardStepPage type for the actions on my wizard controller. Unless I've missed something, I guess I'm going to need to add some support for specifying other assemblies to load into the AppDomain to help with type resolution?
Lee Henson
Wednesday, February 21, 2007 8:07:52 AM (Pacific Standard Time, UTC-08:00)
Excellent indeed, this is much appreciated guys.
I ran into the same problems as Lee with TypeResolution, so I modified the MSBuild task to load up the referenced assemblies. I then had to tweak a few other things so that performance would be acceptable. I'll post the patch over on the Castle Dev list.
Wednesday, February 21, 2007 11:08:59 AM (Pacific Standard Time, UTC-08:00)
Yeah, good call. I suppose some types won't resolve if the assemblies aren't loaded into the AppDomain, a configuration option makes a lot of sense for that.

I didn't create a partial base class mostly because we already had something similar in our EleutianController, that and our implementation differs from the one I posted on the blog (our services are pulled from elsewhere and we don't use the DefaultCodeGeneratorServices implementation) It would have just been an option that we didn't use. I welcome any and all patches though so that we can build something more useful to everybody out there.
Comments are closed.