Friday, August 06, 2004

I ran into something today that had me stumped for a little bit.  I'm developing a website that streams images down to the client.  These images originate from a SQL Server database and are context sensitive as well as user-definable.  I don't yet have my image caching implemented, but that doesn't really play into this discussion.

I have set up an HttpHandler mapped to my custom extension (.dsix) which gets intercepted by aspnet_isapi to be processed (can't wait for Whidbey in which we'll get handlers out of the box for the .asix extension - active server image files :-)  My HttpHandler resolves some HttpContext sensitive details about the request and then proceeds to acquire the image bits from the database.

Now before anyone jumps down my throat, these images are very small (6k-8k) and yes, I'm storing them in memory on the server.  I'm not concerned about this as a matter of fact because 1) my site has very few users (5-15) and 2) there is only 1 image per user in memory.  Should the site get to be more demanding I'll refactor and reevaluate the storage.

Ok, now to the issue...

I created a SqlDataReader object to read my values from the database and could successfully load the image from the database with the following code:

if ( !dr.IsDBNull(USERSTATE_PORTRAIT) ) {
  
long bufferLength = dr.GetBytes(USERSTATE_PORTRAIT, 0, null
, 0, 0);
  
byte[] buffer = new byte
[bufferLength];
   dr.GetBytes(USERSTATE_PORTRAIT, 0, buffer, 0, (int)bufferLength);

  
using ( MemoryStream ms = new MemoryStream(buffer) ) {
      _portrait = 
Bitmap.FromStream(ms) as Bitmap;
  
}
}

This information was later cached.  When it came time to render the image, I used the following code, where res is a reference to the HttpResponse:

user.Portrait.Save(res.OutputStream, user.Portrait.RawFormat);

I had stored a PNG file in the database.  Upon making the call to Image.Save, I would receive a System.Runtime.InteropServices.ExternalException.  The message was 'A generic error occurred in GDI+'.  Well thanks!  At least is wasn't a specific error!

I did a little digging and found an interesting article in Microsoft support about how Bitmaps need access to their originating stream, file, etc.  It turns out that GDI+ may arbitrarily choose to discard the memory occupied by a bitmap, only to rehydrate it when needed later.  For this reason, the source stream or file gets locked.  Aha! I wish I had understood that behavior several years ago when I really needed it.  Well, as it turns out, I don't still have the MemoryStream from which the image was created just hanging around when it comes time to render the image.  So what I have to do is make a copy of the image, and dispose of the original:

...
using ( MemoryStream ms = new MemoryStream(buffer) ) {
   // see Q814675 - PRB: Bitmap and Image Constructor Dependencies for details
  
Bitmap orig = Bitmap.FromStream(ms) as Bitmap;
   _portrait = getPortraitImage(orig);
   orig.Dispose();
}
...

private Bitmap getPortraitImage(Bitmap original) {
   Bitmap ret = new Bitmap(original.Width, original.Height, PixelFormat.Format24bppRgb);
   // copy the original image into the new one
  
using ( Graphics g = Graphics.FromImage(ret) ) {
      g.DrawImage(original, 0, 0);
   }
   return ret;
}

Now, in reality, my actual code is a little different than what I've shown - I've trimmed this down a bit for simplicity.  What you'd probably want to do that I've not shown is check the color depth of the original image and create an image of the same type rather than defaulting to a 24bit image.  Now the HttpHandler might look something like this:

user.Portrait.Save(res.OutputStream, ImageFormat.Jpeg);

Works like a champ!

Friday, August 06, 2004 5:53:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Wednesday, August 04, 2004

