Monday, May 30, 2005
I was perusing some blogs today and stumbled upon this interesting post.  Intrigued, I had to investigate.  My high score, though not yet matching his, is 1579.  It's pretty hilarious.  I almost fell out of my chair laughing the first several times I tried.  Try this, for a direct link to the game.
Monday, May 30, 2005 6:31:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, May 24, 2005

Application installers are pretty cool, and creating one in .NET is pretty straight forward.  A common usage of installers is to set up registry settings, event log/event source information, etc.  Very frequently, when creating a new application or project, a developer will add an installer class to it for custom install/uninstall configurations to be made.  This task gets pretty repetitive and redunant.

It usually involves creating a class, deriving from Installer, marking it with the RunInstallerAttribute, and overriding the Install and Uninstall methods, with the occasional Rollback override thrown into the mix.  I've come up with a strategy that I think is pretty slick.  In this example, I'm tackling the issue where my installer will create an event log (if it doesn't already exist) and register itself as a valid source for the event log.

In a traditional situation (as I've seen) a developer will programmatically create an event log (e.g. MyEventLog) and register a source (e.g. MyEventSource).  This wires up some registry settings such that when an application writes an event to the log with the name MyEventSource it will route the data to the MyEventLog.  This event source string frequently gets declared as a constant within a project and used when writing to the event log, possibly resembling the following:

public class Constants {
   public const string AppSourceName = “MyEventSource“;
}

//... later on in code...
EventLog.WriteEntry(Constants.AppSourceName, ...);

This is usually well and good.  It centralizes the name of the event source so that if it needs to change you have one place to go to change it.

As the OO and component developers we are we tend to wrap and isolate blocks of code into classes for reuse.  It's not a far leap to suppose that a tenacious developer might wrap the EventLog.WriteEntry() method call into a more generic class so that all events get logged in a consistent manner, with formatting, stack walks, error reports, etc.  At this point you have a Logger class in some common, shared library into which you have to always pass the AppSourceName so it writes to the correct log.  You've also imposed some restrictions on the dependent applications/libraries that they have to either 1) have their own or 2) know another's AppSourceName in order to provide a meaningful value.  Then you start creating a Constants class in every application for this purpose.  You might see how this can grow ad nauseum.

Anyway, let me use this as the staging conversation for the rest of this post.

Rather than impose a certain programming style on your applications simply to log events, I propose an alternative approach.  In the same shared library that has the Logger wrapper class, I create two attributes: EventLogNameAttribute and EventSourceNameAttribute, each with AttributeTargets.Assembly as their designated target.  For example:

[AttributeUsage(AttributeTargets.Assembly)]
public sealed class EventSourceNameAttribute : Attribute {
   private string _eventSourceName;

   public EventSourceNameAttribute(string eventSourceName) {
      _eventSourceName = eventSourceName;
   }

   public string EventSourceName {
      get { return _eventSourceName; }
   }
}

Your assemblies that use these attributes, then, would have something similar to the following in their AssemblyInfo class:

[assembly: EventSourceName(”MyEventSource”)]
[assembly: EventLogName(”MyEventLog”)]

An installer (back to the original point), would then have a base installer class (e.g. MyInstallerBase) from which all application installers would derive if they needed event log/event source wire up:

[RunInstaller(true)]
public abstract class MyInstallerBase : Installer {

   protected string eventSourceName, eventLogName;

   public MyInstallerBase() : base() {
      loadEventData();
   }

   private void loadEventData() {
      // resolve to the assembly whose type derives from this class.
      // it is on that assembly that we should reflect to get the event log and source data
      Assembly asm = Assembly.GetAssembly(this.GetType());
      // read the attributes from the assembly and store them for the installer to wire it up properly
      object[] esn = asm.GetCustomAttributes(typeof(EventSourceNameAttribute), false);
      eventSourceName = ((EventSourceNameAttribute)esn[0]).EventSourceName;

      object[] eln = asm.GetCustomAttributes(typeof(EventLogNameAttribute), false);
      eventLogName = ((EventLogNameAttribute)eln[0]).EventLogName;
   }
}

At this point, you have the event source name and log name that the calling assembly wants to use.  You can then use the values in overridden Install/Uninstall/Rollback methods.  Note, you can override these methods on the base class.  This makes it really slick and allows a dependent library to simply have an installer declared as the following:

public sealed class MyCustomInstaller : MyInstallerBase { }

