www.codinghillbilly.com   kyle.baley.org  Subscribe / Contact
 
 
 
 
LATEST POSTS
Tuesday, May 29, 2007

OK, so why hasn't anyone told me of the rockage that is ActiveRecord? I was halfway through writing my first NHibernate XML file when Oren himself pointed me to StoryVerse, whose code is easy enough to follow that I was able to pick up a practical use of ActiveRecord without too much hassle.

Gives some context to the attribute vs. XML file debate which was passing me by. And I have to say, the thought of bypassing XML file management is pretty appealing for a hillbilly with the first signs of carpal tunnel. Data schema dependency be damned, that's some bit of magic when you can dump a bunch of attributes on a class and not write a single line of data access code.

Which brings me to the real reason for this here hootenanny. One of my classes is called FiscalSystem. Its implementation is not worth explaining except for one of its properties. To wit: StateProvince a class which in turn has a property called Country. I.E. A FiscalSystem belongs to a State/Province which belongs to a Country. (I didn't do the reverse relationship because the app is small enough that I don't need it.)

So to get a FiscalSystem's country: myFiscalSystem.State.Country.Name.

Next comes the query: Show me all FiscalSystem objects that apply to a particular country. Here's the query I tried:

    ICriterion[] criteria = new ICriterion[]
        {
            Expression.Eq( "State.Country.Id", countryID )
        };
    FiscalSystem[] systems = ActiveRecordBase<FiscalSystem>.
FindAll( criteria );

Side note: In reality, all I actually wanted on my FiscalSystem class were StateName and CountryName properties because I didn't need the semantics of a full State or Country object anywhere else. But I was unable to determine a way of having ActiveRecord look up information from more than one table (i.e. State and Country) for my FiscalSystem class. So I had to create objects for each of them.

No dice. The error is long gone now but it went something like "Could not resolve property name 'State.Country.Id'". Works if I query at the State level, but not at the Country level. Which leaves me to believe it has something to do with reflection.

Incidentally, you get a similar error if you try to bind a GridView to a list of FiscalSystem objects and use a BoundField that references State.Country.Id (or anything at the Country level). You get around that be using a TemplateField instead with Eval( "State.Country.Id" ).

