Design Guidelines: Stop Reinventing The Wheel!

Design Guidelines: Stop Reinventing The Wheel!

C'mon, are design guidelines really that hard to come up with? Microsoft did it already.  START THERE. 

Adding cl to the beginning of classname does NOT clarify anything; rather,  it's a signal that you're an idiot. 

Adding js as a prefix to a JavaScript function does NOT clarify that you're creating a javascript function as opposed to a vbscript function or some other script languager; it only clarifies one thing: YOU'RE AN IDIOT.

Please, Please, Please Stop Using Hungarian Notation!

Hungarian Notation has never been recommended in .NET.  As a matter of fact, the design guidelines even state that you should NOT use Hungarian Notation.

I have violated my own rule before, usually when naming asp.net webform controls.  I've been in the [bad] habit of naming them like txtFirstName.  Thats just as stupid as the above example, and yes, I AM AN IDIOT for doing that.  That will not happen again.

Prove It

I'm convinced that resumes don't mean shit.  I've seen some crazy resumes in the last week and either experienced the candidates first hand or heard horror stories about how unqualified the candidates are.  (I hate that I just ended that sentence with 'are').  So that gets me thinkin.  My wife and I are considering a move, potentially to another country.  Possibly just another state.  Either way, how do you prove beyond a shadow of a doubt, to potential employers, that you actually are not a waste of space and can backup the skills you claim on your resume?