Have you ever been working on a project and found yourself constantly copying and pasting code between files to maintain consistency (such as comment preambles, disclaimers, (even code snippets! - ok, I'd recommend against that one!) etc).  It can get quite tedious and error-prone, especially if what you're pasting has a bug, misprint, or something that needs to change universally.  If you've ever had occasion to, upon adding a new file to a VS project, go through and rearrange all of the code to fit your standards and add in your comments (and waste hours upon hours doing so), then creating a custom template is for you.  Using the proper jargon: creating a custom project item is for you.

Creating custom templates isn't an entirely trivial endeavor as you may know if you've tried it, but it's not overly complex.  It involves creating (or editing) a .vsz (Wizard) file, a .vsdir file, and customizing your template files.  This process involves navigating several directories and making tiny changes in each one.

The game changes a little if you're trying to add a WebForm to a web project, as I discovered while spending an hour churning over what seemed like it should have worked.  The reason for my frustration is that VS.NET will, while accepting my .aspx template code, generate its own .aspx.cs code.  In fact, it bases it's code on the C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\DesignerTemplates\1033\NewWebFormCode.cs.  I didn't want to go in and edit that file (except for making a few standards-abiding changes) because that would affect every page on every website that I develop, and that's too far-reaching for my tastes.

Here's what I did.  Note that all directories that I reference are found in C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\ as subdirectories.  Also, the example templates, er, project items that I created are for a website called DevTracker.

1)  Navigate to \CSharpProjectItems and create my wizard file called CSharpAddDevTrackerWebFormWiz.vsz.  The contents of this file:

VSWIZARD 7.0
Wizard=VsWizard.VsWizardEngine.7.1
Param="WIZARD_NAME = CSharpAddDevTrackerWebFormWiz"
Param="WIZARD_UI = FALSE"
Param="PROJECT_TYPE = CSPROJ"

The VsWizard.VsWizardEngine.7.1 is the ProgId (yes, back to COM) of the VS.NET-provided IDTWizard implementation - which saves me the trouble of having to create my own wizard - I'll just hijack the plane already in the runway as it gets me off the ground faster.  In fact, that's the recommended practice in this case.

2)  In the \CSharpProjectItems\WebProjectItems folder create a DevstoneWebProjectItems.vsdir file.  The contents of the .vsdir file:

..\CSharpAddDevTrackerWebFormWiz.vsz|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|DevTracker WebForm|1|A webform for the Devstone DevTracker Website|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|4534|0|DevTrackerWebForm.aspx

For a description on the format of the .vsdir files, check out this link.  Basically, each line in the file is pipe (|) delimited.  Suffice it to say, the first parameter references the .vsz file we created.  The second param (the GUID) identifies the C# extensibility library (also used later as an identifier for the dll so we can extract the same icon as used for WebForms).  The third parameter is the short description that appears along with the icon.  The fourth parameter is the ordinal position of the icon within the 'Add Project Item' dialog.  Then we have the long description, the GUID, the icon resource identifier, a zero ;-), and the default name to use for the to-be-added file.

3)  Navigate to the \CSharpProjectItems\WebProjectItems\UI folder and create a DevstoneUI.vsdir file.  I chose to do this rather than edit the extant ui.vsdir so I can place all of my custom project items therein and will be able to copy my directory tree over the C# folder and not run the risk of overwriting any files.  It's great that the VS.NET IDE automatically picks up all .vsdir files rather than looking for specific ones!  The contents of the .vsdir file:

..\..\CSharpAddDevTrackerWebFormWiz.vsz|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|DevTracker WebForm|1|A webform for the Devstone DevTracker Website|{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}|4534|0|DevTrackerWebForm.aspx

You'll probably see a lot of similarities to the previously created file in #2.

4)  Within the \VC#Wizards directory, create a subdirectory called CSharpAddDevTrackerWebFormWiz.  This name should match what you entered in the .vsz file in step #1 as the WIZARD_NAME.

5)  It is within this new folder that the script file and templates get created - well, in subfolders.  The easiest thing to do is to copy files that already exist (such as those from the CSharpAddWebFormWiz folder).  Then you should have \Scripts\1033 and \Templates\1033 folders with some files.

6)  Feel free to delete the Templates.inf file from \Templates\1033 - it's not needed.  In fact it will screw you up when overriding VS.NET's default behavior of auto-generating the code-behind files.

7)  Edit your WebForm1.aspx file as necessary (I renamed mine to DevTrackerWebForm1.aspx).

8)  Now it's time to create your code-behind file.  The best thing to do is to copy the NewWebFormCode.cs mentioned above and rename it (e.g. DevTrackerWebForm1.aspx.cs).  At this point you're not done.