My alternate solution was to dig into HQL which is amazing in its own right:

    SimpleQuery<FiscalSystem> query = new SimpleQuery<FiscalSystem>( @"
    FROM FiscalSystem fs
    WHERE fs.ParentState.ParentCountry.Id = ?
    ", countryID );
    FiscalSystem[] fiscalSystems = (FiscalSystem[])ExecuteQuery( query );

Man, there is a price to be paid living out of the loop down in the caribbean all these years.

Kyle the Impressionable

Tuesday, May 29, 2007

I couldn't tell you what the minimum requirements for Office Enterprise 2007 are but if my experience is any indication, it's higher than:

  • Windows XP Pro
  • 2.00 GHz Centrino Duo processor
  • 1 GB RAM
  • 256MB ATI Mobility Radeon X1400

Oh, it installs fine. But the hideous screen refresh time whenever I switch to Outlook and Excel's startup time are enough to have me revert back to Office 2003. And if you don't like my specs, ping me privately and I'll tell you why I'm glad I have them at all.

And credit where it's due on my last post on the Zune. You can create a playlist consisting of songs already on your Zune. You just create it on your computer first and synchronize. Despite the fact that the Zune appears to want to re-add the songs, it is smart enough to know when the song already exists on the device.

Two more downsides though. If I add a single song to my playlist, it has to verify the list's entire contents. Which takes a while for playlists in the hundreds of songs.

Second one: I think there may be a limit to the number of songs you can store in a playlist. I tried several times to synchronize one with about 2700 songs and the playlist simply didn't appear in the list. Worked with up to 1100 songs but I didn't bother checking further. See downside #1.

Saturday, May 26, 2007

Maybe it's because I'm getting on in years but I'm not much of a gadget person, especially considering my chosen profession. I've been able to get by quite nicely on Mrs. Hillbilly's cast-off devices, which come more frequently than you might imagine. And, being a technically-minded and gracious hillbilly, I'm often called upon to swear and curse at the UI designers of her latest gizmo.

Recently, that included a new Zune and a new Treo. I'll start off with the Zune because my major complaint with it is pretty easy to explain.

I can't create a playlist on it.

Oh I can transfer a playlist to it from my computer. Very easily actually. The software, which I'm guessing is a branded version of Windows Media Player but I'm too lazy to look it up, is intuitive enough. I just can't select a list of songs that are already on the Zune and create a playlist from them.

Here's the scenario: It's a 30Gb Zune and given that it could accommodate both the missus' collection and most of mine, she graciously allowed me to use some of the excess space. So I stumbled my way through and added all my songs.

Cut to a few days later: The ol' lady comes home and says to me, she says, "Codin' Hillbilly, ya gotsta git yer musick off'n this har Zoon! I canst get it to play just my songs without yer Tribute to Yodellin' mixin' it up with mah Il Divo!"

"You got it, Cabbage," says I, "I'll just add mah toons to one o' dem playlisties and you kin listen to them big throaty guys all ye want!"

So boot up the Zune and lo! All the songs I've recently added are nicely listed in a Recently Added list so I don't have to go hunting for them. But no! you are not, to the best of my knowledge, able to create a playlist on the Zune without creating it on your computer first. And when you do that, it wants to add all the songs in it to your Zune all over again.

The solution I am currently implementing (which is not to imply it is the correct one):

  • Delete all my music from the Zune (using the handy "Recently Added" list)
  • Copy all the missus' music to my computer
  • Delete the missus' music from the Zune
  • Create two playlists on my computer, one for her and one for me
  • Copy both playlists back to the Zune

Thankfully, the Zune copies things wicked fast otherwise I'd be in a pretty skunky mood right about now. And before I move on to the Treo, I'll just point out that my three-year-old Dell Digital Jukebox has an awesome interface for this. To wit: it shows up in Windows Explorer and I can drag and drop songs at will. Into both the device itself and playlists that I create on it (also directly from Windows Explorer).

The scenario for the Treo 650: Cabbage has a romantic streak in 'er and thought it'd be nice to have a certain song play whenever I called her on it. The song is not nearly as interesting as what your collective imaginations are dreaming up so let's just say for the sake of argument that it's Whistle Stop by Roger Miller. So the task is: Get a fragment of a song on to the Treo and assign it to a specific number.

I gotsta tell ya, it's easier to skin a raccoon with dull machete.

After a LOT of trial and error and at least three pieces of trial software for various tasks, none of which I eventually actually needed, it was done. Here are the trimmed down steps I performed (and again, I don't want to imply that this is the correct answer):

  • Edit the song in whatever MP3/wave editor into a clip that makes sense as a ringtone.
  • Save it as a wave file unless you want to deal with third-party software because the Treo, out of the box, doesn't support MP3 files as ringtones.
  • Make sure your Treo is set up to receive e-mail (using VersaMail, not MyMail and not Messaging; and on that note: why are there three ways to get mail on this thing?!?)
  • E-mail the wave to yourself. I kid you not, this is the ONLY way I found to copy a wave file on to the Treo using the software that comes with it.
  • Try several times to download the e-mail before it times out
  • Click on the little file icon next to the attachment name (NOT on the attachment name itself)
  • There are three options: View, Save to card..., and Select viewer... Click on Select viewer... (ya, that was MY third choice, too)
  • In the Select Viewer screen, change the Viewer to Sounds. I shouldn't need to explain this because it should be pretty obvious but just in case you aren't picking up on the sarcasm, this will copy the file to your Sounds folder (or area or whatever).
  • Click View
  • Now press the Home button to go to the main screen
  • Go to your favorites (by pressing the green Call button which was NOT easy to figure out)
  • Scroll down (again, not obvious that you can do this) and select an empty slot
  • Leave it on Speed Dial
  • Enter the phone number you want to apply this to (or select an existing contact by clicking Lookup)
  • Change the ringtone to the one you just saved
  • Click OK

I could comment further but I think the list of steps involved speaks for itself.

Kyle the Luddite

Thursday, May 24, 2007

Some years ago, I built a commercial ASP.NET 1.1 application and part of it is about to be sold off. I've been the sole developer on it since its inception but haven't spent much time on it because it hasn't been a major focus for the person for whom I built it.

Now, after being tasked with preparing a stripped down version of it that someone else will be looking at, I'm starting to realize just how far my development skills have come in the last few years.

But that's not what I came to talk about.

The application is small enough that I'm taking the opportunity to refactor a little as well as upgrade to .NET 2.0. Not using the upgrade wizard, mind you. Last thing I want in a .NET 2.0 app is a bunch of designer code intermingled with the page logic. And let's just say I wasn't exactly a proponent of MVC back then, let alone MVP. Heck, at the time I was just proud to be using the DAAB (the original version, with the static SqlHelper).

One of the things I wanted to tackle was cleaning up the authentication code to take advantage of the ASP.NET security controls. In the original version, I used what I think was fairly standard practice at the time. Namely, in the AuthenticateRequest event in Global.asax, I created a GenericPrincipal using the username and roles from the application's database. (Oh yeah, I'm using Forms Authentication.) This was pretty useful in creating a guest-only version of the app as well as an admin section.

With .NET 2.0, I had read and played with the Membership and Role Providers but hadn't done any real work with it other than to run through the ASP.NET configuration wizard and stare blankly at the sheer number of tables created to support it.

In this version, I had two goals: a) use those providers, and b) do NOT, under any circumstances, create that aspnet_regsql security schema in my database. After all, I already had a user/role schema set up that worked.

So starting from Scott Guthrie's comprehensive list of ASP.NET security resources, I began wading through tutorials each of which wants you to either use that same schema, or use some entirely different backend. All I really wanted was to use the SQL providers with my own schema.

It was this article that got me on the right track. From there I was able to determine what I had to do. To wit, I created a custom membership provider and a custom role provider to do only what I need them to do.

The membership provider derives from SqlMembershipProvider and contains only the following code:

    public override bool ValidateUser( string username, string password )
    {
        string role = MyDal.ValidateUser( username, password );
        if ( role == null )
        {
            return false;
        }
        return true;
    }

