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