That's it!  Now you can run InstallUtil on your application and it will wire up the appropriate event log and source for you via reflection!

You can take this a step further in your Logger class so you don't have to still have the Constants class floating around.  Your Logger could have a method called GetAppSourceName() that performs the same action as above to resolve the AppSourceName of the calling assembly:

public static string GetAppSourceName() {
   return getAppSourceName(Assembly.GetCallingAssembly());
}

private static string getAppSourceName(Assembly asm) {
   object[] esn = asm.GetCustomAttributes(typeof(EventSourceNameAttribute), false);
   return ( 0 == esn.Length ) ? “Unknown“ : ((EventSourceNameAttribute)esn[0]).EventSourceName;
}

Note, because the GetAppSourceName() function relies on GetCallingAssembly(), it will resolve to the immediate caller on the stack, so you cannot call it from the Logger class directly, or you will simply get the current assembly.

 

Tuesday, May 24, 2005 9:40:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
Ok, it's been much longer than expected, but the 2005 Betas Unleashed site is up and has most of the slide downloads the speaker bios.  The site is pretty meager, but functional.  We'll be fleshing it out for future events later on, but this suffices for now.  Jason Walker and I actually created the site in about 2 hrs one night for a different event all together that we're still planning in the coming months.
Tuesday, May 24, 2005 9:02:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, May 18, 2005

Well, I'm alive, and that's a good sign!  Today we held an event called “2005 Betas Unleashed” here in Salt Lake City - an event that was the result in over 3 months of planning and coordination.  And I must say that all in all I feel it was a great success!  We were shooting for an attendance of 150 and it ended up that we had over 225, which was wonderful!  We had two tracks of presentations that coincided: one focused on Visual Studio .NET 2005 and the other on SQL Server 2005.

I had the opportunity to present on ASP.NET 2.0 - it was a lot of fun!  Partly due to the success and interest in the event here in SLC, we've extended the event to Denver for June 15th (I believe) so that should also be a fantastic event.

Thanks to all those that participated: Scott Golightly, Andrew Arnott, Ben Miller, Pat Wright, and Ashwin Karuhatty (we can also throw in David Gollob and John Sanderson for speaking).  We had great time and we look forward to being able to do it again next year!

Wednesday, May 18, 2005 11:48:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, May 16, 2005

Being the Star Wars nut that I am, I couldn't resist creating this image to celebrate the arrival of Episode III: The Revenge of the Sith.  Though I have been extremely disappointed and disillusioned with the previous prequels (for reasons that I won't go into here), I am looking forward to this next installment.  I told myself that I wouldn't be, but after reading tons of positive reviews of people ranking it up there just behind Empire Strikes Back (far and away the best of the original trilogy) and A New Hope, I'm pretty stoked.

This is currently my MSN Messenger profile image and it suits me just fine...dark and sinister. :-)
Monday, May 16, 2005 12:15:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Sunday, May 15, 2005

Following our last Utah .NET User Group meeting I was asked a question about component / control development, specifically geared towards design-time enhancements.  That is, the individual wanted to know how to enhance the design-time experience for users of the control.

Visual Studio .NET provides some pretty cool design-time features.  When an object (e.g. a control, component, etc) is selected in the designer, the properties pane displays its set of browsable properties.  Some properties simply allow string or numeric inputs whereas others (e.g. enumerations, booleans, et al) display a dropdown list of acceptable values.  Others take this concept a bit further and provide a drop-down GUI, such as Anchor, the various Color-related properties, TextAlign, and many more properties.  Some properties that are more geared towards collections or that need a more sophisticated UI can even present a dialog box with a slew of options.

If you're a component developer (as I have been for years), you'd probably like to be able to enhance the design-time usage of your components in a like manner.  Well, in fact, you can - and it's not difficult at all.  As with most other things, it's just a matter of knowing what's available and how to use it.

In order to accomplish this, you need three things:

1.  A control with a read/write property for which you'd like to have a designer
2.  A class to instruct the designer as to the expected design-time experience
3.  A control/form to display in order to edit the property.

Your UI (from now on referred to as the EditorUI) comes in two forms: a dropdown in the properties pane, or a dialog.  In the event you want your EditorUI to be a dropdown, you must create a UserControl-derived class.  Dialog-based UIs are Form-derived classes.

The class that you use to designate your EditorUI is a class that derives from System.Drawing.Design.UITypeEditor.  An example might best illustrate this.  Suppose you have a control called Calculator with a property called Formula thus:

using System;
using System.Windows.Forms;


namespace Devstone.Junk {

   public sealed class Calculator : UserControl {
      private string _formula = string.Empty;
    
      [Browsable(true)]
      [Description("Designates the formula to calculate.")]
      [DefaultValue("")]
      public string Formula {
         get { return _formula; }
         set { _formula = value; }
      }
   
   }
  
}

This simple control, when rendered within the Properties dialog will simply have a textbox entry for the Formula property.  However, in our case we've created a nice dialog box that will appear, giving the user the ability to edit a formula.  We must establish a property on the dialog so that we can programmatically set the formula to show when the dialog appears and get the value back when it's accepted. (Note, in the following example, I've included both a Dialog and a Control version, but only or the other is required).


namespace Devstone.Junk {

   // the FormulaEditorDialog is used in the event we want to display a modal, dialog-based UI
   public sealed class FormulaEditorDialog : Form {
      // ... all the code to make this dialog work
     
      public FormulaEditorDialog() {
         txtFormula.KeyPress += new KeyPressEventHandler(keyPress);
         btnOk.Click += new EventHandler(okClick);
      }
     
     
      public string FormulaString {
         get { return txtFormula.Text; }
         set { txtFormula.Text = value; }
      }
     
     
      private void keyPress(object sender, KeyPressEventArgs e) {
         if ( e.KeyChar == (char)Keys.Enter && null != _editorService ) {
            e.Handled = true;
            accept();
         }
      }
     
     
      private void okClick(object sender, EventArgs e) {
         accept();
      }
     
     
      private void accept() {
         DialogResult = DialogResult.Ok;
         this.Close();
      }
     
   }
  
  
  
   // the FormulaEditorControl is used in the event we want to display our editor as a dropdown
   // note that it's constructor takes an IWindowsFormEditorService instance.
   // this allows the control to programmatically close itself later

   public sealed class FormulaEditorControl : UserControl {
      // ... all the code to make this control work
     
      private IWindowsFormEditorService   _editorService;
     
      public FormulaEditorControl(IWindowsFormEditorService editorService) {
         _editorService = editorService;
        
         txtFormula.KeyPress += new KeyPressEventHandler(keyPress);
      }
     
     
      public string FormulaString {
         get { return txtFormula.Text; }
         set { txtFormula.Text = value; }
      }
     
     
      private void keyPress(object sender, KeyPressEventArgs e) {
         if ( e.KeyChar == (char)Keys.Enter && null != _editorService ) {
            e.Handled = true;
            _editorService.CloseDropDown();
         }
      }
     
   }
  
}

At this point, it is incumbent on us to tie the two together so that the designer knows to display our dialog in order to edit the property.  This is done with our UITypeEditor-derived class.  Within this class, we should override at least the EditValue() and GetEditStyle() methods.

The GetEditStyle() method will allow the designer to know what type of UI we're going to display: a modal dialog, a dropdown, or None.  If we don't override the method, it defaults to None, so our UI would never display.

The EditValue() method is a bit more complicated.  When this method is overridden we are responsible for creating the control/form instance to display.

using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace Devstone.Junk {

   public sealed class FormulaEditor : UITypeEditor {
  
      public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
         if ( null != context && null != provider ) {
            IWindowsFormsEditorService svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
            if ( null != svc ) {
               // create the editor instance
               using ( FormulaEditorDialog dlg = new FormulaEditorDialog() ) {
                  // initialize it to the proper state
                  dlg.FormulaString = (string)value;
                  // display the dialog, returning either the updated or the original value
                  return ( DialogResult.OK == svc.ShowDialog(dlg) ) ? dlg.FormulaString : value;
               }
            }
         }
        
         return base.EditValue(context, provider, value);
      }
     
     
      public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
         return ( null != context ) ? UITypeEditorEditStyle.Modal : base.GetEditStyle(context);
      }
     
   }
  
}


If you are going to display a dropdown rather than a modal dialog, your code might resemble the following:

namespace Devstone.Junk {

   public sealed class FormulaEditor : UITypeEditor {
  
      public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value) {
         if ( null != context && null != provider ) {
            IWindowsFormsEditorService svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
            if ( null != svc ) {
               // create the editor instance
               FormulaEditorControl ctl = new FormulaEditorControl(svc);
               // initialize it to the proper state
               ctl.FormulaString = (string)value;
               // display the control
               svc.DropDownControl(ctl);
               // return the updated value
               return ctl.FormulaString;
            }
         }
        
         return base.EditValue(context, provider, value);
      }
     
     
      public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) {
         return ( null != context ) ? UITypeEditorEditStyle.DropDown : base.GetEditStyle(context);
      }
     
   }
  
}