Similarly, the role provider derives from SqlRoleProvider and contains the following:

    public override string[] GetRolesForUser( string username )
    {
        ArrayList roleList = new ArrayList( );

        DataTable roleTable = MyDal.GetRolesForUser( username );
        foreach ( DataRow row in roleTable.Rows )
        {
            roleList.Add( row["UserGroup"].ToString( ) );
        }

        return (string[])roleList.ToArray( typeof( string ));
    }

(NOTE: If I may use my Hillbilly mind powers for a moment: You WILL ignore the data access code.)

These methods do exactly what they say they do but they are doing it the way *I* want them to do it. I.E. They're retrieving the data from my database using stored procedures of my own design.

After that, it was a simple matter of registering the providers in the web.config:

    <membership 
          defaultProvider="MySqlMembershipProvider" >
        <providers>
            <add name="MySqlMembershipProvider" 
                 type="MySqlMembershipProvider"
                 requiresQuestionAndAnswer="false"
                 connectionStringName="MyConnection" />
        </providers>
    </membership>
    <roleManager enabled="true"
                 defaultProvider="MySqlRoleProvider">
        <providers>
            <add name="MySqlRoleProvider"
                 type="MySqlRoleProvider"
                 connectionStringName="MyConnection" />
        </providers>
    </roleManager>    

Where "MyConnection" refers to an entry in the <connectionStrings> section.

Now I'm able to use the ASP.NET login control and the various menu and navigation controls using security trimming as well as protect my application without having to fiddle around with the forms authentication cookie.

Note that this works splendidly for my scenario. In my application, users log in and the system determines what role (that's right, I'm supporting only a single role, you wanna make something out of it?) they're in. That's it. There is no "I haven't registered yet so I'll just sign up online" or "I forgot my password can you send it to me?" or "What users are in this role?" scenarios. That's why I can get away with overriding only the two methods listed above.

Like JP says, small victories...

Kyle the Victorious

P.S. I still hate security-related coding

Monday, May 21, 2007

This is part moot of inconsequential posts on my professional development odyssey in which I try to develop an online music library application using whatever techniques and technology strike my fancy at the time. Code can be found at: svn://208.109.223.228/Suvius.Flamingo if memory serves.

In our last episode, I popped up an initial test to see if we could retrieve all songs by an artist. I've since added a test, ShouldBeAbleToRetrieveSongsWithPartialTitle, that tests to see if we can retrieve songs by title (or partial title). It's not altogether interesting so I'll skip it here.

The next test was ShouldBeAbleToRetrieveSongsByGenre which garners slightly more air time because if the notion of a Genre. Here is the test in its current incarnation:

  [ Test ]
  public void ShouldBeAbleToRetrieveSongsByGenre( )
  {
      Genre genre = new Genre( "Really &*%$ old" );
      int numberOfSongs = songRepository.GetSongsByGenre( genre ).Count;
      using ( mockery.Record( ) )
      {
          ISong fakeSong = mockery.DynamicMock<ISong>( );
          SetupResult.For( fakeSong.Genre ).Return( genre );
          songRepository.AddSong( fakeSong );
      }
      Assert.AreEqual( numberOfSongs + 1, 
songRepository.GetSongsByGenre(
new Genre(
"Really &*%$ old" ) ).Count ); }

And here is the GetSongsByGenre method:

    public IList<ISong> GetSongsByGenre( Genre genre )
    {
        List<ISong> songsWithGenre = new List<ISong>( );
        foreach ( ISong song in songList )
        {
            if ( song.Genre.Equals( genre ) )
            {
                songsWithGenre.Add( song );
            }
        }

        return songsWithGenre;
    }

(NOTE: No this isn't my final implementation. But it's enough to pass the tests for now.)

I've decided to make the Genre a Value Object which you might be able to determine from the comparison in GetSongsByGenre. The inital reason for this is that a Genre seems like it should be an object and I'd like to be able to compare two songs' genres.

But as I write this, I realize I probably should apply the same thinking to an artist. If a genre is to be its own object, why not use an Artist object rather than a string? For the purpose of our application, both are to be used identically (i.e. as search criteria and metadata on a song).

So I should create an Artist object, yesno? Now it's starting to sound more complicated than it probably needs it to be. Do I care about genres or artists other than as a property on the song? Is there any other data about them that is interesting to me?

At this point, I'd say the answer is no. Certainly for genres. But also for artists for the moment. Maybe we'll get to the point where we want to store the names, birthdates, country, aliases, and a little bio on each one but for now, I believe it's time to demote Genres back to a string.

Kyle the Devalued

Thursday, May 17, 2007

Here's the Subversion info for the online music app I keep referring to sporadically as Flamingo: svn://208.109.223.228/Suvius.Flamingo.

A couple of notes on the project structure:

  • The initial solution was created by TreeSurgeon, whose cause was recently taken up by Bil Simser
  • The build file was subsequently modified to account for the fact that TreeSurgeon's NAnt file doesn't work for .NET 2.0 solutions. It was borrowed heavily from various demos by JP. I've removed the deploy dependency from the "all" target because there is nothing to deploy yet. Have also add an <nunit2report> task because for the most part, Hillbillies don't like to read XML natively.
  • I'm using Rhino Mocks 3.1 which is where the funky new mockery.Record( ) functionality comes in.

