www.codinghillbilly.com   kyle.baley.org  Subscribe / Contact
 
 
 
 
LATEST POSTS
Tuesday, December 30, 2008

As the year lurches to a close, I'ma gettin' all reflective and stuff. The last two years have been pretty key for me career-wise. Looking back, you'd almost think I had a plan. In general terms, 2007 was learning hillbilly as I put some effort into a learning regime. More importantly, I got on track for continuous learning.

2008 was community hillbilly as I started connecting more with my online brethren and sistren both in attending, and speaking at, conferences and user groups. I'd enumerate them but if you care that much, you can click on the Community category to get a summary. Plus I don't want to give my wife and daughter any ammunition for the "you're never home" discussions that have abated of late.

It also marked the launch of the Bahamas .NET User Group which is something I'm particularly proud of. And this segues nicely into my 2009 plans.

The coming year will be introvert hillbilly but not in a bad way. I won't be withdrawing from what I like doing, which is to connect with other people in the industry. But looking over my travel schedule of the last year, and it wasn't nearly as intense as many people I talk to (especially my co-author, who essentially toured Canada coast-to-coast, as well as India, the US, and somewhere in Europe, all for community work), I'm looking to put some direction to my efforts.

So my two over-arching goals are: continue doing community work, and travel less. Luckily, we work in an industry where this is not even possible, it's viable.

Community comes in two flavours: global and local. For the last year and a bit-ish, I've been skewing more on the former. For 2009, my focus is going to be on the latter. The Bahamas is an untapped treasure trove, I think. The proximity to the US certainly helps and it's a nice, small, manageable market.

The industry is so young here that it would be remiss of me not to take advantage of the opportunity. The people want to learn but don't yet have years of bad habits ingrained into them. Not two weeks ago, I kid you not, I had a request on how to access data without using stored procedures. I just about cried with joy. I'm planning to have the e-mail bronzed and framed.

So the intent is to spend more time closer to home. Along with regular user group meetings, I'm hoping to try some half-day to day long seminars on various subjects, almost certainly starting with fundamentals. And for those of you following my attempts at a conference, keep your Bermuda shorts on. This winter is too early but it's still a possibility for 2009/2010 winter.

Which is not to say I'll be ignoring the global aspect, at least not altogether. As much as Brendan wants to maintain his subscription level, I plan to stick around here for a while yet, if only to see if I can top my single favorite post which I made so early in my tenure. I also have a raft of Dimecast ideas to sift through as well as a few other plans, each more nebulous than the last.

Finally, while I recognize that there is little by way of "official CodeBetter" advice here, I'm posting this anyway. I mention this partially to pre-empt the inevitable "stay on topic" comments because responding to them requires more thought, tact, and, most importantly, time than you might think. But mostly because I think there is a parable or two buried in here somewhere for others. In any case, I have no intention of turning this place into a BahaNET bulletin board (other plans are afoot in that arena). Just taking advantage of the latitude usually granted to year-end bloggers.

Kyle the Communal

Wednesday, December 24, 2008

I'm such a nice brother. For Christmas this year, I got my older brother an MVC application to help him with some of his community work for his land surveying responsibilities. In return, I've got a nice little app that's got at least two more posts in it and probably just as many Dimecasts.

I'll describe the domain that led to this post so's I don't have to change any of the sample code I'll inevitably paste in here.

We have land surveyors. Each one, over the course of his or her career, will use "monuments". Which as far as I can tell are those big sticks with orange paint on the end you often see stuck into the ground. Why are they called something as grandiose as monuments? My brother likes to say it's the second oldest profession in the world (and given what some of them charge, they aren't far removed from the first). So in ye olde dayes, instead of using sticks, they'd erect monuments. See, the Pyramids and the Eiffel Tower aren't actually the grand constructions you think they are. They're just the result of diligent land surveyors trying to find the property line.

Anyway, I'm getting off track (though to my credit, I took longer than usual to do so this time). A surveyor will use monuments/sticks and they each one has an official label. These days, the label appears to be related to the company you work for. So all surveyors currently working for Tango & Cash Geomatics will use monuments labelled TC. If a surveyor jumps ship over to Rowan & Martin Land Surveyors, he'll then use a monument labelled RM. And so on and so forth.