9)  The \Scripts\1033\default.js file is the code that drives (sets up) the process of replacing [!output] tags in your source.  In order to override the code-behind behavior we have to edit this file.  That's because the default.js file provided for WebForms is written to handle just one file.  The underlying functionality behind all default.js files is actually found within the script \VC#Wizards\1033\common.js.  Fortunately for me :-D, I didn't have to scour this too much.  I found a great article by Chris Sells that identifies how to do what I've already talked about to some depth.  I took his default.js file and tweaked it to match my needs and I'm now ready to go.

All in all, it's not that bad, but it doesn't seem too well documented and can be a bit disconcerting to a newcomer to the template, dang!, project item arena.

Wednesday, August 04, 2004 7:01:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Monday, August 02, 2004

A few days ago, I posted a blog entry mentioning my foray into SCM systems.  The purpose of the investigation was two-fold: 1) to move away from VSS (Sourcesafe) and 2) to enable remote source code access.  I work with a client out in California routinely and using VSS is impractical in this situation.  It didn't make sense not being able to sync up our code every couple weeks when I went to Santa Barbara, and then when he travelled here to SLC not having a SCM...well, let's just say that it hampered our work.  Yes, we found ways to work around the system and we've been pretty effective, but it became too painful, plain and simple.

We considered using SOS (Source Offsite).  SOS runs as an agent over a VSS repository, TCP/IP enabling it.  This would be great - except that we'd still be using VSS which would have violated goal #1.  At my previous employment we used SOS in maintaining source code and sharing it with our remote client site.  It worked well, but VERY slow.  Now, if I'm not mistaken, the server was patched, rebuilt, or something and the perf was noticeably better.

Of the SCM systems we were investigating, the two that stood out the most were SourceGear's Vault and PerForce.

I've heard nothing but good about PerForce.  There seems to be a very strong community backing.  I downloaded it and started to play with it.  To be honest, I really liked it, though it was at first a bit confusing.  I attribute the confusion to my VSS background.  In PerForce the terminology is quite different from VSS (e.g. 'Open for Edit' vs 'Check Out', 'Submit' vs 'Check In'), but I found a terminology differences document and that helped substantially.  Once you look at it and think about it, though, it's really not that bad.  After installing a Visual Studio plug in, I was up and going.  I was very impressed with the speed and ease of use of PerForce (even though I was running it on a Virtual PC).

Vault, on the other hand, is a relative newcomer to the SCM world.  Written purely in C#, and backed to SQL Server, this was an intriguing offering.  After downloading the single-user trial (free BTW) and playing with it I fell in love with it.  Perhaps the biggest advantage to my getting up and running here was that it was built to look and feel like VSS.