So download the code and follow along with my professional development and we can explore the various definitions of the word "mock". Keep in mind that this series of posts differs from other demos in that it isn't a demo. It's a record of my learning shiny new things. By no means am I suggesting what I'm doing is a de facto standard or even a smart way of doing things. Quite the contrary, I encourage you to call me out if something doesn't look right.

The astute reader will probably guess that I have just finished reading Jimmy Nilsson's Applied Domain Driven Design and Patterns. Well, you'd be wrong. I'm still a couple of chapters from the end.

With that, let's get started. The solution contains two projects: Domain.Test and Domain. Like all the hot shot TDDers I'm trying to emulate, I started with a test:

[ Test ]
public void ShouldBeAbleToRetrieveSongsByAnArtist( )
{
    string artist = "Frank Crummitt";
    int numberOfCrummittSongs = songRepository.GetSongsByArtist( artist ).Count;
    MockRepository mockery = new MockRepository( );
    using ( mockery.Record( ) )
    {
        ISong fakeSong = mockery.DynamicMock<ISong>( );
        SetupResult.For( fakeSong.Artist ).Return( artist );
        songRepository.AddSong( fakeSong );
    }

    Assert.AreEqual( numberOfCrummittSongs + 1, 
songRepository.GetSongsByArtist( artist ).Count ); }

This led to the ISong interface and the SongRepository class, both of which are in the Domain project and neither of which I think belong there. ISong probably should go in a separate Domain.Interfaces project. SongRepository...well, I haven't decided yet.

Pay no notice if the Song class. It shouldn't be there yet because there is no code testing it (which you can verify by executing the "coverage" target in the build file). I've added it because I keep forgetting the API for the UltraID3 library and I left some sample code in the Song's constructor as a reminder.

Incidentally, in case it's not readily obvious, I'm starting with the requirement: Should be able to retrieve songs by an artist.

Some comments on the process to date. Firstly, it's not as easy as it looks coming up with initial tests. It's odd that I've written a test about song objects and still don't need a Song class. Even as I look at the test I've written, I'm wondering if there shouldn't be another to test SongRepository.AddSong.

Secondly, after reading what Nilsson had to go through to create fake objects in his book, I have an even greater appreciation of Rhino Mocks. Here's hoping Oren Eini doesn't get hit by a bus.

But overall, I'm happy with how things have started out. It took a bit to understand the build files I pilfered from JP enough to make my own changes to it but it's effort that translates directly to time saved on future projects.

Kyle the Initialized

Thursday, May 17, 2007

The Suvius.Flamingo project is not quite dead although it is, to go with the motif of the National Geograhic show playing behind me, about ninety percent down the python's gullet. But let's see what we can't glean from the carcass.

I haven't linked to the application because I'm scared to even check to see if it still works. Last I checked, Windows Media Player 11 had made the application all but useless so I kind of abandoned it. Now, in a fit of PD, I'm going to see if we can't all learn something from resurrecting the app and re-doing it in a TDD way. So with that, I shall carefully document its development from start to finish. Or at least until something else shiny catches my eye.

For those that want to play along, I have a Subversion repository and will post the details for it in the near future once I have created a read-only user. If you *really* want to gain my undying gratitude, roll up your sleeves and ping me to become a developer on the project.

The primary goal is learning. If we end up with a working application at the end, all the better.

First, the boring part: defining what the application is supposed to do. This will be a moving target but in a good way. Mostly because I'm reserving the right to change the requirements to make development (and blog topics) more interesting.

At a high level, I'm looking at building a front-end to my online music library. It currently resides at http://radio.baley.org as a directory list of about 300 songs. The other 2500 have yet to be migrated to the new server.

If I were to free associate some features, they would include:

  • Ability to search for music based on one or more criteria, including title, artist, and genre
  • Ability to chain search filters together to form a playlist
  • Ability to play the playlist in at least one popular media player in a user-friendly way
  • Ability to save a playlist
  • Ability for an administrator to upload additional songs to the database and glean the metadata from it (as opposed to keying it in manually)
  • Ability to update metadata and have it persisted not only in the database but in the underlying file as well
  • Ability for users to rate a song

Woman, I'd better stop there or I'll be here all night. Suffice it to say, there is a lot more I could throw in....ah, what the hell:

  • Ability for any user to tag a song with keywords
  • Ability to search based on tags
  • Ability to apply more than one genre, artist, album, etc to a song. This might be combined with the tagging feature. E.g. Wish You Were Here would be tagged with Pink Floyd, Roger Waters, David Gilmour, Rick Wright, Nick Mason, Wish You Were Here (album), A Collection of Great Dance Songs (album), classic rock, acoustic
  • Ability for users to add comments about a song (possibly in a wiki-like fashion)
  • Ability to share playlists
  • Ability to link songs (i.e. link to other versions of a song). This would also be user-driven

Let the music begin.

Wednesday, May 16, 2007

Have been gathering requirements for the land surveying application I'm working on for my brothers and it's an interesting exercise doing so remotely. But one of the things that has worked remarkably well is using an instant messenging client rather than a phone call.