Here are the tables involved:

image

A pretty standard many-to-many relationship. But where this differs from almost all of the NHibernate examples you see is that there is data attached to the relationship. I.e. it's not enough that we know Sandy McLean used monument TH, we want to know the period in which he and/or she used it.

Now, I'm using Fluent NHibernate here because, well, it's my app. I don't to answer to you people. And the question arose, how do I map this to my domain?

I've skipped an obvious precursor question though. Namely: what does the domain look like. This may not be crazy obvious at first glance. Of course, there is a Surveyor object and a Monument object (sparse as it is at the moment). And you might think that a surveyor has a collection of Monuments but that's not the case.

imageIn fact, as was pointed out to me by a few people (acknowledgements are at the end), the relationship 'twixt Surveyors and Monuments is a domain concept in its own right. That is, we need another object to represent that relationship. A MonumentAssignment, for example, which contains the properties you might expect: Monument, Surveyor, YearStarted, and YearEnded.

Mapping the MonumentAssignment with Fluent NHibernate was relatively easy ("relatively" being the key word; this *is* NHibernate we're dealing with):

public class MonumentAssignmentMap : ClassMap
{
    public MonumentAssignmentMap( )
    {
        WithTable( "SurveyorMonument" );
        Id( x => x.ID, "SurveyorMonumentID" );
        References( x => x.Surveyor, "SurveyorID" )
            .WithForeignKey( "SurveyorID" )
            .FetchType.Join( );
        References( x => x.Monument, "MonumentID" )
            .WithForeignKey( "MonumentID" )
            .FetchType.Join( );
        Map( x => x.YearStarted );
        Map( x => x.YearEnded );
    }
}

The References calls required some Googling. They basically allow you to map a class to multiple tables.

Now to the Surveyor map. At first, I thought this required the use of HasManyToMany because, y'know, this is a many-to-many relationship. That's not quite true. Yes, in one sense, a Surveyor has many Monuments and a Monument can have many Surveyors but in fact, a Surveyor isn't directly related to Monument. It is related to MonumentAssignments. In a many-to-many relationship, this intermediate object is usually left out because it doesn't have any meaning other than to relate two objects. In our case, it has a temporal meaning outside of just the relationship itself.

So now, our Surveyor has a many-to-one relationship with MonumentAssignment. Monument, in turn, also has a many-to-one relationship with MonumentAssignment. With that in mind, we can map our Surveyor class thusly:

public class SurveyorMap : ClassMap
{
    public SurveyorMap() {
        WithTable("Surveyor");
        Id(x => x.ID, "SurveyorID");
        
        Map(x => x.CommissionNumber);
        Map(x => x.Surname);
        Map(x => x.GivenNames);
        Map(x => x.YearCommissionGranted);

        HasMany( x => x.Monuments )
            .Cascade.AllDeleteOrphan( )
            .WithKeyColumn( "SurveyorID" );
    }
}

That's it. In the end, it wasn't nearly as complicated as it first seemed. Of course, two key ideas led to this (again, relative) simplicity:

  • MonumentAssignment is a domain concept in its own right
  • Surveyor has a many-to-one relationship with MonumentAssignment, *not* a many-to-many relationship with Monument

This post was brought to you by Colin Jack, Chad Myers, Shane Courtrille, James Gregory and the number 12.

Merry Christmas!

Kyle the Monumental

Thursday, December 18, 2008

This post would probably be more entertaining if I dictated it because then you could all have a good chuckle on my Canadian pronunciation on "route". For the record, it is supposed to be pronounced "root", which is clean, terse, and polite. Not "r-OW-t", which, if done correctly (like in Texas), requires your jaw to be double-jointed.

To date, routes have always been something I've kinda, sorta had figured out. I use the default ones as much as possible and when I stray from that, I end up plugging away until they work for my current situation. Sometimes this involves creating hard-coded ones, other times simply re-arranging the order has the desired effect.

Oh sorry, did I just make you throw up a little?

But now it's time to tackle these little beasties head-on, thanks in no small part to Ben Scheirman's recent addition to MvcContrib, wherein you can test your routing strategy with a call as simple as:

"~/Still/AddIngredient/Dandelion".Route().ShouldMapTo( c => c.AddIngredient( "Dandelion" ) );

First, let's take a look at a concrete example to define the problem space. Here is a sample route table:

routes.Add(new Route("{controller}/{action}", new MvcRouteHandler()) 
   { 
       Defaults = new RouteValueDictionary(new {action = "Index"}), 
   }); 

routes.Add(new Route("{controller}/{id}/{action}", new MvcRouteHandler()) 
   { 
       Defaults = new RouteValueDictionary(new {action = "Index", id = ""}), 
   }); 

With something like this, and in fact, with routes in general, order is important. More so than I'd like, quite frankly. A URL like ~/Still/Mix will match the first one but one like ~/Still/101/Dismantle matches the second. Note that in this scenario, the default values in the second one seem meaningless. If no action or id are provided, the first route will be used so there is no need for defaults. We'll come back to that.

It should also be noted that the routes come into play not only when parsing URLs but when generating them. This is actually what led to this post. In one of our controllers, we used the RedirectToRoute result. For those of you that can't parse out Pascal casing, this just allows you to redirect to another route from within an action.

The final line of that action was: return RedirectToRouteResult( "Ferment", "Hooch", new { id = 123 } );

Given the above route table, what do you suppose would be the URL that this generates? If you say ~/Hooch/123/Ferment, give yourself a pat on the back. But it'll be just because you're a nice guy/gal because it's actually wrong. The correct answer is: ~/Hooch/Ferment?id=123.

Here's the thought process as I imagine it to be. The route generator checks the first route in the dictionary, which is "{controller}/{action}" and says "Can I make these values match that route?" Well, of course it can. It's got a controller and an action. Everything else can be tacked on in the query string.

This URL turned out to be a bit of an issue for me because of another problem that this uncovered. At some point in our controller, we reference ViewContext.RouteData.Values["id"]. And with the id as part of the query string, this bit of code returns null. The debugger shows that for a URL of ~/Hooch/Ferment?id=123, the RouteData collection contains two values: controller = Hooch and action = Ferment. The id is nowhere to be found, at least by the RouteData.

So how *do* we get the RedirectToRouteResult to generate a shinier URL? Why, we fiddle with the routes until they work, of course! It's the principle that bug fixing is based upon.

I jest, of course. But only partly. We do need to fiddle with the routes but we should really write a test to verify that the ViewContext.RouteData is being set properly. Unfortunately, this isn't easy without some funky mocking.

One would think that you could just examine the RedirectToRouteResult coming back from the controller action but alas! It appears correct. I.e. It has all three components (controller, action, and id) set properly. I suppose the ViewContext.RouteData is set somewhere else in the bowels of the framework. So I'll skip the test because I'm already over my allotted time. Plus we're using Preview 5 still and this could very well be different in the beta.

Let's reverse the order of the routes in the table and see what happens. Here's what that looks like:

routes.Add(new Route("{controller}/{id}/{action}", new MvcRouteHandler()) 
   { 
       Defaults = new RouteValueDictionary(new {action = "Index", id = ""}), 
   }); 

routes.Add(new Route("{controller}/{action}", new MvcRouteHandler()) 
   { 
       Defaults = new RouteValueDictionary(new {action = "Index"}), 
   }); 

This immediately caused problems. The URL ~/Still/Mix now maps to the first route. That is, it is invoking the Index action on the Still controller with an id of Mix. Again, the defaults are screwing us over like some sort of mot--...actually, let's leave the simile out of that one. Part of the reason for our problem is because the {action} is in a different place in each route. But with the defaults in place, this set up will essentially ensure the second route will never be used.

Instead, we need to take out the defaults altogether, like so:

routes.Add(new Route("{controller}/{id}/{action}", new MvcRouteHandler()) 
   { 
       Defaults = new RouteValueDictionary(new {}), 
   }); 

routes.Add(new Route("{controller}/{action}", new MvcRouteHandler()) 
   { 
       Defaults = new RouteValueDictionary(new {action = "Index"}), 
   }); 

Now we're cooking. The URL ~/Still/Mix no longer matches the first route because we haven't provided an id. But ~/Still/101/Dismantle does meet the requirements. Furthermore, generating a RedirectToRouteResult( "Ferment", "Hooch", new { id = 123 } ) now gives us a URL in the format: ~/Hooch/123/Ferment. Again, this is because it examines the first route in the table and finds a match.

I've omitted quite a bit of context here, like why we have {id} second in the route and why we're referencing ViewContext.RouteData.Values["id"] in the first place. There is a good reason for all of that. At least as far as you know...

Kyle the Evasive

Friday, December 12, 2008

Yes, I know how it's spelled. But "GAC" doesn't fully demonstrate the wretching feeling I typically get when I'm dealing with it. So I'm trying my hand on some onomatopoeia.

I was recording a Dimecast this morning on S#arp Architecture because it's an awesome piece of work. But one of the things it (currently) does with the VS template is create an MVC project. That is, a project explicitly linked to the MVC template. And if you don't have that template installed, you get at least one error.

So I went about installing the MSI because I was also feeling some pain not having the various MVC item templates anyway. Tried the S@arp template again and all was well for the three hours it took me to record that ten minute webcast.

Then I switched over to the MVC project I'm working on. It uses an older version of MVC, around preview five-ish. Load up the site and an error appears indicating something like "Html.UrlAction doesn't take 5 parameters" or "Exception: Maybe you ought to take up another line of work" or something. Clearly it is using the assemblies from the GAC rather than the bin directory like I told it.

The best option running through my head is to uninstall the assemblies from the GAC. I get to keep my templates but don't have to deal with versioning issues. Unfortunately, as Phil notes, you can't uninstall an assembly from the GAC if it was installed via MSI. Derik Whittaker says I could probably fix it by removing the MVC stuff from the web.config and re-adding it. Too hacky, says I.

My solution: whine to someone who can possibly solve my problem. In this case, the accommodating Billy McCafferty who assures me that S%arp will no longer have a hard dependency on the MVC templates in the near future. Which means I can uninstall MVC and go back to working with my various projects using different versions of the assemblies.

It's temporary though. At the moment, I can't open up the project I created with S~arp anymore. It gives an error: The project type is not supported by this application. (By the way, the solution to this is to open the .csproj file in a text editor and remove {603c0e0b-db56-11dc-be95-000d561079b0}; from the list of ProjectTypeGUIDs at the top.) In any case, S&arp isn't the only project I've dealt with that is explicitly linked to the MVC templates.

I imagine in order to even get the templates to work, the assemblies it uses need to be somewhere relatively global. Whatever, all I know as a user is: I don't want them in my GAC but I still want the project and item templates to work.

Final note: S*arp makes use of T4Toolbox to do some scaffolding. This project doesn't appear to play well with VisualSVN if you happen to have it installed. The problem I believe is with T4Toolbox but don't quote me. I had to uninstall VisualSVN to get the scaffolding to work. Seems related to similar issues with ActiveWriter.

Kyle the Versioned

Wednesday, December 10, 2008

There have been numerous posts about hiring the "right" people. One of the more ideal characteristics is a passion for the industry and for learning new techniques. But as I look at more and more code written by these zealots (and I don't mean that to be derogatory because I count myself as one of them) and because I have been thinking about brownfield applications lately for some reason, a kind of pattern seems to emerge in projects written by them.

The way to recognize this pattern is if you can tell what the developer has been learning or reading about from scanning various parts of the codebase. Here's a scenario:


New guy: How are we handling dependencies in the code?

Eager and enthusiastic veteran: Oh, it's awesome, man! At first, we were just newing shite up. Then I went to JP's course and he had this pretty cool hand-rolled IoC container so we started using that in the service layer. Eventually, we took the plunge and brought in Windsor. We don't use it everywhere but some of the newer presenters do. But then we saw the new stuff in StructureMap so this one part of the app is using that.

NG: So you have four different ways to get new instances of a class?

EaEV: Yeah, but don't worry your purty li'l head over it none. We'll be standardizing on one soon.

NG: Oh yeah? Which one?

EaEV: Well, it *was* going to be StructureMap but NInject looks pretty cool...


The point being: How do you handle implementing new techniques as you're learning them? You have a web application built by a team that still things they're coding in classic ASP and now you want to move it to an MVP pattern.

Naturally, for new screens, you can do this pretty easily. But as the code grows, you eventually get to a mishmash of new MVP screens and old code-smackdab-in-front screens that everyone treats like your disfigured, but still oddly attractive sister/cousin who's kept locked in the basement except during family reunions/speed-dating sessions.

What's worse, the problem is exacerbated as new techniques come in vogue and as new people are brought on to the project.

Also, for the most part, we usually don't want to go refactorin' just for the intellectual exercise. Most of the time, I like to move to a pattern or implementing some new technique on an as-needed basis. Which means you don't overhaul the entire application just because you're working in one area. If your admin section is working, I say leave it alone, despite your psychological issues with DataSets. (But at the first sign of bugs or new features in that area, bring in your hacksaw and have at 'er.)

In any case, it's cute little dichtomy. You certainly don't want to keep doing things the old and busted way but you are hesitant to fork part of the application down a new and inconsistent path.

And this isn't limited just to large projects. I'm experiencing it even know on an application I started earlier this year. An application, I might add, where I'm the sole developer. It's an MVC app with bits of Fluent NHibernate in it but not all the mappings have been migrated over yet.

The way to avoid this, of course, is to stop learning. But if you're one of those eager types, then let's explore some options. Unfortunately, all I've got are the thoughts in my head. And keep in mind, these are the same thoughts that evoked that little speed-dating analogy a few paragraphs up.

// TODO: Refactor this when the client stops reporting bugs and adding features

This is kind of the bare minimum you can do to at least acknowledge that some work needs to be done. It's well-intentioned but not very practical. Most of the time, we leave these comments because we want to cover our asses in case someone goes looking through the code and spontaneously goes blind. In any case, if the client isn't reporting bugs or adding features, that means no one is actually using the app.

The "Refactoring" Iteration

"That's it. I've had enough looking at this code. You, Mr. Project Manager Person, tell the client that we have deemed the application unpalatable and that we are holding it ransom until such time that it we feel warm and fuzzy about working with it again."

Don't need any limbs to count the number of times this has worked for me.

Leave it the way it is and strive for consistency

If you think you can get away with this, I applaud your ability to hire Type-B programmers.

Deal with it

This is probably my default approach. Hunker down and get your work done within the constraints laid out for you. Fix what you can, when you can. The application will morph. Try to control the shape and rate in which it does so. Knock of one head of the hydra at a time and hope three more don't grow in its place. It's probably not the most glamourous position to take but like any stance that involves lowering your standards, it's easier to attain your goals.

None of this strikes me as being all that encouraging, which is probably why this post lingered in my //TODO list for so long. I'm not sure there are any solutions to this other than to practice deep breathing exercises whenever you look at the code. Best I ever hope for is to provide value for my pay and try to learn something as I go.

Kyle the Acknowledged

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

Copyright © 2010 Kyle Baley. All rights reserved.
 
CATEGORIES
.NET General (18) alt.net (4) altnetconf (9) ASP.NET AJAX (40) ASP.NET MVC (29) Bahamas (1) Bahanet (9) BDD (1) Brownfield (18) Career (9) Castle (1) Code coverage (1) Coding Style (6) Communication (1) Community (18) Conscientious Coding (34) Continuous Integration (11) dasBlog (12) Development (16) DevTeach (4) Domain (2) Environment (4) Estimating (1) Featured (14) Flamingo (10) Games (1) Google App Engine (2) GWT (5) Hardware (6) Java (1) Javascript (7) Linq (2) Livelink (6) Lucene.NET (2) MbUnit (1) Metrics (1) Miscellaneous (24) Mocking (4) NAnt (4) NHibernate (12) NInject (1) Office (3) Office Development (6) Open Rasta (1) Patterns (5) Presenting (13) Professional Development (15) Refactoring (10) ReSharper (11) REST (2) S#arp Architecture (5) Security (3) Software (11) Sundry (18) TDD (19) Tools (21) User Interface (5) Utilities (8) Visual Studio (8) VSTO (1) Web development (12) Windows (3) Working Remotely (16) Workplace (3) Writing (4)
 
LATEST POSTS
 
POPULAR POSTS
 
 
ARCHIVE