We decided to go with Vault as our SCM system for the following reasons.  PerForce offers many of the same capabilities, so this list isn't a set of “Vault has these and PerForce doesn't“ features.

  • Familiarity (no learning curve)
  • TCP/IP capabilities
  • Transactional check-ins
  • Simplicity to install
  • The backing database automatically incorporates itself into my database backup maintenance plan
  • Price ($)
  • The ability to turn off Vault being my primary SCM (I didn't see this anywhere in PerForce).  That way, I can still fall back and use VSS for my own and legacy projects - very important!
Monday, August 02, 2004 4:03:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Wednesday, July 28, 2004

No doubt if you dabbled around in .NET you've come across an attribute in the System namespace called CLSCompliantAttribute (CLSCompliant for short). If not, try running FxCop against your code once in a while - you'll see it real quick. The CLS in this context doesn't mean 'CLear Screen'. Instead it is short for Common Language Specification.

What is the Common Language Specification you ask? Well, in brief, the CLS helps ensure interoperability between languages by defining basic language features and rules; it is a subset of the Common Type System (CTS). Code that is CLS-compliant is guaranteed to be accessible from another CLS-supporting programming language. This level of support and interoperability is fantastic.

If, when writing code you conform to the CLS rules, you forget to add the CLSCompliantAttribute to your assembly, your assembly is NOT compliant. However, if you take the time and effort to add the attribute to your assembly you enter a world where you must now ensure compliance in all types, or at least explicitly identify those that aren't. The same holds true for a type. If you mark a type (i.e. class, struct, interfaces, etc) as CLS-compliant, you must mark all non-CLS-compliant members. Alternatively, you could fix up your code to be CLS-compliant; there is usually a CLS-compliant mechanism that provides the same capabilities.  Types that derive from non-compliant types are inherently not compliant and should therefore also be marked as such.

All you need to do to apply the attribute to your assembly is the following:

using System;
[assembly: CLSCompliant(true)]

As a best practice, I highly encourage you to add this attribute. It will definitely facilitate reuse of your components, and causes the C# compiler to check for compliance in all types.

But what are some of the rules associated with CLS compliance? What constitutes a non-compliant entity? Let's check it out.

   1. Naming conventions: protected or public identifiers must begin with a letter. For example, you can't begin an identifier with an underscore.

   2. Uniqueness of names: in order for a type to be CLS-compliant, it cannot contain members that differ in name solely by case. For example, the following is invalid:

class MyClass {
   protected int value;
   public int Value;
}

   Some languages (such as C# support this), but it's not CLS-compliant, so an alternative might be the following (ok, I know, it's a lame example):

class MyClass {
   protected int val;
   public int Value;
}

   3. Data types: variables of the unsigned varieties (e.g. uint, ulong, etc) are not CLS-compliant. If you expose an unsigned integer from a protected or public member, your type will not be compliant. Instead, consider using the signed variety.

I don't pretend to be exhaustive with this short set of rules, but these are the ones with which I believe most developers will run into trouble. For a more comprehensive view, check this out.

Perhaps it's important to mention that these rules (for the most part) apply only to protected and public members; these are the members that other CLS-compliant langauges will be using to interact with the objects.  You can do what you want in private fields, variables, methods, types, etc.

Wednesday, July 28, 2004 4:53:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback
 Monday, July 26, 2004

Over the years I've been a user of Microsoft's SourceSafe.  Mostly this is because the tool was there and was free (well, it was part of the Visual Studio family of tools).  While for the most part this experiece has been a good one, I have had my fair share of mishaps and issues (mostly regarding web applications).  I'm not going to take the time to delve into these issues as they are incredibly frustrating and I'd spend the time venting, which is not what this blog is about.  I am wanting to get away from VSS for a variety of reasons and am looking into other solutions.

I'm in the process of evaluating other SCM packages and was wondering what your experiences have been with each.  I'm looking for constructive, decision-making feedback.  I'm in the process of evaluating Sourcegear's Vault, and was wondering if anyone has had any experiences (good or bad) with it.

1) VSS
2) Source Depot
3) Perforce
4) CVS
5) Vault
6) Other...

Monday, July 26, 2004 9:51:00 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Friday, July 23, 2004

One of the greatest personal challenges of mine with respect to code (or anything over which I feel ownership) is that of letting go and allowing others to edit, enhance, or in any way modify it.  This is not a quality that I find attractive or endearing, but more of a hurdle.  I do have the experience (many times over I might add) of working in a team environment on a product - but that's different at some level.  In that scenario there's more of a feeling of 'team ownership' despite the fact that I 'own' an area or an aspect of code.

I have several personal side projects that I (along with a friend) have been working on for quite some time.  I have 'ownership' of about 95% of the code - that is, I wrote it, I designed it, I conceptualized it - it's my baby.  Recently, my friend and I extended an invitation to three other mutual friends to join us in developing this software...this was great in concept.  I've always tried to be an includer, so no problem.  However, my heart stops or occasionally skips a beat when these other associates start dabbling in this code that I've had almost exclusive ownership of for the past millenium.  I'm really working hard at overcoming this perceived weakness and feel that I'm conquering it - but it's hard.

What can I do to convince myself that these other contributors will surprise me and write great code without my meddling?  I believe I need to educate them on the initial ideas and concepts and direction and then simply have a degree of trust and confidence.  It's mostly a matter of trust and learning to let go.

Friday, July 23, 2004 5:47:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Thursday, July 22, 2004

You ever have the need to validate whether the user entered a valid IP address?  Regular expressions come in really handy here.  However, if you do some searching online for a good regex to accomplish this, you'll undoubtedly come across some regular expressions that are pretty lousy or only half-baked.