There are two things I've come to like about it: First, there's a copy of what was said. Granted, it's not in the most structured format but at least it's searchable from a SharePoint site.

Secondly, and your results may vary, but I find it's easier to stay focussed on one aspect of the requirements when you're typing things out. People are less likely to "blurt" things out when they have to type it in. This is typically more pronounced if you can type faster than they can.

And it forces them (eventually) to explain things in a way that's easy to understand. I put (eventually) in parentheses there because it takes a few initial prompts of, "what do you mean?" before some people get in a mode where they put some thought into what they're typing. But when they do, they are a lot less likely to start sentences without knowing how they are going to end, which is a problem I find myself battling sometimes when I'm speaking aloud.

It's kind of a variation on the theme: The act of explaining a problem is enough to identify a solution. A theme I've been sorely lacking in my little caribbean hovel. Of course, it helps that the client is my own family and they know how anal I am about grammar in MSN Messenger.

But in general, I still find most people put more effort into what they type than what they say. Maybe it's the impression that anything typewritten is more formal than the spoken word. Maybe in the back of their mind, they're considering that what they say will be saved and used against them. Whatever the case, I'm liking the results.

Plus you don't need to brush your teeth before talking to them.

Kyle the Remote

Wednesday, May 16, 2007

My evidence is purely anecdotal but has anyone else noticed a tremendous increase in startup time, application launch time, and overall performance when Windows Desktop Search is uninstalled?

Monday, May 14, 2007

If there is one thing my compulsive mind does not like, it's seeing little Chevrons on my Quick Launch toolbar. Especially since it also usually means there's a new icon on my desktop that I need to delete.

Y'see, hillbillies like'un to keep their desktops orgie-nized. And to do that, I like to know what's on it. Which means I like to decide what goes on it. And I don't need the likes of Apple, Microsoft, Adobe, Nero, or anyone else using it as a dumping ground for a piece of software I may not even keep, let alone use enough to warrant an easily-accessible icon.

The screen I love to see most in an installation is: Do you want an icon for <my app> on your desktop? WinZip is masterful at this, asking not only if there should be a desktop icon but if they should even *create* a program group for it in my Programs menu.

And the answer is no. I can no longer remember the last time I actively sought out the WinZip application, if I ever did. That's what the context menus are for.

Apple is a particular thorn in my side on this issue. Not only did they not even ask to install icons in the Quick Launch bar and the desktop, every time I get an update from them, I have to delete these things all over again.

And there is simply no reason for me to have them. I have never, ever opened QuickTime directly to do anything. Oh, I've viewed my share of .mov files. By double-clicking them. Why would I need an icon (or even a program group) for it? Apple, of all companies, should recognize this and give me the option not to add the icons.

I'd make the same complaint about Acrobat Reader but I went one better on Adobe. Not only did I delete the icons and program groups, I also deleted the application itself in favour of Foxit Reader. But it is a textbook example of an application that needs no icon. Anywhere.

My Quick Launch bar is sacred ground. Yes, it shouldn't be necessary with the likes of SlickRun and WinKey but I still use it for applications I open sporadically, if not regularly. Until I can spare the twenty-six seconds it takes to set up a magic word or hotkey for them.

Until then, lay off my Quick Launch, QuickTime.

Friday, May 11, 2007

I've decided to make use of the ASP.NET Ajax Toolkit for an app I'm working on after dropping out of the Atlas space for a few months. Sweet Jayzus, those guys have been busy. There are at least a dozen new controls since I last looked.

I like the idea of the extenders. That is, you extend the capabilities of existing ASP.NET controls simply by dropping them on the page and configuring them.

I purposely used the word "simply" there. Because I found the practice a little different than the promise. Let's take a look at the HTML I'm extending:

 <div style="margin: 0.5em; float: right; width: 150px; 
background-color: steelblue; color: lightsteelblue;
font-weight: bold;"
class="quickLinks"> <div style="padding: 0.25em; background-color: white;
height: 18px; color: steelblue;
border: 1px solid steelblue;">
<div style="float: left;"> Quick Links </div> <div style="float: right; vertical-align: middle;"> <asp:Image ID="Image2" runat="server"
ImageUrl="~/images/collapse.jpg" /> </div> </div> <div style="padding: 0.5em; border-bottom: 2px solid white;"> <a href="#">Add Job</a><br /> <a href="#">Find a Job</a><br /> </div> <div style="padding: 0.5em;"> <a href="#">Edit...</a><br /> </div> </div>

I've purposely kept things out of CSS as much as possible to keep things in the same file for the purpose of this demonstration. The exception is my use of a quickLinks class in the first DIV. I did that so that I could make the links white:

.quickLinks A
{
    font-weight: normal;
    color: white;
}

The above HTML produces the output you see to the right (aligned to the right side of the browser thanks to the "float").

Now, the toolkit effects I chose for this bad boy are: Drop Shadow (which comes with its own built-in rounded corners), the Collapsible Panel,  and the Drag Panel. So I set out extending my code with these nifty little controls.