I post code snippets on this blog.  Some are pretty good, some, well, not so good (although I still like the name).  I post screencasts like this one and this one(it's 22 megs!).  I've got actual sites on the web I can say "I wrote that!".  I have released open source software, albeit, I'm probably the only person who's actually used it, it's still code, and it's out there for public consumption.  Oh, did I mention I document my code?

So yay. I have really neat geek shiz on the web.  But how does one know, for sure, that I am The One?   I think one answer could be the ability to share your desktop with anyone using SharedView from Microsoft or a similar product.

At a past company I worked at, we used to bring candidates in and give them a story problem to solve in code, in front of us, while we were developing.  That way we could see them work.  It was great.  Totally weeded out the liars.  We had one guy, an OLEDB expert, who couldn't figure out the connection string to sql server(he was given user name, password, server and database to start with...)

This Blog v2: Now What

OK.  I have the project started.  I'm trying to figure out the best way to approach this.  Out of curiousity, I have yet to set up any unit tests for the controllers.  So I think I want to tackle that next, and see what that has to offer.

I want to try markdown for my editor.  Not sure how that's gonna work out though.  I'm a keyboard guy, so fewer clicks is a good thing.  I'll try it and see how that works.

I signed up for a Wordpress blog so I could get an akismet key for the spam service.  Anywho, wordpress rocks, they have soooo many great themes to choose from and it's ease of use is sweet.  Their admin tools are no less than top of the line.  One of my favorite features is the tool that let's you drag and drop widgets(yes, that's an abused term).  Anyway it looks something like this:

The widget thingy let's you drag and drop content easily, so I figure I'd try a stab at implementing something like that.

Woo Hoo! PanteraVB.com is now on .NET 3.5

This is cool, now I can use VS2008 for v2 of the blog software.  That also means I can easily use the latest and greatest from the Castle Trunk.

Blog v2: One Project

Sometimes I let myself get stuck in a rut of doing projects the same way every time.  If it ain't broke why fix it, right? The current blog solution uses 3 projects, but c'mon, there really isn't a need for 3 that's just what I do.  So for v2 it's going to be one project, unless a need comes up requiring more.

MonoRail - Using The Trunk

This will be the core change I make on v2 of my blog. MonoRail and ActiveRecord trunk editions.  Nothing but bleeding edge software here folks :)

This Blog v2

I like MonoRail.  I like Brail.  I don't like how I've managed my blog project.  It already has code rot.  Obviously as I learn more ins and outs of MonoRail I find better ways of doing things.  Some of those things I change, some I don't.  One thing that bugs me is that I put all of the directories for the blog at the root, and i really needed them in a directory because now the root is messy and that's annoying.  The admin tools are very lame, I have built support for tagging on the backend but just haven't hooked up the front end, mostly due to digging into the code and then seeing all the things I want to change, add, remove, getting overwhelmed and finally i just close the project and don't add tags support.

So my new new goal is start over, and see if I can set up the project in a manner where I can post the source here and see what features I can add.  Should be fun.

Windows Workflow: From the Line

This weekend I'm trying to wrap my ahead around what Windows Workflow Foundation is.  I'm powering through Microsoft Windows Workflow Foundation Step by Step by Kenn Scribner, one of the Wintellect smarties.  It's a good book, chapter 1 helped to quickly figure out what exactly windows workflow is and I immediately started thinking of possible uses for it.  I don't like learning through using designers.  So I wrote some hello world type code for learning purposes, here's the first one.

All of the code can be downloaded here.

The Workflow

The workflow I need to perform is VERY simple and really stupid.  Perfect for learning.  I need to find a customer by their customer id.  If i find a customer, I want to print their name out.  If I don't find a customer I want to write out a message to the console indicating so.  That's it.

The Customer

 
public class Customer {
    private int _id;
    public int ID {
        get { return _id; }
        set { _id = value; }
    }
    private string _name;
    public string Name {
        get { return _name; }
        set { _name = value; }
    }
    private int _age;
    public int Age {
        get { return _age; }
        set { _age = value; }
    }
    public Customer(){}
    public Customer(int id, int age, string name){
        this._id = id;
        this._age = age;
        this._name = name;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

The CustomerFinder


using System;
using System.Collections.Generic;

public class CustomerFinder {
    private static List _customers;
    static CustomerFinder(){
        _customers = new List();
        _customers.Add(new Customer(1, 1, "Emmitt"));
        _customers.Add(new Customer(2, 3, "Riley"));
        _customers.Add(new Customer(3, 36, "Anja"));
        _customers.Add(new Customer(4, 37, "Chris"));
    }
    public static Customer Find(int id){
        return _customers.Find(delegate(Customer customer){
        	return customer.ID == id;
        });
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Finding a Customer Without Windows Workflow

OK. This is going to look funny. Mostly because to code up the following logic so that the flow uses Windows Workflow plumbing requires an insane amount of code. Please keep in mind this is a stupid exercise I'm using to learn Windows Workflow and that's it.


Customer customer = CustomerFinder.Find(customerID);
if (customer != null)
    Console.WriteLine("You found {0}", customer.Name);
else
    Console.WriteLine("You didn't find any customers...");

1
2
3
4
5

The entire app looks like this, fairly straightforward:


using System;
using System.Collections.Generic;

namespace NoWorkflow {

    public class Driver {
        public static void Main(string[] args){
        	try{
        		int customerID;
        		if (args == null ||
        			args.Length == 0 ||
        			!Int32.TryParse(args[0], out customerID)) {
        			Console.WriteLine(
        			@"You forgot to put only one customer id on the command line!");
        			return;
        		}
        		Customer customer = CustomerFinder.Find(customerID);
        		if (customer != null)
        			Console.WriteLine("You found {0}", customer.Name);
        		else
        			Console.WriteLine("You didn't find any customers...");
        	}catch(Exception ex){
        		Console.WriteLine(ex.ToString());
        	}
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

OK, Same Thing With Windows Workflow Foundation(No Designer Flavor)

Since I did this without the designer, I have no pretty picture to show. I have code and that's it. The way the designer in Visual Studio splits code files into partial classes, is good but can be confusing if you're trying to follow the flow. I've named everything appropriately(I hope) so that reading this code is a little easier and there is only one code file for the workflow, FindCustomerWorkflow:


using System;
using System.Workflow.Activities;

namespace HelloWorld {
    public class FindCustomerWorkflow : SequentialWorkflowActivity {
        private int _customerID;
        private Customer _customer;
        public int CustomerID{
        	get { return _customerID; }
        	set { _customerID = value; }
        }
        private CodeActivity FindCustomerActivity;
        private IfElseActivity DidWeFindTheCustomer;
        private IfElseBranchActivity IfCustomerFound;
        private CodeCondition DetermineIfCustomerFound;
        private CodeActivity WhatToDoIfWeFoundACustomer;
        private IfElseBranchActivity IfCustomerNotFound;
        private CodeCondition DetermineIfCustomerNotFound;
        private CodeActivity WhatToDoIfWeDoNotFindACustomer;

        public FindCustomerWorkflow(){
        	this.CanModifyActivities = true;

        	this.FindCustomerActivity = new CodeActivity();
        	this.FindCustomerActivity.ExecuteCode +=
        		new System.EventHandler(this.FindCustomerActivity_ExecuteCode);

        	this.DetermineIfCustomerFound = new CodeCondition();
        	this.DetermineIfCustomerFound.Condition +=
        		new System.EventHandler(
        			this.DetermineIfCustomerFound_Condition);

        	this.WhatToDoIfWeFoundACustomer = new CodeActivity();
        	this.WhatToDoIfWeFoundACustomer.Name = "WhatToDoIfWeFoundACustomer";
        	this.WhatToDoIfWeFoundACustomer.ExecuteCode +=
        		new System.EventHandler(
        			this.WhatToDoIfWeFoundACustomer_ExecuteCode);

        	this.IfCustomerFound = new IfElseBranchActivity();
        	this.IfCustomerFound.Activities.Add(this.WhatToDoIfWeFoundACustomer);
        	this.IfCustomerFound.Condition = this.DetermineIfCustomerFound;

        	this.DetermineIfCustomerNotFound = new CodeCondition();
        	this.DetermineIfCustomerNotFound.Condition +=
        		new System.EventHandler(
        			this.DetermineIfCustomerNotFound_Condition);

        	this.WhatToDoIfWeDoNotFindACustomer = new CodeActivity();
        	this.WhatToDoIfWeDoNotFindACustomer.Name = "WhatToDoIfWeDoNotFindACustomer";
        	this.WhatToDoIfWeDoNotFindACustomer.ExecuteCode +=
        		new System.EventHandler(
        			this.WhatToDoIfWeDoNotFindACustomer_ExecuteCode);

        	this.IfCustomerNotFound = new IfElseBranchActivity();
        	this.IfCustomerNotFound.Activities.Add(this.WhatToDoIfWeDoNotFindACustomer);
        	this.IfCustomerNotFound.Condition = this.DetermineIfCustomerNotFound;

        	this.DidWeFindTheCustomer = new IfElseActivity();
        	this.DidWeFindTheCustomer.Activities.Add(this.IfCustomerFound);
        	this.DidWeFindTheCustomer.Activities.Add(this.IfCustomerNotFound);

        	this.Activities.Add(this.FindCustomerActivity);
        	this.Activities.Add(this.DidWeFindTheCustomer);
        	this.CanModifyActivities = false;
        }
        private void FindCustomerActivity_ExecuteCode(
        	object sender, EventArgs e){
        	_customer = CustomerFinder.Find(this.CustomerID);
        }
        private void DetermineIfCustomerFound_Condition(
        	object sender, ConditionalEventArgs e){
        	e.Result = (this._customer != null);
        }
        private void WhatToDoIfWeFoundACustomer_ExecuteCode(
        	object sender, EventArgs e){
        	Console.WriteLine("We found {0}!", _customer.Name);
        }
        private void DetermineIfCustomerNotFound_Condition(
        	object sender, ConditionalEventArgs e){
        	e.Result = (this._customer == null);
        }
        private void WhatToDoIfWeDoNotFindACustomer_ExecuteCode(
        	object sender, EventArgs e){
        	Console.WriteLine("We did not find a customer ...");
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

And the App That Runs the Workflow


using System;
using System.Collections.Generic;
using System.Threading;
using System.Workflow.Activities;
using System.Workflow.Runtime;

namespace HelloWorld {
    public class Driver {
        public static void Main(string[] args) {
        	try {
        		int customerID;
        		if (args == null ||
        			args.Length == 0 ||
        			!Int32.TryParse(args[0], out customerID)) {
        			Console.WriteLine(
        			@"You forgot to put only one customer id on the command line!");
        			return;
        		}
        		AutoResetEvent waitHandle = new AutoResetEvent(false);
        		WorkflowRuntime runtime = new WorkflowRuntime();
        		runtime.WorkflowCompleted += delegate {waitHandle.Set();};
        		Dictionary parameters =
        			new Dictionary();
        		parameters.Add("CustomerID", customerID);
        		WorkflowInstance workflow =
        			runtime.CreateWorkflow(typeof(FindCustomerWorkflow), parameters);
        		workflow.Start();
        		waitHandle.WaitOne();
        	} catch(Exception ex) {
        		Console.WriteLine(ex.ToString());
        	}
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

Points of Interest

The waitHandle trick I got from the book.  I didn't include this at first, and it took me a while to figure out why I wasn't seeing any output.  The app was exiting before the thread running workflow instance completed.  Adding the waitHandle blocks the console and waits for one of these to get called waitHandle.Set(), which, in the example, happens when a WorkflowCompleted event is fired from the workflow runtime.

Originally I didn't name any of the activities in the ctor of the FindCustomerWorkflow class. However not doing so was resulting in this exception: System.Workflow.ComponentModel.Compiler.WorkflowValidationFailedException: The workflow failed validation. Through trial and error I narrowed down the violation to the CodeActivities added to the IfElseBranchActivities.  After I added a name, they were good to go.  This isn't a problem in the designer as it flags the violation for you.

What I Learned

At some point early in my career I decided to see what it takes to build a windows form using Visual C++ 6 without the designer, just straight code using straight Win32 calls, a hello world example if you will.  It was a TON of code.  It made me appreciate how much the form designer helps developers and at that time, very appreciative of the fact that I was a visual basic developer(no laughing please), and didn't have to deal with one line of the monkey code needed to create windows forms. 

The same thing goes for Windows Workflow Foundation.  Developing without the designer is insane and you'd be a idiot for doing so.  As a result of how painful it was to create the console example, this will be the only non-designer generated code example I post.  There are a lot of possibilities for the designer too, apparently it can be hosted in your apps which seems like it might open up the possibilities for regular business users having the ability to create their own workflows without having to know how to write code.  We'll see.

Mind Mapping + Thoughtex = Cool Ass Shizzel

I found this cool WPF app today on Thoughtext.net, and I can't remember how I got to the site but check out this screenshot:

It's a mind mapping tool that has a unique twist, it hits the internet(and your local filesystem) for anything similar to an idea you put down on the map.  Here's a closeup of what's on the left side:

Buying Software Feature by Feature

OK.  Here's my idea for a new business model.  This is inspired by the Pay Per View model I have with my Comcast.  Paying for software by the "feature".  And when I say feature, I literally mean that. 

The Need

Let's say you want to by a tool that let's you create documents, for the purpose of this example, those will be Microsoft Word documents.  Let's say the basic features you require are Bold, Italics, and Underline.  So you'll need the core features needed to run the application, basic stuff that ever windows app has, File, Edit, View, blah blah blah. 

A Monthly Fee Maybe?

What if you had to pay 5.95/month just for Microsoft Word, the application, that didn't do anything, except allow you to choose the features you wanted to use?  Need the ability to make something bold? Go to the feature menu(built into the application), and select the Bold feature.  You already have an account setup with Microsoft or a 3rd party vendor, and the credit card info saved to the site.  So selecting Bold turns that feature on.  Need it just for today? add .25 cents to my current months charges.  Make sure to turn bold off in 24hours, within that 24hours I can Bold to my hearts content.  After 24 hours, if I bold again, maybe I get prompted to upgrade to the Monthly Bold feature which only costs 1 dollar per month.  Well how can you afford NOT to do it, sure, sign me up!

Where is this Coming From??

Typically, in Microsoft Word I use, and I'm totally guessing, about .1% of all of the features in the software. That's right, point one percent.  In my little blog editor, I use FckEditor, and the toolbar strip had most of it's features removed cuz I just don't use any of them. 

I just downloaded Toad for Sql Server, I had never heard of it, and never needed anything like that.  And actually, when I did need something like that, I use QueryExpress.  Simple, lightweight, free.  I can't beat that with a stick.  I don't use designers for SQL objects except for tables and complicated views.  Need a stored proc? Open QueryAnalyzer/QueryExpress and write one! I don't need a designer to write a stored proc, function or anything else code related.  The point is that, the software is expensive(and yes I know there's a free version), and sometimes I just want the cool whiz bang feature and not the whole frickin package.  And I'd be willing to pay for it right when I need it.

CodeRush is a good example of how I like to purchase software. Brand new, I paid 249.99.  Every year I renew my subscription for 99 bucks.  The support is phenomenal.  I will continue to pay 99 bucks a year for the product until a better one comes along and takes my money.  That's 8.25 per day for the supported privelege of using their software. 

Red Gate is the same way.  I paid a lot for Sql Compare the first time I purchased, but every year they will continue to 99 bucks, until I have a compelling reason to not give them my money. Actually now that I'm thinking of it, Red Gate's model more closely resembles my idea.  If you used some of their other tools, they plug into the same application framework.  So if you're using Sql Compare or Sql Data Compare, they both use the same thing, so essentially you're kind of using the software on a feature by feature level.

I Want More

I want the choice of what features I get in my software, and I want to determine the length of time I'll use those features, and I'll pay for the software that way.  If I have a 3 month contract, I may only need a tool and/or feature for those 3 months and then never use it again that year.  So why can't I get the software and rent it for 3months and then discard it.  I don't want all bells and whistles, and I don't want a one year license. 

I think a lot of business is missed by locking out customers who would pay some money to use some of the software some of the time, but not the full price of a software package full of features they'll never use.

Images, Images, Everywhere

I had a need to make working with images on blog posts easier.  I wanted to be able to upload files and then have available to me, the ability to show a thumbnail of the image that you could click on and then see a cool lightbox zoom of the pic.  Anyway, I kept having to create two images, the thumb and the biggin.  Then writing the anchor tag so that it's lightbox friendly.  Anyway,without getting into too many of those details, I wanted a quick "macro"-ish type of thing.  I already have $snippet{666}, which is useful for embedding formatted snippets in my posts.  So I added $image which basically is a macro that takes the database id of an image i've previously uploaded and saved to the database.  Here is a sample image.  Click on it for the neat lightbox effect:

Ya, that's me a few months ago in Cancun. 

This is kind of a rough draft.  I'm having a major performance problem right now.  Listing posts takes forever. Most of the time I get request timeouts which is way sub optimal.  My image macro consists of requesting an image by id.  I look in a list of image instances I've saved in cache, if it's found there i get that one, otherwise i hit the database.  Next I look to the filesystem for an image that's saved there that represents what I'm looking for.  Images that are requested are always written to the filesystem, if they are found there, those are written to the client requesting them.  If they are not found, a new image of whatever size is generated to the filesystem, then that file is written back to the client.

I Have An Idea! Let's Use Prototype!!

On my blog I use the title of a post as the entry name that will also appear as the friendly url pointing to that post. Since rolling my new blog I've been doing this by hand, meaning I write the title of a post then i write an entry name for it, the pattern is all lowercase, no special characters like !$?# or commas, periods and pertty much anything that's not an alpha char. With the help of prototype I wrote a little javascripty thing that does this on the client side. Here's the javascript:


$('post').observe('keyup', function(){
  $('entryname').value = this.value.toLowerCase().gsub(/ /,'-').gsub(/[!@#$%^&*(),./?;:\'\"]/, '');
});

1
2
3

 

As you can see by the little demo it's just doing some simple replacements on character's I don't want appearing in the url.

More Fluent Interfaces Over The CodeDom

Need to Generate Code - What's Expected

I'm playing around with fluent interfaces tonight. I'm refactoring some code generation stuff I did before and needed to be able to generate the following line:


System.Data.DataColumnCollection columns = result.Columns;

1

 

Using the Raw CodeDom

 


method.Statements.Add(
  new CodeVariableDeclarationStatement(
    typeof(DataColumnCollection),
    "columns",
    new CodePropertyReferenceExpression(
      new CodeVariableReferenceExpression("result"),
      "Columns")));

1
2
3
4
5
6
7

 

Using the Dominator

 


method.Declare("columns")
  .As(typeof(DataColumnCollection))
  .Assign("result", "Columns");

1
2
3

 

I still am not completely convinced that I need to be doing this, but it's kinda fun. One thing for sure is my CodeDom-FU is getting very strong, way too much spare time on my hands :)

Related Post: What is the Dominator

My NAnt Castle Helper Script

I've installed Release Candidate 3 of the Castle stack. It installs project templates for MonoRail and ActiveRecord which are helpful. I never use them though. The problem is that if I do an example project, I generally like to have all assemblies(aside from the .NET framework assemblies) that the project requires included in the same folder structure as the project. And frequently I use the latest successful builds from the buildserver or I'll build my own off the trunk. Whatever the reason, I never use the installed project templates.

My usual project structure includes a lib directory that contains all assemblies needed by the project and/or solution. Copying all of the Castle dependencies for only what I'm working on can be a little annoying, so I wrote this NAnt script to help:



    
    

    
        
    

    
        
        	
        		
        		
        		
        		
        		
        		
        		
        		
        		
        		
        	
        
    

    
        
        	
        		
        		
        		
        		
        		
        		
        		
        		
        		
        		
        	
        
    

    
        
        	
        		
        		
        		
        		
        		
        		
        		
        		
        	
        
        
        	
        		
        		
        		
        		
        		
        		
        	
        
    

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

 

It's just a scrapper right now, something to work through some ideas. I'm thinking there's no reason why I couldn't add some tasks for generating the entire projects, set up the way I normally set them up.

ActiveRecord, Images, and Lightbox

Persisting Images With Castle ActiveRecord

Let's assume we have this table in our database:


if object_id('example_image') is not null
    drop table example_image
go

CREATE TABLE [dbo].[example_image](
    [image_id] [int] IDENTITY(1,1) NOT NULL,
    [data] [image] NOT NULL,
    CONSTRAINT [PK_example_image] PRIMARY KEY CLUSTERED (
        [image_id] ASC
    )
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

1
2
3
4
5
6
7
8
9
10
11

 

The ActiveRecord class that represents that table would like this:


using System;
using Castle.ActiveRecord;

namespace ActiveRecordImages.Data
{
    [ActiveRecord("example_image")]
    public class ExampleImage : ActiveRecordBase
    {
        private int _iD;
        [PrimaryKey(PrimaryKeyType.Identity, "image_id")]
        public int ID
        {
        	get { return _iD; }
        	set
        	{
        		_iD = value;
        	}
        }
        private byte[] _data;
        [Castle.ActiveRecord.Property("data", "BinaryBlob")]
        public byte[] Data
        {
        	get
        	{
        		return _data;
        	}
        	set
        	{
        		_data = value;
        	}
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

 

Given that code, the act of saving an image is as simple as:


string imagePath =
  Path.Combine(Environment.CurrentDirectory, @"..\..\chrisandemmitt.jpg");
ExampleImage image = new ExampleImage();
image.Data = File.ReadAllBytes(imagePath);
image.Save();

1
2
3
4
5

 

The Demo

The demo is a thumbnail image of me and my son. Click on the thumbnail to see the lightbox enhanced version. The images are pulled directly from the database. View live demo here.

This is a simple example of delivering an image from a database. I've included the necessary assemblies in the solution that can be downloaded here(the download is almost 3 megs because I've included all of the Castle RC3 dependencies).

There are 3 projects in the zip. ActiveRecordImages.Data has the ActiveRecord class and a test class. ActiveRecordImages.Web is a standard run of the mill asp.net and web forms example. ActiveRecordImages.MonoRail.Web is the MonoRail version of the same demo which can be viewed here.

The resize code in the demo was stolen directly from here.

Oh Ya, That's Why I Didn't Use Refly

Refly was a little bit of overkill for what I needed. Here is how I got the same customer example from my Dominator post generated using Refly:


NamespaceDeclaration nsdecl = new NamespaceDeclaration("My.Data");
ClassDeclaration cdecl = nsdecl.AddClass("Customer");
FieldDeclaration field = cdecl.AddField(typeof(string), "_firstName");
cdecl.AddProperty(field, "FirstName", true, true, false);
CodeGenerator generator = new CodeGenerator();
string outputpath = Path.GetTempPath();
generator.GenerateCode(outputpath, nsdecl);
string fileName = Path.Combine(outputpath, @"My\Data\Customer.cs");
Console.WriteLine(File.ReadAllText(fileName));
1
2
3
4
5
6
7
8
9
A bunch of work was put into Refly and it's a great library, but I wanted something a little simpler to use. It seems as though it only outputs a folder path. When I started on the HyperActive one of my needs was to be able to generate a CodeCompileUnit from a namespace. Initially I don't see a method of doing this except for falling back to the raw CodeDom. I'm gonna keep messin around with it though, it's a very complete library.

Here is some test code with Refly included:ReflyTester.zip

What is the Dominator?

I went through some different iterations trying to figure out a good way of generating code. I've seen some generators that used text files and did keyword replacements. I didn't want that baggage. And I wanted to be able to generate code in any .NET language. Having separate versions of text templates, a set for every .NET language is lame.

CodeSmith rocks as a templating engine. The main setback is that it's not free. Another setback to using CodeSmith was that you end up generating templated code and limiting any extension points to the generated code(without actually modifying the templates). Essentially I wanted to stay away from string replacement text file templates.

Since I'm pretty familiar with the CodeDom I set out to roll my own. Here is the class diagram for what I came up with, the Dominator:

An Example: My.Data.Customer

Here's a good example of what the Dominator can do. Let's suppose want to generate some code in the namespace My.Data. The namespace will contain a single class, Customer, with string property FirstName that is read/write. The class should look like this:


namespace My.Data {


    public class Customer {

        private string _firstName;

        public virtual string FirstName {
            get {
                return this._firstName;
            }
            set {
                this._firstName = value;
            }
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Generating Code with System.CodeDom

If I was just to stay within the confines of System.CodeDom, the code needed to write the Customer class would look like this:


CodeNamespace nsdecl = new CodeNamespace("My.Data");
CodeTypeDeclaration cdecl = new CodeTypeDeclaration("Customer");
CodeMemberField field = new CodeMemberField(typeof(string), "_firstName");
cdecl.Members.Add(field);
CodeMemberProperty prop = new CodeMemberProperty();
prop.Name = "FirstName";
prop.Attributes = MemberAttributes.Public;
prop.Type = new CodeTypeReference(typeof(string));
prop.GetStatements.Add(
    new CodeMethodReturnStatement(
    new CodeFieldReferenceExpression(
    new CodeThisReferenceExpression(), "_firstName")));
prop.SetStatements.Add(
    new CodeAssignStatement(
    new CodeFieldReferenceExpression(
    new CodeThisReferenceExpression(), "_firstName"),
    new CodeSnippetExpression("value")));
cdecl.Members.Add(prop);
nsdecl.Types.Add(cdecl);
CSharpCodeProvider codeProvider = new CSharpCodeProvider();
codeProvider.GenerateCodeFromNamespace(nsdecl, Console.Out, new CodeGeneratorOptions());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Bring on the Dominator

Generating the same class using the Dominator would look like this:


ClassDeclaration cdecl = new ClassDeclaration("Customer");
cdecl.AddProperty("FirstName", "_firstName", typeof(string));
new CodeBuilder().GenerateCode(Console.Out, "My.Data", cdecl);
1
2
3

Generating a Castle ActiveRecord Class

The expected generated code is this:


public class Customer : Castle.ActiveRecord.ActiveRecordBase {
}
1
2
Using the System.CodeDom to generate the expected code looks like this:

CodeTypeDeclaration cdecl = new CodeTypeDeclaration("Customer");
cdecl.BaseTypes.Add(
    new CodeTypeReference("Castle.ActiveRecord.ActiveRecordBase",
    new CodeTypeReference("Customer")));
new CSharpCodeProvider().GenerateCodeFromType(cdecl, Console.Out,
    new CodeGeneratorOptions());
1
2
3
4
5
6
The Dominator swoops in with a fluent interface to generate the same thing like this:

ClassDeclaration cdecl = new ClassDeclaration("Customer")
    .InheritsFrom("Castle.ActiveRecord.ActiveRecordBase", "Customer");
new CodeBuilder().GenerateCode(Console.Out, cdecl.ToCodeDom());
1
2
3

Code

Here's some sample code that demonstrates the code in this post, very simple stuff, download it here.

Before creating the Dominator I checked out Refly and for some reason that I can't remember today, it didn't work for me. It sure looks like it does what I need but I need to look at it some more.

 
Author: , 0000-00-00