When validating an IP address, we only want to accept values between 0.0.0.0 and 255.255.255.255.  I wrote the following function which seems to do the trick nicely:

[UPDATE: 09/28/2006]

As mentioned by a few of the readers, my initial regular expression was shortsighted and I overlooked a subset of numbers.  I've updated my solution to use a suggestion by AleXX below, which correctly validates an ip address from 1.0.0.0 to 255.255.255.255.

Thanks for the observations, guys, and the corrections.  I'll be more thorough in the future.

public bool IsValidIPAddress(string ipAddr) {
  
string pattern = @"^([1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}$";
  
Regex reg = new Regex(pattern, RegexOptions.Singleline | RegexOptions.ExplicitCapture);
  
return reg.IsMatch(ipAddr);
}

Thursday, July 22, 2004 7:06:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [12]  |  Trackback

Now that I'm back from the funeral and back in action I thought I'd create a post that hopefully will prove to be helpful to someone out there in the cyberworld seeking guidance.  This one's regarding constructors.

A while back, while dabbling in the realm of constructors, I came upon a little tidbit that I haven't seen documented - not that it isn't documented, I just didn't go searching for it.  If you have a class (or a struct) in which you have several overloads on the constructor where each constructor does a lot of the same work, you can save yourself some headache.  In a fashion similar to how you call the base class's constructor, you can call another constructor by simply calling the this(...) method.  So instead of having code that resembles the following:

public sealed class Customer {
   private string _name;
   private DateTime _startDate;

   public Customer(string name) {
      _name = name;
      _startDate = DateTime.Now();
   }

   public Customer(string name, DateTime startDate) {
      _name = name;
      _startDate = startDate;
   }
}

you have code that looks like this:

public sealed class Customer {
   private string _name;
   private DateTime _startDate;

   public Customer(string name) : this(name, DateTime.Now()) { }

   public Customer(string name, DateTime startDate) {
      _name = name;
      _startDate = startDate;
   }
}

Granted, this is a simplistic case that doesn't really do much, but it saves the burden of repeating code unnecessarily and give you the benefit of having a single working constructor (or at least just a few) which greatly simplifies the readability of your code.

Thursday, July 22, 2004 5:43:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [2]  |  Trackback
 Thursday, July 15, 2004

Today has indeed been a somber day.  Today just before 4:00 PM my beloved grandmother (Granny) passed away.  Up until just a few days ago she's been as active as ever, only just recently a dormant cancer finally caught up to her.  Granny was a wonderful person, a devoted mother and grandmother, and  genuine saint.  Granny leaves behind an incomparable legacy.  We were planning on traveling down to Las Cruces, NM tomorrow to see her as we knew she was ill, but as fate would have it that was not to be.  Instead, we will be traveling down on Sunday for the Tuesday funeral.

I feel genuinely blessed to have known Granny as well as I did - I have many wonderful memories taking care of her garden, playing her piano, doing puzzles with her, and hanging out.  I vividly recall spending (almost) every Sunday evening at her house while growing up.  There we would eat her famous beans, Pa would pop popcorn, and we'd watch Laurence Welk, The Wonderful World of Disney, and Mutual of Omaha's Wild Kingdom.  We would play in the backyard, climb her trees, and build forts and haunted houses in the basement.

I had the privilege of going down to Las Cruces with my previous company in January.  That turned out to be my farewell visit and I am very grateful for it.

Granny and Pa had five children - 4 daughters and a son.  Twenty seven grandchildren and I don't know how many great-grandchildren.  At our family reunion last November we had 96 people there - direct descendants and their spouses - and not quite everyone was accounted for.  What a wonderful experience.

Granny, we love you and are deeply honored to have you as our grandmother.  Your devotion to family and church was amazing.  You will be remembered for so many things: your gardens, your wonderful Thanksgiving dinners, your charity, your beans, your basement where we built many a haunted house, your puzzles, your backyard, your incomparable quilts, and much, much more.  But most of all you will be remembered for being our Granny.  We love you very much.

Thursday, July 15, 2004 4:50:00 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [0]  |  Trackback