The keen observer will have noticed that the extenders apply to ASP.NET controls which was my first problem. That means using Panels rather than divs. Not such a big deal that. Usually one can just replace the <div> tag with <asp:Panel id="moo" runat="server"> and change the class tag to CssClass and be done with it.

But some of the extenders also need things to be lain out a little differently in order to attach to the appropriate elements. Take the Drop Shadow for example. In my first attempt, I converted the outer <div> to an ASP.NET Panel and applied the DropShadowExtender to it directly. This led to the grooviness you see below.

Which isn't the most ideal-looking piece of design I've done (look closely at the corners at the bottom). Also, you can't see it but the right-margin was no longer in effect. As in, this was right up against the right side of the screen.

The solution for this particular case was to wrap the entire HTML in another <div> and move the float:right and margin:0.5em; styles to the new outer <div>.

I won't go through all the little tweaks I had to make for the other controls. But here is the final HTML I needed to build to get this to work in both Firefox and IE (with *some* styles moved to CSS classes):

  <div style="float: right; margin: 0.5em; width: 150px;">
      <asp:Panel ID="panelQuickLinks" runat="server" CssClass="quickLinks" Style="z-index: 20;">
          <asp:Panel ID="quickLinksExpand" runat="server">
              <div style="padding: 0.25em; background-color: white; height: 14px; color: steelblue; 
border: 1px solid steelblue; cursor: pointer;">
<div style="float: left;"> Quick Links </div> <div style="float: right; vertical-align: middle;"> <asp:Image ID="Image1" runat="server" ImageUrl="~/images/collapse.jpg" /> </div> </div> </asp:Panel> <asp:Panel ID="quickLinksContent" runat="server"> <div style="padding: 0.5em; border-bottom: 2px solid white;"> <a href="#">Add Job</a><br /> <a href="#">Find a Job</a><br /> </div> <div style="padding: 0.5em;"> <a href="#">Edit...</a><br /> </div> </asp:Panel> </asp:Panel> </div>

So now I'm five levels deep and using a mix of <div>s and Panels each with its own unique little style attached. Not very pretty.

To be fair, it didn't take very long to mangle up my HTML to accommodate the extenders. And I'm certainly not proposing that what the toolkit team is doing is anything but sublime. Let's not ignore what I've actually done here: created a draggable, collapsible box with drop shadows that works in both IE and Firefox and I wrote zero javascript code.

But like many tools that deal with HTML, you still need more than a basic knowledge of HTML and CSS to get the extenders to match what you are picturing in your head.

Still, the fact that I *can* match what's in my head is testament to how far we've come.

Or a commentary on my lack of imagination.

Kyle the Extended

Thursday, May 10, 2007

One of the things I've always hated about my otherwise-dashing Coding Hillbilly theme is that there was no easy way to get back to the home page. Normally, it's a pretty easy thing to do. Apply an anchor tag to the text and/or image in the title and away you go.

Unfortunately, I made a design decision early on that prohibits this. The image you see at the top is actually a background image for a <div>, not an <img> tag. Originally it was an <img>. But when I did that, I noticed the browser would start throwing up horizontal scroll bars when it was resized to be not as wide as the image + the search bar. And while I don't mind forcing users to scroll when the actual text warrants it, I *hate* forcing it just because you're proud of your pretty picture.

So I made it a background image and lo! I could now resize the screen so that the search bar can overlay that image if a user so desires. But it meant that there is no actual content to which I could apply an <a> tag to get users back to the home page. What to do, what to do.

Enter my CSS archnemesis: spacer.gif. Oh, how I loathe you and the fact that people still use you for layouts even though you are no longer necessary.

But you still can be useful. All I need to do is add you to the <div> at the top with an anchor tag and people will click you thinking they are clicking the image!

So that lasted about ten minutes until I came to my senses. A freakin' transparent gif filling the entire header? Not on my blog, sister.

The eventual solution: modify the <td> that makes up the left part of the header:

<td style="cursor:hand;" onclick="location.href='/'">

Yeah, Firefox doesn't recognize "cursor" in CSS but it's still better than nothing.

*UPDATE* Guess I'm showing my age. Firefox does support "cursor" in CSS. Just not cursor: hand. The correct, non-IE-specific version is cursor: pointer.

Thursday, May 10, 2007

Someone tried to send me the following e-mail today:

Is there a quick (slick) way to force SSL on a website in IIS? I.e. if a user types in "http://www.mysite.com/mypage.aspx" we want IIS to automatically redirect to "http s://www.mysite.com/mypage.aspx"... I know you can do this through code in many ways, but wondering if there is some tricky way to do it in IIS.

Clearly, my friend is trying to infect the world with clandestine virii through his use of innocent IIS questions. But no matter! I have GoDaddy to protect me! So instead of his spite-filled spam actually making it to my Inbox, he received the following:

PERM_FAILURE: SMTP Error (state 12): 554 The message was rejected because it contains prohibited virus or spam content

I eventually did get the message thanks to the efforts of two others to whom the message had been sent, one of whom had the foresight to try my Gmail account which, like all things Google, worked.

Sure enough, I tried forwarding that same message to my normal, everyday e-mail account and received the same outright rejection (which is oddly reminiscent of the ones I got in high school).