The last step is to tie our Formula property in the Calculator control to the UI, and this is done via the custom EditorAttribute:

   [EditorAttribute("Devstone.Junk.FormulaEditor, CalculatorControl", typeof(System.Drawing.Design.UITypeEditor)]
   public string Formula {
      get { return _formula; }
      set { _formula = value; }
   }

Have fun with this!  It can really enhance the user's design-time experience and makes your components feel MUCH more professional!

Sunday, May 15, 2005 2:16:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, May 13, 2005

Finally! Some breathing room! ...but not for long.  though this week was probably one of the toughest weeks I've ever had in terms of work load and schedule, it's not going to entirely lighten up over the next little bit.  I can finally relax a bit this weekend (while wrapping up some other lighter work), and get ready for this coming week - which is looking to be almost as hectic as this past one.

Fortunately, everything to which I had committed last week was accomplished, delivered ontime, and well received - which is a reward in and of itself.  I can only hope the pattern will continue.

Friday, May 13, 2005 4:32:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Wednesday, May 11, 2005

I have so much that I want to blog about lately, but simply haven't had the time.  It's been a bit since I've written in the blog that I almost feel like I've abandonded it a bit - which is definitely not the case.  Hopefully after this week things will settle down.  This week in particular I've been completely slammed.  I honestly don't have time between now and Friday to sleep...I don't think I will sleep until Friday night..and I've only had 7 hrs of sleep so far this week.

Remaining this week I have:

  • Dry-run presentation tomorrow morning @ 9:00 on ASP.NET 2.0 for the 2005 Betas Unleashed event next week.  I've spent about 30 hrs this week getting ready for it and still have to finalize my demos and notes
  • .NET User Group Meeting tomorrow evening @ 6:00 (thank heavens I'm not presenting! whew!)
  • I took a side project this week (against my better judgement considering the circumstances, but there wasn't a way to push it back) for an associate of mine which must be delivered in two days...there're still about 30 hrs of work on that remaining that I have to put in between now and my 9:00 AM presentation, between 12:00 AM and 5:00 PM and 9:00 PM and that morning (all said, there are about 20 hrs remaining in the allotted time to do 30+ hrs of work)
  • Continue on my regularly scheduled contract in which I work 8:00 AM - 5:00 PM every week day
  • Deliver on another contract this weekend to another associate (that's a cool project but NDA doesn't allow me to talk about it)
  • Have company over on Saturday and Sunday
  • Oh, and don't forget that I have to be a dad to 4 beautiful girls in the meantime

Next week the cycle starts over again except that I have to actually give the presentation at 2005 Betas Unleashed to 150+ people and then leave town for a week, only to have to still deliver on Friday and Sunday on my other projects.  Oh, and I have to fit Star Wars III in there somewhere too...I hope not to sleep through it.

Am I overworked?  I feel like I'm either going to explode, faint, or cry.

Wednesday, May 11, 2005 6:01:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Friday, May 06, 2005

Today has been quite a day.  On a whim I thought I'd look to see if various of the tools I use have updates and it turns out that just today a few were released - what a strange synergy!

JetBrains updated their ReSharper tool to version 1.5.1.  Fantastic tool.  If you don't use it, you should!

Microsoft released their SQL Server 2000 Service Pack 4.  We've been on SP3a for some time now - there are a lot of fixes, plus MS now added x64-platform support

Though it was released last month, I upgraded Skype to 1.2 from 1.1.

All in all, it was a good day of upgrades and patches.

Friday, May 06, 2005 12:09:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Tuesday, May 03, 2005

I stumbled upon this recently and wanted to share.  As you may be aware, attributes provide a mechanism to describe an assembly, class, method, etc with information known as metadata.  This metadata is useful alter behaviors (e.g. AutoCompleteAttribute), define security restrictions (e.g. FileIOPermissionAttribute), enforce compile-time analyses (e.g. ObsoleteAttribute, CLSCompliantAttribute, AttributeUsageAttribute) or simply provide information (e.g. AssemblyTitleAttribute, AssemblyFileVersionAttribute, AssemblyCopyrightAttribute).

Via reflection you can query a target such as an assembly, et al to see what attributes it has applied.  A simple, but somewhat useless example might resemble the following:

using System;
using System.Reflection;

object[] attribs = Assembly.GetExecutingAssembly().GetCustomAttributes(false);
foreach ( Attribute a in attribs ) {
   Console.WriteLine(a.GetType().FullName);
}

It simply spits out the attributes associated with the running assembly.  It's pretty cool, and there are some applications that take advantage of such functionality.

Most likely you've had the opportunity to version stamp an assembly with the AssemblyVersionAttribute.  If so, your code probably resembled:

[assembly: AssemblyVersion(”1.0.0.0”)]

If you then run the above code on an assembly so versioned, you might be surprised not to see it there.  I don't fully understand (nor have I thought much about it, nor investigated) the reasoning behind this.  If you want to extract the version from the assembly you have to go an alternative route.  Instead, you call the GetName() method on the Assembly to retrieve an AssemblyName object which contains a Version class.  A simple call to .ToString() on the Version will yield the property value.

using System;
using System.Reflection;

Assembly asm = Assembly.GetExecutingAssembly();
Version ver = asm.GetName().Version;
Console.WriteLine(”Version “ + ver.ToString());

Does anyone know of any other attributes that don't translate into metadata such as this?  Am I wrong and it's really there but I just don't see it or am not querying for it properly?

Tuesday, May 03, 2005 2:58:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback

This evening I had the opportunity to attend the inaugural meeting of the Northern Utah .NET User Group and help support the community.  We had about 17 people attend, which was pretty good!  Our local MS Regional Director, Scott Golightly, presented on some of the new and upcoming VS 2005 features (such as ASP.NET Master Pages, Generics, new WinForms enhancements, etc).

The presentation was well received and a lot of fun, even if we could hardly see it due to the bright sunset through blindless/curtainless windows.  We all had a great time and enjoyed some good times.

Tuesday, May 03, 2005 2:29:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback

I am more and more convinced that Virtual PC (and its Server counterpart) are awesome products.  I've been using them for several years now and I absolutely love them.  That said, however, I do run into the occasional weirdness - mostly involving networking.

This really comes into play when constantly changing networks (home, office, hotel, friend's wireless, some DHCP, some not, etc).  All firewall issues aside, I've run into situations (usually in a DHCP environment) where the host can't access the VPC (by NETBIOS, DNS, IP, etc).  Then all of the sudden it can.  Then a few minutes later it doesn't.  I don't see this often, but occassionally I do ;)  When it does happen it's extremely frustrating; you never know when it's going to work and when it's not (85% of the time it doesn't, so you can probably make a pretty good guess as to when it's going to fail).

I haven't ever thoroughly diagnosed the problem, but I think it's a derivative of the fact that it's sharing the NIC with the host and some NICs/networks can't handle it well.  I've made a practice of setting up a private, virtual network with my VPCs rather than have them participate in the host's network directly.  This allows the host direct, named access to the VPCs and also allows for multiple VPCs (yes I've had up to 3 or 4 running w/o perf hits) to participate in their own private network.  By going through these steps, however, you restrict network access to the VPC from other than the host, but for the vast majority of my VPCs, I don't need any other access (except to occasionally test a website or something like that).

Here's how you set it up:

  • First, if you haven't already, add the Local Loopback Adapter.  This is accomplished (on WinXP) by:
    • opening the 'Add Hardware Wizard' (from the Control Panel)
    • indicating that 'Yes, I have already connected the hardware'
    • selecting 'Add a new hardware device' at the bottom of the list
    • selecting 'Install the hardware that I manually select from a list'
    • selecting 'Network adapters'
    • selecting 'Microsoft' and 'Microsoft Loopback Adapter'
  • Once it's been added, you configure the private network via 'Network Connections' in the Control Panel.  I typically rename my network to 'Local Loopback Network' from 'Local Area Connection X'.  I then assign it a unique, non-routable IP address (such as 172.10.0.1 or 192.168.99.1) - something that won't conflict with the various networks into which the host will be connected.
  • Then, open your VPC and manually assign an IP address to it on the same subnet (e.g. 192.168.99.2).
  • At this point, it's incumbent on us to edit the hosts file (found in %WINDIR%\System32\drivers\etc) to map the host name to its respective IP address.

That's pretty much all there is to it.  I've found this technique to be MUCH more reliable than any other mechanism.

Tuesday, May 03, 2005 2:23:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback

Yesterday, I was presented with the following error message while running an application that I created:

The located assembly's manifest definition with name 'XXX' does not match the assembly reference.

The .NET Framework (specifically Fusion) is throwing a FileLoadException.  This particular exception is occuring because .NET is attempting to load and bind to a particular version of a library at runtime.  Now I know what precipitated this particular error:  I had just upped the version # of the application.  The symptoms were apparent, but the remedy wasn't quite obvious at first - at least until I figured out what was truly going on.

I had changed the version of my application (and all dependencies across the board) from version 1.0.0.0 to 2.0.0.0.  This would, under normal circumstances be sufficient to avoid this particular error, however these weren't normal circumstances.  Unfortunately, this particular exception didn't really identify the root of the problem, so I had to dig a bit deeper.

Here's what I did to try to fix the problem:

  • I opened up an administrative console and ran fuslogvw.exe (a handy tool available with the .NET Framework SDK).  This particular tool is great for helping diagnose assembly binding errors.
  • Selected the 'Log Failures' checkbox (this in turn updated HKLM\SOFTWARE\Microsoft\Fusion\LogFailures to 0x00000001).
  • Selected the 'Custom' radio control.
  • Ran REGEDIT adding a string value to HLKM\SOFTWARE\Microsoft\Fusion\LogPath, setting its value to c:\fusionlogs (a folder I created for the purpose of capturing the logs).
  • Ran IISRESET (my issue was, in fact, occurring within an ASP.NET application, and I had to restart the aspnet_wp.exe process for the FUSLOGVW and registry updates to take effect).
  • Reran the ASP.NET application to recreate the error.

With logging in effect, I simply had to refresh the FUSLOGVW dialog and see the exception log appear.

A quick glance in the log indicated that it could not load version 1.0.0.0 of my application.  What!?  Why is it even trying to load the old version?  This shouldn't be happening!  In most normal circumstances, like I said, this wouldn't be an issue.  But it turns out that my application actually has two parts:

  1. The web application (the one manifesting the problem).
  2. An administrative / management console that was installed via MSI on my machine in the \Program Files folder.  This console has the same dependencies as my web application.  The role of the console is to aggregate data that has been tested and verified and 'publish' them to a 'production' database for the web application to consume.  Some of the published data (in the form of XML) contains fully-qualified references to a third-party toolset used to publish the XML so that it can be read back dynamically by the component.

Well, despite having compiled the web application successfully, I had legacy (v1.0.0.0) dlls in the \Program Files directory, so when the data got published it would bake a v1.0.0.0 stamp in the XML file.  At runtime, the ASP.NEt application would try to load the v1.0.0.0 dll when only a v2.0.0.0 dll exists.

Once I saw that this was the problem, it was easy to fix.  The .NET Framework supports a mechanism known as binding redirection which effectively allow you to use a dll of a different version that what was originally bound at compile time.  Therefore, if an assembly were compiled against a strongly-named v1.0.0.0 assembly you can effectively redirect it to use v2.0.0.0 at runtime (note that this really only applies to strongly-named assemblies as there is no version checking for assemblies without strong names - the newer version will be used by default).  In order to accomplish this, you need to establish a .config file with a section that resembles the following:

<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <dependentAssembly>
            <assemblyIdentity name="FancySchmancyLibrary" publicKeyToken="caeeebc55fa1f62d" culture="neutral" />
            <bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
         </dependentAssembly>
      </assemblyBinding>
   </runtime>
</configuration>

I've seen some people try to set oldVersion to something resembling "1.0.*" but that syntax is not supported.  If you want to redirect all 1.0.x.x versions, you must specify a range such as:

<bindingRedirect oldVersion="1.0.0.0-1.0.99999.99999" newVersion=“2.0.0.0“ />

It's a pretty powerful concept and in this particular case it saved my hide.

Tuesday, May 03, 2005 3:42:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, May 02, 2005

I need to do this all the time, yet I invariably forget and spend a few minutes re-figuring it out (and feeling really stupid everytime), only to forget it the next time I need to do it.

If you have an assembly and need to get the PublicKeyToken for one reason or another, the easiest way that I know of is to use the sn.exe (Strong Name Utility) with the -T switch.

sn -T SomeFancySchmancyLibrary.dll

There...now maybe I won't forget it anymore.  Why do some things just not stick?

Monday, May 02, 2005 1:17:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [15]  |  Trackback