Didn't take too long to figure out it was the URL that got flagged and flogged as "prohibited spam content".

So let that be a lesson to you all. There are evil people out there on the Internets. They may try to trick you with their pleas for help. But you must constantly be on the alert. Next thing you know, I'll be fielding questions on how to send money to Nigeria using LINQ.

Oh, the original question. Don't worry, it got answered in the same way I usually get mine answered. Send it out to a random group of developers from my address book plus James Kovacs. Chances are he's posted the solution already.

Kyle the Vigilant(e)

Tuesday, May 08, 2007

Against my better judgement, I bought a Sony TV in Miami and had it shipped to the Bahamas. It's a 46" Bravia that within its first week, started showing a 3" stripe along the right side of the screen. It was red when it should have been black, yellow when it should have been green, etc, etc and so on and so forth.

But that's not what I came to talk about.

At some point during my travels, Sony got it into its head that I live in Latin America. So much so that now whenever I try to navigate to www.sony.com, I'm redirected to www.sony-latin.com/corporate.

When I first tried to hit sony.com, it detected something from my browser (or more likely my IP address since my computer probably still thinks I'm in the US) and asked me if I wanted to go to sony.com or the Sony Bahamas regional site. I.E. sony-latin.com. I went with sony.com and had it remember my preference but that was duly ignored.

(For now, I'm ignoring the ludicrous idea that the Bahamas should be considered part of Latin America. I'm also ignoring the odyssey I've had to endure )

The main issue with the Sony Latin site is, as far as I can tell, there is no way to get back to sony.com. There is a link to Global Home, which takes you to sony.net (which has its own issues, like the fact that the Bahamas doesn't even exist in their list of countries) but nothing back to sony.com. At least nothing in English.

There are other problems with sony-latin.com. Firstly, the dropdown menus don't work in Firefox which essentially means you can't navigate anywhere from the homepage. (And even in IE, I'm getting javascript errors with the menu.) And clicking on the Puerto Rico e-mail link from this site gives a Public Area General Error! which is a new one to me. (Ya, I know what you're going to say. I have no idea why I would be directed to the Puerto Rico support site either.)

Salient points:

  • Sony doesn't appear to be putting in even the minimum amount of effort required for sony-latin.com. The two errors I found are pretty glaring. After all, Firefox is on version 2.
  • Sony does however appear to have put in some effort to try to direct non-US customers to the right place. The problem being, they've put in a lot of effort and it's worse than, say, Dell where at least you can switch to the US site if they've directed you to the wrong place.
  • The Bahamas simply should not be considered part of Latin America. I don't speak Spanish (and in fact, many Bahamians are still working on English as their first language judging from the slogans from the recent political campaign). I could see the argument though considering the nearest US city is Miami.

For the sake of closure, I did manage to get to sony.com after clearing my cookies. Firefox took me to the page where I had to choose sony.com or sony-latin.com and I also chose to remember my preference. Then I closed my browser and went to sony.com again and was directed to sony-latin.com.

<sigh> ¿Cualquier persona desea comprar una televisión nueva?

Monday, May 07, 2007

Apologies in advance if I've misspelled aspnet_compiler as aspnet_compilter anywhere in this post. For the life of me, I can't figure out why my fingers always want to add that 't'.

Interesting little quirk. Many moons ago, I installed Scott Guthrie et al's Web Application Project, probably because I needed to for dasBlog to compile. Since then, I've kind of ignored it in my project list.

Today I absent-mindedly added one to my solution thinking it was a regular VS2005 Website and I received errors in the build step that used aspnet_compiler to precompile the app: Could not load type 'SampleWebPage.aspx.cs'.

I set up debugging this already knowing in the back of my mind that it probably had something to do with the type of web project it was. My first clue was that when I built the web project in Visual Studio itself, it was compiled using csc:

Whereas the output for a Web Site build is as follows:

The other hint was the property pages. For a Web Application Project, you get property pages that look like they do for a class library or WinForms app:

For a Web Site:

Plus they're called different things in the right-click menu (Properties vs. Property Pages).

Finally, there's the actual error message itself. Web Applications Projects, of course, use an explicit namespace, Web Sites do not. And the big ol' honkin' namespace declaration at the top of the class was a pretty solid indication that this ain't no VS2005 Web Site.

So the choices were pretty obvious: modify the build to accommodate the Web Application Project or use a Web Site. I chose two as the path of least resistance since I had but one (incomplete) page in it.

Kyle the Rebuilt

Monday, May 07, 2007

I got pretty lucky with my next "contract". I'll be doing some work for my dad's company, a land surveying company where not only my dad, but all three of my brothers and my mother also work. That's literally my entire immediate family (Mrs. Hillbilly and the young'un notwithstanding).

It's an interesting environment, to be sure. My mother's nickname is the Office Nazi. Rather than Casual Fridays, they enjoy Potty Mouth Friday where everyone swears like sailors. This was apparently such a resounding success, they also implemented Racial Slur Wednesday, followed by Repentence Thursday ("I'm sorry I called you a Hebe, man").

So yeah, it ain't exactly Corporate Challenge Country.

But yes, I'm lucky to be working on this contract because I basically have carte blance on how to build the application I'm working on for them. But therein lies the rub, if you can forgive a Shakespeare-quotin' Hillbilly.

Carte Blanche can actually be a scary phrase, especially when you're neck deep in Professional Development mode. Forthwith, a list of technologies and methodologies I originally considered for this application:

  • TDD
  • DDD
  • WCF
  • WPF/E (and now Silverlight)
  • WF
  • SharePoint
  • ASP.NET AJAX

This doesn't include all of the development tools I also wanted to learn (or update my deprecated knowledge of) for the underlying code. To wit: Rhino Mocks (and mocking in general), NUnit, NAnt, NCover, Cruise Control, Spring, MonoRail, Windsor, and pretty much any other little doohickey that caught my eye in the last two months.

And keep in mind, I'm on a team of one stuck in a place where "development" refers more to how the government can find creative ways of giving away crown land.

So this morning's little epiphany is to scale back. This thing does eventually need to be deployed in the next three to six months. Yes, the deadlines are loose and yes, they'll be pretty forgiving but they are also putting out a healthy chunk of cash for this and will be expecting the same as any other client. To wit, learn on your own time and your own dime.

I'm sticking with TDD and DDD for the time being and probably ASP.NET AJAX for the GUI. Rhino Mocks, NUnit, NAnt, NCover are in but everything else will have to wait until after the first release or, more likely, an independent side project.

Kyle the Ambitious

Friday, May 04, 2007

In the comments of my recent Rhino Mocks post, James Kovacs pointed me to this post from Oren on interaction-based testing. In it, Oren gives guidelines for using Rhino Mocks to create fake objects. Namely, use DynamicMock instead of CreateMock and use SetupResult instead of Expect or LastCall.

So that said, here is my original test:

        [ Test ]
        public void JobNumberShouldNotBeZeroAfterReconstitution( )
        {
            int jobNumber = 42;
            fakeAJob( jobNumber );
            IJob job = jobRepository.GetJob( jobNumber );

            Assert.AreEqual( job.JobNumber, jobNumber );
        }
        private void fakeAJob( int jobNumber )
        {
            Job job = jobFactory.CreateJob( );
            JobRepository.SetFieldWhenReconstitutingFromPersistence( job, "jobNumber", jobNumber );
            jobRepository.AddJob( job );
        }

This is following pretty closely with Jimmy Nilsson's Applying Domain-Driven Design, at least in the early chapters which I'm currently scouring, including the problem he mentions about creating a job with an order number without specifying it in the constructor. SetFieldWhenReconstitutingFromPersistence uses reflection to set the field which he knows is bad but I haven't got to that part of the book yet.

Instead, I thought I'd have a go at creating a fake object in Rhino Mocks, leading to the following revised code:

        [ Test ]
        public void JobNumberShouldNotBeZeroAfterReconstitution( )
        {
            int jobNumber = 42;
            MockRepository mockery = new MockRepository( );
            IJob fakeJob = mockery.DynamicMock<IJob>( );
            SetupResult.For( fakeJob.JobNumber ).Return( jobNumber );
            jobRepository.AddJob( fakeJob );
            IJob job = jobRepository.GetJob( jobNumber );

            Assert.AreEqual( job.JobNumber, jobNumber );
        }

Both tests pass which was nice to see. I'm throwing this out partially as an example of how to create a fake with Rhino Mocks (because I had trouble finding any explicit ones) but mostly to get confirmation that I'm still testing what I want to test, which I think I am.

Tuesday, May 01, 2007

Holy crap, this TDD thing is one heckuva mindf*%#@!

The concept, I can dig. Write your tests, get it to compile, then get it to pass. I thought it mapped nicely to my normal coding practice, which is to start writing code making up objects and methods as you go, then fill in the blanks.

Turns out my method is working against me. I'm in the midst of writing my tests and I can't even get to the end of the test before I start creating my objects. For example, I started writing the following test:

        [Test]
        public void CanCreateJob( )
        {
            Job job = JobFactory.CreateJob( );
            Assert.IsNotNull( job );
        }

The problem is, as soon as I pressed enter on the first line of code in this method, I went barrelling through the Domain project creating the Job class. Before I even finished writing the test, let alone trying to compile and run it. And I shouldn't even *have* a Domain project yet.

So everything you've heard is true. It's what people used to call a "paradigm shift" back when the phrase had some meaning. It takes discipline and people will probably poo-poo it if it scares them too much (was that really less than a year ago?).

On that note, after reading more extensively on the subject, I now tend to agree with a friend of mine, a developer-who-doesn't-blog-but-should who is fairly senior but relatively new to TDD, and who said that TDD is at the place OO was ten or fifteen years ago. That is, it's not quite widely accepted but eventually it will be the de facto standard (in one form or another).

And hopefully by that time, all the little golden nuggets about it will be codified so that I don't have to highlight them in various blog posts/books.

Tuesday, May 01, 2007

Sweet Jayzus, I have to reveal my ignorance more often if this is going to be the result. That's a faster turnaround than at your average Bahamian bank check-in counter!

So, James, I hate to mention it for fear of looking stupid but I'm having a little trouble writing this tiny piece of software to predict the stock market....