# Monday, June 09, 2008

A Simple Sound Effects Engine for Silverlight 2

Audio support is all too often one of the last things a developer thinks of when it comes to building a user interface. Fortunately, Silverlight offers some basic building blocks that make it fairly trivial to add event-driven sounds to our applications. I have taken the built-in functionality one step further, and created a simple run-time audio engine that makes it even easier.

First, I will discuss the requirements that I had in mind when I created this engine. Second, I will discuss the ideal API for a complete solution. Third, I submit the simple effects engine that I developed in order to meet those requirements and API.

Sound Effects Engine Requirements

  • Must be able to play looping “background music” tracks.
  • Must be able to play smaller sound effects on demand.
  • Must support concurrent (and overlapping) sounds effects without interfering with already-playing audio.
  • Must adequately handle the possibility of download delays when pulling down external audio files.
  • Should cache audio files between playbacks.
  • Should allow serving of audio from high-bandwidth CDN such as Silverlight Streaming.

An Ideal Sound Effects API

  • Should be very approachable. If possible, only a single line of code to play any audio (similar to PlaySound() API).
  • Should support any formats supported by Silverlight.
  • Should be efficient.
  • Should offer download and playback completion callbacks (for chaining and synchronization).
  • Should support aggressive background pre-caching of audio files.

The SilverlightToolbox Solution

To address the simplicity requirements, this sound effects engine is implemented as a single static C# class that can be easily included into any Silverlight project. It can exist in a referenced class library, or can be linked directly into the main project.

To address looping background music, a single method SetBackgroundLoop() is supplied. This will begin playback of a single audio clip, and will automatically repeat it. Only one track can be played as the background loop – calling this method again will terminate the running track and start the new one. You can also pass String.Empty to stop all background music.

To address typical sound effect clips. two overloads of the PlaySoundEffect() method are supplied. The simpler of the two methods will simply play a clip once it has been downloaded. The extended version allows you to specify a callback for when the clip has been downloaded and started playing, and a second callback that can be used for notification that the clip has ended.

To address the scenario where multiple sound effects might be played at the same time, the engine internally maintains a queue of MediaElements. When a new sound needs to played, it pulls an unused element and puts it into service. Once the sound is finished, the element is returned to the queue. This allows Silverlight itself to handle media mixing, and by caching those MediaElements, it does so without placing undue stress on the environment.

To address the issue of download delays, the startedCallback and completedCallback parameters of PlaySoundEffect() were introduced.

To address caching of audio files, a WebClient is used (which in turn leverages the browser’s cache). Furthermore, the engine will reuse any downloaded file streams (and is smart enough to not re-queue the same file if it already queued for download).

By using a MediaElement for playback, audio tracks are automatically supported from streaming sources and Content Delivery Networks, and can be of any type supported by Silverlight.

To support chaining of audio effects, and to support synchronization of UI with audio, the PlaySoundEffect() method accepts callback events that are fired when the clip has been downloaded, and again when it has completed playback. To further help with UI synchronization, a second utility class is provided (DelayedAction) which provides a simple wrapper for BackgroundWorker that can be used to easily delay execution of a block of code after a specified amount of time.

To support pre-caching of audio files, the PreloadMedia() method can be used.

Typical usage:

In order for the sound effects engine to function properly, it must be provided with a top-level XAML container (MediaElement currently will not perform playback without a parent object). This is done by calling the Initialize() method, typically from your program’s startup code:

SoundEffects.Initialize(this.LayoutRoot);

 

After this has been done, background music can be played, and we can also queue up some sound effect clips for later:

SoundEffects.SetBackgroundLoop("cautious-path.wma");
SoundEffects.PreloadMedia("pop1.wma");

 

Then, at various points throughout the application, we can play the sound effect (for example, in response to a control event):

SoundEffects.PlaySoundEffect("pop1.wma");

 

If at any point we need to delay the sound effect slightly, we can control this be introducing a short delay:

DelayedAction.Execute(2.0, () => SoundEffects.PlaySoundEffect("pop1.wma"));

 

Complete Source Code for DelayedAction.cs:

   1: using System;
   2: using System.ComponentModel;
   3: using System.Threading;
   4:  
   5: namespace Wintellect.SilverlightToolbox
   6: {
   7:     public class DelayedAction
   8:     {
   9:         class DelayedCallback
  10:         {
  11:             public TimeSpan Delay { get; set; }
  12:             public Action Callback { get; set; }
  13:         }
  14:  
  15:         public static void Execute(double seconds, Action callback)
  16:         {
  17:             BackgroundWorker Delay = new BackgroundWorker();
  18:             Delay.DoWork += (s, e) =>
  19:             {
  20:                 DelayedCallback DelayCallback = (DelayedCallback)e.Argument;
  21:                 Thread.Sleep(DelayCallback.Delay);
  22:                 e.Result = DelayCallback.Callback;
  23:             };
  24:             Delay.RunWorkerCompleted += (s, e) =>
  25:             {
  26:                 Action Callback = e.Result as Action;
  27:                 Callback();
  28:             };
  29:             Delay.RunWorkerAsync(new DelayedCallback
  30:             {
  31:                 Delay = TimeSpan.FromSeconds(seconds),
  32:                 Callback = callback
  33:             });
  34:         }
  35:     }
  36: }

 

Complete Source Code for SoundEffects.cs:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.IO;
   4: using System.Net;
   5: using System.Windows;
   6: using System.Windows.Controls;
   7: using System.Windows.Media;
   8:  
   9: namespace Wintellect.SilverlightToolbox
  10: {
  11:     public static class SoundEffects
  12:     {
  13:         static Panel Root;
  14:         static MediaElement BackgroundLoop = new MediaElement();
  15:         static WebClient EffectDownloader = new WebClient();
  16:  
  17:         static Queue<MediaElement> AvailableSoundEffectGenerators = new Queue<MediaElement>();
  18:         static Dictionary<string, Stream> DownloadedEffects = new Dictionary<string, Stream>();
  19:         static Queue<string> PendingDownloads = new Queue<string>();
  20:         static Queue<QueuedEffect> PendingEffects = new Queue<QueuedEffect>();
  21:         static Dictionary<MediaElement, Action> PendingStartupCallbacks = new Dictionary<MediaElement, Action>();
  22:         static Dictionary<MediaElement, Action> PendingCompletionCallbacks = new Dictionary<MediaElement, Action>();
  23:  
  24:         enum TargetType
  25:         {
  26:             BackgroundMusic,
  27:             SoundEffect,
  28:         }
  29:  
  30:         class QueuedEffect
  31:         {
  32:             public string MediaName { get; set; }
  33:             public TargetType Target { get; set; }
  34:             public Action StartedCallback { get; set; }
  35:             public Action CompletedCallback { get; set; }
  36:         }
  37:  
  38:         public static void Initialize(Panel root)
  39:         {
  40:             Root = root;
  41:             InitializeTarget(root, BackgroundLoop);
  42:             EffectDownloader.OpenReadCompleted += (s, e) =>
  43:             {
  44:                 DownloadedEffects[(string)e.UserState] = e.Result;
  45:                 DownloadEffects();
  46:                 PlayEffect();
  47:             };
  48:         }
  49:  
  50:         static void DownloadEffects()
  51:         {
  52:             if (PendingDownloads.Count == 0)
  53:                 return;
  54:             string MediaName = PendingDownloads.Dequeue();
  55:             EffectDownloader.OpenReadAsync(new Uri(MediaName, UriKind.Relative), MediaName);
  56:         }
  57:  
  58:         static void InitializeTarget(Panel root, MediaElement target)
  59:         {
  60:             target.Width = 0;
  61:             target.Height = 0;
  62:             target.Visibility = Visibility.Collapsed;
  63:             root.Children.Add(target);
  64:             target.AutoPlay = false;
  65:             target.MediaOpened += (s, e) =>
  66:             {
  67:                 MediaElement Target = s as MediaElement;
  68:                 Target.Volume = 0.35;
  69:                 Target.Play();
  70:                 if (PendingStartupCallbacks.ContainsKey(Target))
  71:                 {
  72:                     Target.Dispatcher.BeginInvoke(PendingStartupCallbacks[Target]);
  73:                     PendingStartupCallbacks.Remove(Target);
  74:                 }
  75:             };
  76:             target.MediaEnded += (s, e) =>
  77:             {
  78:                 MediaElement Target = s as MediaElement;
  79:                 Target.Stop();
  80:                 if (s == BackgroundLoop)
  81:                     Target.Play();
  82:                 else
  83:                     AvailableSoundEffectGenerators.Enqueue(Target);
  84:  
  85:                 if (PendingCompletionCallbacks.ContainsKey(Target))
  86:                 {
  87:                     Target.Dispatcher.BeginInvoke(PendingCompletionCallbacks[Target]);
  88:                     PendingCompletionCallbacks.Remove(Target);
  89:                 }
  90:             };
  91:         }
  92:  
  93:         static MediaElement GetUnusedEffectGenerator()
  94:         {
  95:             if (AvailableSoundEffectGenerators.Count > 0)
  96:                 return AvailableSoundEffectGenerators.Dequeue();
  97:             else
  98:             {
  99:                 MediaElement Result = new MediaElement();
 100:                 InitializeTarget(Root, Result);
 101:                 return Result;
 102:             }
 103:         }
 104:  
 105:         static void PlayEffect()
 106:         {
 107:             lock (PendingEffects)
 108:             {
 109:                 if (PendingEffects.Count == 0)
 110:                     return;
 111:                 QueuedEffect Effect = PendingEffects.Dequeue();
 112:                 if (DownloadedEffects.ContainsKey(Effect.MediaName))
 113:                 {
 114:                     MediaElement TargetElement = null;
 115:                     switch (Effect.Target)
 116:                     {
 117:                         case TargetType.BackgroundMusic:
 118:                             { TargetElement = BackgroundLoop; break; }
 119:                         case TargetType.SoundEffect:
 120:                             { TargetElement = GetUnusedEffectGenerator(); break; }
 121:                     }
 122:                     if (Effect.StartedCallback != null)
 123:                         PendingStartupCallbacks.Add(TargetElement, Effect.StartedCallback);
 124:                     if (Effect.CompletedCallback != null)
 125:                         PendingCompletionCallbacks.Add(TargetElement, Effect.CompletedCallback);
 126:                     TargetElement.SetSource(DownloadedEffects[Effect.MediaName]);
 127:                 }
 128:                 else
 129:                 {
 130:                     PendingEffects.Enqueue(Effect);
 131:                 }
 132:             }
 133:         }
 134:  
 135:         static void PlaySound(TargetType target, string mediaName, Action startedCallback, Action completedCallback)
 136:         {
 137:             if (target == TargetType.BackgroundMusic)
 138:                 if (BackgroundLoop.CurrentState != MediaElementState.Stopped)
 139:                     BackgroundLoop.Stop();
 140:  
 141:             if (mediaName != String.Empty)
 142:             {
 143:                 lock (PendingEffects)
 144:                 {
 145:                     if (!DownloadedEffects.ContainsKey(mediaName))
 146:                     {
 147:                         PendingDownloads.Enqueue(mediaName);
 148:                     }
 149:                     PendingEffects.Enqueue(new QueuedEffect
 150:                     {
 151:                         MediaName = mediaName,
 152:                         Target = target,
 153:                         StartedCallback = startedCallback,
 154:                         CompletedCallback = completedCallback
 155:                     });
 156:                 }
 157:                 DownloadEffects();
 158:                 PlayEffect();
 159:             }
 160:         }
 161:  
 162:         public static void SetBackgroundLoop(string mediaName)
 163:         {
 164:             PlaySound(TargetType.BackgroundMusic, mediaName, null, null);
 165:         }
 166:  
 167:         public static void PlaySoundEffect(string effectName)
 168:         {
 169:             PlaySound(TargetType.SoundEffect, effectName, null, null);
 170:         }
 171:  
 172:         public static void PlaySoundEffect(string effectName, Action startedCallback, Action completedCallback)
 173:         {
 174:             PlaySound(TargetType.SoundEffect, effectName, startedCallback, completedCallback);
 175:         }
 176:  
 177:         public static void PreloadMedia(string mediaName)
 178:         {
 179:             if (!DownloadedEffects.ContainsKey(mediaName))
 180:             {
 181:                 PendingDownloads.Enqueue(mediaName);
 182:                 DownloadEffects();
 183:             }
 184:         }
 185:     }
 186: }
Monday, June 09, 2008 5:59:22 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [7] | 

# Thursday, April 03, 2008

Awesome tool for Media Center - Lifextender

I have been running a Media Center PC as the hub of my home media network for a few years now - first MCE 2005 and then VMC (Vista Media Center) once it became available. In fact, I am about to install my 3rd XBOX 360 in my house (I use those as media extenders wherever I have a TV). I love my media setup - and more importantly - so does my wife.

I pretty much just let things run and don't think too often about it... but this week I ran across this completely awesome application which makes my setup even better... Lifextender.

Lifextender has a very simple premise, which is stated on the home page of it's website: "Lifextender is a dead-simple commercial-removal application". The program basically runs in the background where it monitors your system for new recordings. It then analyzes those recordings and removes any commercial pods that it finds. It is pretty good - I would say the success rate is about 95%. When it makes it mistake, it seems to always err on the side of caution and has never removed something I didn't want it to. The modified recordings are about 75% of the size of of the originals - which means it is literally giving me back 15 minutes of my life for every "hour" of commercial-infested programming that I watch.

And oh did I mention - It's also FREE (free as in beer)!

Thursday, April 03, 2008 6:46:12 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Monday, January 07, 2008

Performing Seek operations in Silverlight on a MediaElement

I ran into a small, but dastardly quirk in Silverlight today. I haven't seen it mentioned out there so figured I would post this in case someone else might suffer from the same problem someday...

If you have a MediaElement being used to display video, chances are pretty good that you also have what is known as a "scrub bar". or at least some buttons used to jump forward or backward in the video timeline. The documentation says that you can accomplish this by interacting with the Position property of the MediaElement. The Position property is of type TimeSpan, which is documented as being immutable except for the Seconds property, which is read/write. In other words, the only way to change the current playback position of a playing media element is to set Position.Seconds to the location you wish to jump to.

The only thing is - this actually doesn't work if you take the documentation at face value. This bit of code doesn't work as you might think it would:

MyVideo.Position.Seconds = MyVideo.Position.Seconds + 10.0;

In fact, that code has no effect at all. No error message, but also no change to the position.

The secret here is that while the TimeSpan is indeed modifiable through the Seconds property, the MediaElement doesn't really care about it unless you reassign the Position property itself. To compound this, there is no way to create your own TimeSpan from code in Silverlight 1.0, so the only option we have is to take an existing one. Here is the code to change the current position (which actually works):

var pos = MyVideo.Position;

pos.Seconds = pos.Seconds + 10.0;

MyVideo.Position = pos;

HTH

Monday, January 07, 2008 3:09:59 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Saturday, December 01, 2007

Silverlight Bug: Using a packaged image source for multiple Image elements

I ran into this nasty little bug today... it is not easy to reproduce, but when it happens it is very annoying to debug.

First, this bug only occurs when you are using a ZIP file to package your external resources and using a Silverlight Downloader object to bring the package to the browser, and subsequently using Image.SetSource() to provide the image files to the visual elements.

Second, it's not really a bug in functionality exactly, but more of a nuisance to the viewer. You see, the images get displayed correctly, but Silverlight throws extra error messages.

The problem occurs when you reuse a single image resource from a downloaded zip archive for multiple Image elements. And it only happens when the two images are assigned to the same resource consecutively. Example:

function downloadComplete(sender, eventArgs)
{
  // this one is OK
  sender.GetHost().FindName(“Image1”).SetSource(sender, “picture.png”);
  // this one fails
  sender.GetHost().FindName(“Image2”).SetSource(sender, “picture.png”);
}

This is using Silverlight 1.0, but I assume the same issue also happens in 1.1.

The problem appears to be related to referencing the same resource twice consecutively. If you reference another resource from the Downloader between the two calls to SetSource(), then the problem goes away. Also, if you simply don't reuse the same resource like this, then it can be avoided.

UPDATE January 2008: My previous "fix" of referencing another resource in between uses of the same resource does not seem to always work. In addition, some Silverlight installations (not all!) are also raising spurious ImageError 4001 messages. The only complete fix I have found is to implement a custom error handler, and ignore those ImageErrors:

function handleError(sender, errorArgs)
{
  if (errorArgs.errorType == "ImageError" && errorArgs.errorCode == 3002)
    {
      // This error is raised sporadically by SetSource
      return;
    }
    if (errorArgs.errorType == "ImageError" && errorArgs.errorCode == 4001)
    {
      // This error is raised sporadically by SetSource
      return;
    }
  Silverlight.default_error_handler(sender, errorArgs);
}

Saturday, December 01, 2007 12:15:55 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Tuesday, October 23, 2007

10 interview tips that will help you land a better job

My last post was somewhat negative. I acknowledge that. I was annoyed. Let me make amends by posting some useful, positive information for those who find themselves on the other end of the interview table (or phone).

  1. Google Live Search your interviewer. Most recruiters will tell you who will be performing your interview. If they don't venture the information, then ask for it - chances are if they know then they will tell you. Take that name and do some research. Get to know what your interviewer is most likely to be questioning you about. If they have written a book on security best practices, then there is a pretty good chance they will ask you questions about security. Do drop subtle hints that you read their weblog - most tech people like to hear that - just be sparing in it. You don't want to come off as a stalker.
  2. Be honest about your skill level. Many interviewers will ask you how you rate yourself, so that they can judge just how delusional you are. Be honest here. If you claim to be a 10 out of 10 in a subject area, then you had better be an absolute expert. A 10 out of 10 is expected to know that subject inside and out, perhaps even teach the interviewer a thing or two. Don't be too humble though. Rating yourself as a 2 out of 10 is highly unlikely to get you into the interview room in the first place.
  3. Don't interrupt. When the interviewer is speaking, never (and I mean NEVER) forcibly interrupt them. I have had many interviews where the candidate would inject words, statements, or questions while I was trying to guide the interview towards the next topic. Doing that does not impress the interviewer with your profound wisdom. What it does is send a clear message that you feel your thoughts are more important than the interviewer's... which is nothing but a fast track to bottom of the resume stack.
  4. Speak clearly. No need to shout, but you should be certain that you speak confidently and with ample volume to be heard clearly. Avoid slang terms - especially cursing! Try to provide concise and accurate answers, with as few "umms" as possible. And for heavens sake, if you don't know the answer to a question, just say it rather than trying to BS your way through it.
  5. Be prepared to ask questions about the employer's business and work environment. Even if you already know the answers (you did research the company's website beforehand, didn't you?). This shows an interest in the company and team.
  6. Be prepared to answer strange, sometimes seemingly irrelevant questions during the interview. Good interviewers are not looking for right or wrong answers to every question - they are looking for clues about how you work your way through problems and how you are able to handle stressful situations. This is especially true in the Consulting industry, where your reaction to a crisis is often more visible and more important than the act of correcting the underlying problem itself.
  7. Follow-up. If you already did a technical screening and are now in for the "big face to face" interview, chances are good that you missed at least one of the questions during the screening. You should note those questions during the initial interview, and research them before going to the main interview - especially if the interviewer is the same person. Let them know that you felt compelled to research the topic on your own time, and that you now have an answer for that missed question if they would like to hear it. Chances are they won't want to hear the answer, but they will take notice that you followed up and did your homework in between the interviews.
  8. Be calm. It is normal to be nervous. Some folks even have obvious nervous twitches that come out in full color during something as stressful as an interview (sweating, fast talking, jittering). Take a few steps to defeat those demons beforehand - exercise rigorously the morning before your interview, this will clear your mind and stabilize your metabolism, which helps control sweating problems. Learn some deep breathing exercises if you need help calming down beforehand. Never, under any circumstances, drink a "Double Vente Latte Machiatto" or other "energy drink" less than an hour before the interview. All that caffeine will lead to a panicky interviewee that talks WAY too fast and incoherently, and that is trying to hold their bladder for an hour. Nothing good can come of that.
  9. Sleep well the night before. Don't study your "interview cheat sheet" late into the night. Go to bed early, and go to bed with a clear mind. You will not sleep well if you spend the night ruminating over those questions and answers. In fact, I understand that taking an over-the-counter aid (if you are healthy enough) can help. Seasickness medications such as Bonine can clear nausea from "stomach butterflies" that might precede a stressful interview, and also have the nice side effect of being a sleeping aid (causes drowsiness). As always, check with your doctor before taking something like that.
  10. Smile. The interviewer is not only looking to see if you know the technical answers to their questions, they are also evaluating whether they would want to work with you on a team or not, and if you know how to enjoy working with others. When interviewing, the measuring stick I often use is to ask myself "Would I be comfortable having a beer after work with this person?". You might be surprised how often the answer to that is "no" (especially in the ultra-bizarre circus of tech workers).

And here is a "bonus" tip submitted by David Daughtrey...

11. Dont eat at the Wing Factory on the night before an interview. Or any hot wing establishment for that matter.

Tuesday, October 23, 2007 2:22:18 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [1] | 

# Thursday, June 14, 2007

Quick Tip - Input Controls in Silverlight

UPDATE 10/25/2007: Updated this sample to function correctly with the Silverlight 1.0 release. The main changes necessary were the typical 0.9 to 1.0 changes (remove "Sys.", isWindowless now takes a string and not a boolean, event handlers no longer string based), and I also had to explicitly set the z-index of the INPUT element. Thank you Erik for bringing it to my attention.

 

I have seen a lot of questions and confusion regarding capturing input when using Silverlight. There are no Button, TextBox, or other similar controls to work with. I see many folks asking for help with building their own directly within Silverlight - and bless their hearts, that is a daunting task indeed! The UI model currently does not offer basic input capture features such as "focus" or "tabbing", or even control-level keystroke capture. So folks tend to start building those basic services first, before they ever even get to writing code and xaml to support that simple textbox they need.

If you read that first paragraph and thought "thats crazy, it shouldn't be that hard!", then I would agree with you. Fortunately, there is a FAR easier and more robust way to achieve the same thing. In fact, its something that is not new to Silverlight at all, it's been with us for years. I am of course talking about the tried-and-true html <input> tag.

One of the most overlooked aspects of Silverlight is that it is a component, not a platform. Your browser is the platform. It can do a lot of stuff, if you just ask it to. Nobody wants an entire site as a single Silverlight canvas, just like nobody wants an entire site as a Flash canvas (unless possibly it is a mini-game or rich media application)... Flash designers realized this fact years ago. And as a component, a part of the solution if you will, Silverlight can play nicely with it's neighbors. With just a little bit of effort and sprinkling a very minimal amount of javascript pixie dust, we can get a Silverlight applet talking to the rest of our html DOM. And that's exactly what I am going to show in this topic...

You can download the code demonstrated in this article here (QuickTip-TextboxesInSilverlight.zip).

First of all, I am doing this with Silverlight 1.0 beta (the javascript one), as I think the 1.1 alpha is far too likely to change, and this technique should work with either. That, and I am lazy and don't want to come back and revisit this post later to correct the code...

Secondly, I am using the current CTP builds of Visual Studio Orcas and Blend 2 (the May 2007 bits), both with the Silverlight extensions. If you are using something else, then your mileage may vary.

UPDATE 10/25/2007: Code updated for VS2008 beta2 and the RTM version of Silverlight 1.0.

Now on to the code... to be sure we are on the same page, I am creating a new project from scratch...

First, Create a new project in Blend. Select the Silverlight 1.0 (JavaScript) project type. It does not matter what you name the project, but for this example I went with "TextboxesInSilverlight".

Switch to XAML view and replace the default canvas with this markup:

<Canvas
 xmlns="
http://schemas.microsoft.com/client/2007"
 xmlns:x="
http://schemas.microsoft.com/winfx/2006/xaml"
 Width="252" Height="272"
 Background="#FFFF2121"
 >
  <TextBlock Width="64" Height="24" Canvas.Left="8" Canvas.Top="8"
             Text="Opacity" TextWrapping="Wrap"/>
  <Ellipse Opacity="1" Fill="#FF0406FF" Stroke="#FF000000"
           x:Name="TheCircle" Width="180" Height="180" 
           Canvas.Left="36" Canvas.Top="64"/>
</Canvas>

This will create a simple red canvas, with a blue circle.

At this point, I generally switch over to Visual Studio Orcas since Blend does not have Intellisense nor does it really know how to deal with JavaScript. You can do this easily by right-clicking a project item (such as the xaml file) and selecting "Edit in Visual Studio".

Next, we need to do one small housekeeping chore to make sure that our Silverlight canvas plays nicely - specifically, we need to ask it to operate "windowless", which will allow other dhtml elements to overlay it, Open the Default.html.js file, and modify the call to Silverlight.createObjectEx(). We want to add the parameter for isWindowless...

 Silverlight.createObjectEx({
  source: "Scene.xaml",
  parentElement: document.getElementById("SilverlightControlHost"),
  id: "SilverlightControl",
  properties: {
   width: "100%",
   height: "100%",
   version: "0.9",
   isWindowless: "true"
  },
  events: {
   onLoad: sceneLoaded
  }
 });

To make things a bit more "clean", we will create the input element directly from code, however it's always good to control visual styling with CSS. Therefore, open up the Default.html, and alter the <style> tag to match the following:

 <style type="text/css">
  div, body, input
  {
   margin: 0;
   padding: 0;
  }
  #opacity
  {
   margin: -272px 0 0 75px;
   z-index: 100;
  }
  .silverlightHost
  {
   margin: 40px auto auto auto;
   height: 272px;
   width: 252px;
  }
 </style>

This will handle the placement and sizing of the silverlight container <div> as well as the input control itself. The negative margin is not a typo - this is used to pull the input control "on top" of the silverlight canvas. We could have also used absolute positioning, but that is much more brittle, relative positioning FTW. Also, notice the use of "auto" margins for the main Silverlight Host <div>. This is how you can center content without resorting to using <center> or <table>... if you take nothing away from this post, at least remember that one trick.

The last thing we must do is wire the whole thing up. This can be done in many places, in many ways. I consider this particular TextBox to be an extension of the Xaml "scene", so I will add my code to the TextboxesInSilverlight.Scene class which was created for us by the Blend project template. This is not the only place you could do this kind of code, but I found that in this particular example it made the most sense. Had I been building a dialog for a game engine, I might have this code in a seperate script file that manages my game mechanics (but thats another article...).

First, we need to capture a global reference to the Scene object that is created (this object is instantiated by the createSilverlight() function of the Default.html.js script file we edited in a previous step). The purpose of capturing this reference is that we will need it later in an event handler. This will allow our html <input> control to communicate back with the silverlight content. This is easier done than said. Open the Scene.xaml.js file. Just before the definition of the TextboxesInSilverlight.Scene.prototype (look up javascript prototype for what this is if you are interested, but that discussion is out of scope for this article), add a line of code to declare the global reference:

var globalScene = null;

Now we will create a callback function that will be used to create our JavaScript object and initialize it:

function sceneLoaded(control, userContext, rootElement)
{
    globalScene = new TextboxesInSilverlight.Scene();
    globalScene.handleLoad(control, userContext, rootElement);
};

Now, the idea for this example is that the value of the text box will determine the "Opacity" Xaml property of the Ellipse shape in our markup. In order for the event handler we are about to add to be able to do this, we will capture a reference to the circle object (technically we can wait and use findName() later during the event handler, but I prefer to capture it only once - its just my style of coding). Add this line to the handleLoad function:

this.circle = control.content.findName("TheCircle");

Next, we will create the input control and add it to the DHTML document. We will add it directly to the same <div> that Silverlight has injected itself into, and therefore any layout or positioning that affects the Silverlight canvas will also affect our <input> box. Add this code to the end of the handleLoad function:

var opacityEdit = window.document.createElement("input");
opacityEdit.type = "text";
opacityEdit.id = "opacity";
opacityEdit.name = "opacity";
opacityEdit.value = "1.0"
;

We are almost done - only two more steps and then we can fire this thing up! First, we need to add an event handler to react to changes in the value property of the input control. Add this code to the very end of the handleLoad function:

opacityEdit.onpropertychange = function()
    {
        if (event.propertyName == "value")
        {
            globalScene.circle.Opacity = event.srcElement.value;
        }
    }

This effectively creates an anonymous function to handle property change events on the <input> control, which in turn updates the Opacity property of the circle shape. Cool, huh?

The last thing to do is finally add the new <input> element to the page, otherwise all the work until this point will have had no discernable impact at all... add this one last line to the handleLoad function:

this.control.parentElement.appendChild(opacityEdit);

At this point, your Scene.xaml.js file should look like this:

if (!window.TextboxesInSilverlight)
 window.TextboxesInSilverlight = {};

TextboxesInSilverlight.Scene = function()
{
}

var globalScene = null;

function sceneLoaded(control, userContext, rootElement)
{
    globalScene = new TextboxesInSilverlight.Scene();
    globalScene.handleLoad(control, userContext, rootElement);
}

TextboxesInSilverlight.Scene.prototype =
{
 handleLoad: function(control, userContext, rootElement)
 {
  this.control = control;
  
  this.circle = control.content.findName("TheCircle");
  
  var opacityEdit = window.document.createElement("input");
  opacityEdit.type = "text";
  opacityEdit.id = "opacity";
  opacityEdit.name = "opacity";
  opacityEdit.value = "1.0";
  opacityEdit.onpropertychange = function()
    {
      if (event.propertyName == "value")
      {
        globalScene.circle.Opacity = event.srcElement.value;
      }
    }
  
this.control.parentElement.appendChild(opacityEdit);
 } 
}

If the typo gods favor you, then you should be able to use F5 to run the page and see it all working together:

Notice the positioning of the input textboxes, and the interaction of them with the underlying Silverlight canvas. This is just a simple example, but can be a very powerful way to "plug in" Silverlight into the surrounding DHTML.

Thursday, June 14, 2007 4:25:05 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Monday, March 05, 2007

Longest Posts EVAR

I catch a lot of crap from my peers about the length of my posts and their apparent lack of reasonable brevity. Well complain no more, because my pal Dan has totally jumped the shark with his pair of matching Longest Posts Evar:

 

Office SharePoint Server 2007 - Forms Based Authentication (FBA) Walk-through - Part 1

and the sequel to the bestselling novel:

Office SharePoint Server 2007 - Forms Based Authentication (FBA) w/MySites Walk-through - Part 2

 

Heck, even the titles are huge.

Monday, March 05, 2007 2:12:22 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Tuesday, January 09, 2007

Adventures in Databinding ~ Part 2 ~ Simple versus Complex Binding

Databinding in .NET is immensely powerful. You can bind nearly any property of any object to any property of any control. The source object can be any .NET object - a business component, a DataSet, a built-in .NET class, or even another Control! Of course like any powerful feature, the Devil is in the Details.

Simple versus Complex Binding

To understand the databinding system first requires acknowledgement that there is in fact two "flavors" of databinding. You can bind to two very different categories of objects, and this is what differentiates the flavors of binding. The two flavors are called Simple and Complex binding. This is somewhat of a misnomer in my opinion as there is nothing inherently more complicated about Complex binding - it's just different. I would prefer to call these two forms of binding Item and List binding, as that more accurately describes their use. In fact, I will try to refer to them as such throughout these posts.

Item Binding

Item Binding refers to the connection of a source object property value to a target control's bindable property. These connections are typically two-way, in other words you can change either side of the connection and the databinding system will push the change to the other side. Another cool aspect is that you can bind many controls to the same source property - however you cannot bind a control's property to more than one source. An example of this would be binding the Text property of a Label control to the FirstName string property of a Customer object.

List Binding

List Binding refers to the connection of a source list of objects to a target control that is capable of display and/or navigating between multiple items in the list. The source list can be any .NET object that implements IList, ICollection, IEnumerable or IBindingList, including arrays, collections, generic lists, and DataTables. In more advanced scenarios, an object that implements IListSource can also be used - IListSource simply defines a list of lists. Controls that are able to participate in List Binding include the ubiquitous DataGrid/DataGridView, and many other controls that consume lists of items for various reasons - DropDowns, ListBoxes, and TreeViews to name a few.

Mixing and Matching

Unfortunately, you usually cannot mix (or should not mix) both Item and List Binding in the same source objects. Due to the way the databinding system discovers and uses databinding interfaces (will be discussed in a followup topic), it is not possible to have both Item and List bindings on the same object. An example of this would be binding to Customer entries in a list as well as the Count property of the list itself.

Examples

Item Binding Examples

When creating Item Binding connections, you always begin with the target control, and add to its DataBindings collection a list of property names and their sources. DataBindings.Add() is an overloaded method with two main usage scenarios. Most of the time you will use the form of Add(PropertyName, DataSource, SourcePropertyName). In more rare situations you might want to create a Binding object directly and and use the form Add(Binding)... this can be useful when you want to override default formatting and parsing of values.

  • Binding to a property of a business object

Customer C = new Customer("John");

TextBox TextBox1 = new TextBox();

TextBox1.DataBindings.Add("Text", C, "FirstName");

-or-

Binding B = new Binding("Text", C, "FirstName");

TextBox1.DataBindings.Add(B);

  • Binding to a public field of a business object

You can't do it in this version of the framework!

  • Binding to a field in a DataTable

DataTable CustomerDT = new DataTable("Customers");

CustomerDT.Columns.Add("FirstName", typeof(string));

CustomerDT.Rows.Add(new object[1] {"John"});

TextBox TextBox1 = new TextBox();

TextBox1.DataBindings.Add("Text", CustomerDT, "FirstName");

  • Binding multiple controls to the same source data

CheckBox CheckBox1 = new CheckBox();

TextBox TextBox1 = new TextBox();

TextBox1.DataBindings.Add("Enabled", CheckBox1, "Checked");

List Binding Examples
  • Binding a grid to a DataTable

DataSet CustomerDS = new DataSet();

DataTable CustomerDT = CustomerDS.Tables.Add("Customers");

CustomerDT.Columns.Add("FirstName", typeof(string));

DataGrid CustomerGrid = new DataGrid();

CustomerGrid.DataSource = CustomerDT;

Note that the following can also replace the above DataSource assignment:

CustomerGrid.DataMember = "Customers";

CustomerGrid.DataSource = CustomerDS;

However, it is very important to realize that while on the surface they may appear to give identical results, these datasource assignments are not the same! I will explain this further in part 6 of this series.

  • Binding a ComboBox to an array

string[] StatusList = new string[2] {"Active", "Canceled", "Completed"};

ComboBox CurrentStatus = new ComboBox();

CurrentStatus.DataSource = StatusList;

Note that with a ComboBox, the List Binding is what is used to supply the list of possible values displayed when activating the control.

  • Binding a ListBox to a Business Object Collection

List<Customer> CustomerList = new List<Customer>;

CustomerList.Add(new Customer("John"));

CustomerList.Add(new Customer("Mary"));

ComboBox CustomerSelection = new ComboBox();

CustomerSelection.DisplayMember = "FirstName";

CustomerSelection.ValueMember = "CustomerID";

CustomerSelection.DataSource = CustomerList;

Note that ListBox controls, like ComboBox controls, use a List Binding to supply values for the range pf possible values. They both can also use an Item Binding to connect the actual currently selected value to a data source object.

To Be Continued...

Tuesday, January 09, 2007 12:21:26 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [2] | 

# Monday, January 30, 2006

Adding a Google or MSDN2 search to VS2005

Rik explains how to use a Visual Studio macro to search for highlited text using Google (or alternatively from MSDN2 online) directly from within the IDE... its a pretty cool trick.

Which reminds me - Rik R has been a friend of mine for a few years now (a former coworker from 2001/2002), we were building highly interactive websites years ago using javascript, xml, and XmlHttpRequest. Today, we would call such a system a "Web 2.0 AJAX site". But back then, it was seen as heretical - since postbacks were the status quo. Anyways... I finally talked him into blogging... you can catch the feed at www.r2musings.com as he writes about his journey in the land of .NET

Speaking of new bloggers.... I also convinced Dan Attis to pick up the habit not too long ago... and as you can tell from the activity on his weblog, he is quite the prolific writer!

 

Monday, January 30, 2006 3:52:21 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Sunday, January 15, 2006

Planning pays off

Last May, I posted about how I was signing up for an internet-based file backup service. I saw it as insurance for my personal data, and an investment in my personal sanity. Today was the day I had to "cash in" on that investment.

The server that I have been using to store my personal files at home has been on its last leg for a while now. I went through the trouble of installing a RAID controller months ago and configuring for mirrored drives (faster recovery with mirrored, and besides I didnt have enough drive bays to support raid 5).

The strong winds yesterday caused the power in my home to waver on and off, which was the death blow for my ailing rackmounted UPS. And in it's final moments, as a last act, the UPS must have sent a spike into the server... because at this point, the server refuses to boot, or even to POST test. The redundant drives, while protecting me from a disk failure, are pretty useless without a server to power them up.

Thats when the internet backup came to the rescue. I was able to restore every byte of lost data (last successful backup was only the day before). Our digital photos, old emails, online banking files, corporate documents, source code repositories,... everything was salvaged!

I am very glad that I had the foresight nearly a year ago to plan for a significant data loss, and by taking the right steps managed to mitigate the risk, saving my personal digital valuables which would surely be lost today if I had not. I would definately recommend a data protection/backup service such as Storage Guardian to anyone with personal data that they consider important (which is pretty much everyone that owns a computer, isnt it?).

Sunday, January 15, 2006 9:03:39 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Tuesday, December 06, 2005

Interested in AJAX?

I have been following this blog for a while now - it is quite active and has great timely information on AJAX technologies (not just .NET either - they talk about anything ajax):

 

http://www.ajaxian.com/

.

Tuesday, December 06, 2005 12:46:48 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [2] | 

# Sunday, December 04, 2005

Weird HTTP header issue in Internet Explorer 6

I recently needed to implement an HttpHandler in a project to handle retrieval of rendered PDF reports from a database. It shouldn't have been a big deal, very few lines of code, even less than typical since my Business Logic Layer encapsulates all of the aspects of the data retrieval itself into a single line of code... my handler only had to set the ContentType, add a couple of headers (Content-Length and Content-Disposition), and then BinaryWrite() the data. Easy as pie, or so I thought.

The file streamed fine out of the database - no surprises there. Content-Length had no issue and once I remembered to install Acrobat Reader into my VPC where I was testing this, then the ContentType of "application/pdf" worked as well.

But try as I might, I could not seem to get that darn Content-Disposition to be recognized by Internet Explorer! Every time I tested it, the "Save" dialog would always display a random file name, instead of the one I supplied in the header.

I double-checked, then triple-checked my code. I had two other web developers look at the code, and give it a thumbs-up. I even resorted to tracing the HTTP protocol traffic just to be 100% certain that IE was getting the HTTP header. Sure enough, the header was being set and IE was seeing it. But it wasnt obeying it.

It turns out there seems to be a maximum length to the filename you can supply using the attachment notation of the Content-Disposition header. And my filename was in excess of that limit, causing it to be ignored by IE. While I am not sure what the exact limit is (or if it is some other kind of esoteric bug in IE), and do not have time to experiment and discover that limit, simply reducing the length of my attachment filename has resolved the issue.

By the way, the length of the filename causing a problem was only something like 40 to 50 characters, which really isn't unrealistic. I was embedding the report execution date/time (in a string format that is valid in a file name), in order to prevent saved reports from being overwritten.

Sunday, December 04, 2005 10:55:51 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [2] | 

# Friday, October 21, 2005

VMWare Player

I saw this come across on slashdot this morning...

It seems VMWare has a new free "player" product. It appears to be the same as vmware 5 workstation, except that you cannot create new virtual machines or add hardware to existing ones - you can only use machines created using one of the full-featured versions (Wkstn/GSX/ESX). Aside from that, it's supposed to be fully-functional.

And it claims to support Virtual PC / Virtual Server images as well.

And thats pretty cool, since vmware has always been a better virtualization product than VPC. The only reason most shops use VPC right now is because it comes with MSDN subscriptions.

Friday, October 21, 2005 2:42:21 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Monday, October 17, 2005

C# UG Presentation: Engineering an Application Foundation using Code Generation

This was a presentation I gave earlier this month (October 2005) to the Atlanta C# Developer Group.

Code generation is one of those "cool" techniques in software development where you just don't know what you have been missing until you see it in action... and then once exposed to it, you find yourself wondering "how did I ever get along without this?".

In this presentation, I discussed the reasons and benefits of using code generation tools, and the problems they can solve (or avoid entirely). I focused on the benefits of consistent code, building upon a solid foundation, and simplification of complex application features.

We designed and built the code for a basic application foundation, supporting simple features such as lightweight enforced transactions, entity persistence, and other baseline features. Then a series of linked templates were created that take advantage of that code foundation. Finally, we built an example UI that leverages the generated middleware to dramatically simplify and streamline the actual development process. It was pretty durn cool IMO.

All templates were built using CodeSmith, which is a fantastically useful tool. I feel like I might have been giving a sales pitch for that product by showing off many of the way too cool features of the new version of CodeSmith, but it's one of those things I strongly feel is worth every penny.

The powerpoint slide deck and all relevant source code can be downloaded from the presentation archive of the Atlanta C# Developer's Group website at: http://www.atlantacsharp.org/PresentationArchive.aspx

 

Monday, October 17, 2005 2:55:57 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [3] | 

C# UG Presentation: Custom Exceptions

I actually gave this short presentation to the Atlanta C# Developer Group back in September, but never got around to blogging about it...

The presentation covered why you should be using custom exception classes in your own code, and how to structure your exception class inheritance to gain the most benefit. It showed working examples of the concepts. It also touched on a few related practices such as only catching exceptions that you can deal with, and designing a meaningful exception taxonomy.

The slides and source code can be downloaded from the Atlanta C# Developer Group's presentation archive at: http://www.atlantacsharp.org/PresentationArchive.aspx

 

Monday, October 17, 2005 2:41:25 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Wednesday, August 03, 2005

Designing a Service, Part 2

In my previous post, I talked about how to properly design a service. However, I did not give any real examples of it. All talk and no code. In this post, I hope to show how to actually do it.


Example: Hello, World!

Because you can never wear out an old example, and everyone already knows the Software Requirements Specification (SRS) for "Hello, World!".

Contract-First Design

Starting out, we need to first define the contract of the service that will be known as HelloWorldService. While I could demonstrate the purist's SOA approach by starting out with actual XSD schema, I will instead show what 9 out of 10 developers will be more comfortable with - a .NET interface contract.

HelloWorldService is a simple service, it's functionality is comprised of only one operation, "HelloWorld". HelloWorld accepts a single string input: "PersonName". It also returns a single string value: "GreetingText". With these requirements in mind, I will lay out the following interfaces and supporting class types:

namespace HelloWorld.Interface
{
    public class HelloWorldRequest
    {
        public string PersonName;
    }
    public class HelloWorldResponse
    {
        public string GreetingText;
    }
    public interface IDocHelloWorldService
    {
        // This is the document-centric design method
        HelloWorldResponse HelloWorld(HelloWorldRequest Request);
    }
    public interface IRpcHelloWorldService
    {
        // This is the Rpc-centric design method
        string HelloWorld(string PersonName);
    }
}

All of these definitions are placed in a single assembly, HelloWorld.Interface.dll. This part is important if you decide to provide a Remoting option, as this will function as the shared assembly (the equivalent to COM's type library and WebServices' WSDL).

Define Seperate Endpoints and Implementations

Here is where the rubber hits the road. The guts of the service will live in the Implementation assembly. Actual endpoints will live in their own assemblies, or web projects. I will start out by building the Implementation assembly, named HelloWorld.Implementation.dll, which contains the following code:

namespace HelloWorld.Implementation
{
    using HelloWorld.Interface;
    public class DocHelloWorld: MarshalByRefObject, IDocHelloWorldService
    {
        public HelloWorldResponse HelloWorld(HelloWorldRequest Request)
        {
            HelloWorldResponse Response = new HelloWorldResponse();
            Response.GreetingText = "Hello, " + Request.PersonName + "!";
            return Response;
        }
    }
    public class RpcHelloWorld: MarshalByRefObject, IRpcHelloWorldService
    {
        public string HelloWorld(string PersonName)
        {
            return "Hello, " + PersonName + "!";
        }
    }
}

Notice the difference in the amount of code needed to support each design method. Document-centric can require more code, but the trade-off is for increased control and flexibility.

Now, we can define endpoints.

The simplest "endpoint" is to merely add a direct reference to HelloWorld.Implementation.dll, and use the service classes directly. This will result in an in-process working instance, just like any other .NET object.

The next simplest endpoint is to configure the service as a Remotable object. You might have noticed that I chose to derive my implementation classes from MarshalByRefObject. This decision allows the Remoting system to expose my implementation classes as Remoting objects - objects that run in one process/machine, but are used in another process/machine transparently [almost] to the calling code.

I will not go into details of how to set up Remoting, Ingo Rammer does a MUCH better job than I could ever hope to in his book "Advanced .NET Remoting". In essence, you will need a remoting host (an IIS web application is the easiest), and you will need to add the web.config entries to expose IDocHelloWorld and IRpcHelloWorld as "well-known services". Note that you are exposing the interfaces here, and NOT the implementation objects... an important distinction. Then you simply dump the Interface and Implementation assemblies into the web's bin folder.

On the Remoting client side, you create a similar entry in the application's config file (alternatively, you can make calls in code to Remoting.Configuration to achieve the same effect). The client-side configuration causes .NET to channel requests for the service interface to be forwarded across the network to the target server. In your client application code, you simply use Activator.GetObject(typeof(IDocHelloWorld), "remoting url....") to request the remote instance. The framework takes care of the rest.

Finally, we can define a traditional Web Service endpoint. Since asmx is the simplest way to do this, we will use that technology in this example, although you could alternatively use WSE's non-asmx transport ability. We just create a plain-vanilla Web Service asmx in a web project (named DocHelloWorldWebService), and add a reference to HelloWorld.Interface.dll and HelloWorld.Implementation.dll. We modify the web service code like so:

public class DocHelloWorldWebService : System.Web.Services.WebService, IDocHelloWorld
{
    using HelloWorld.Interface;
    using HelloWorld.Implementation;
    /* .... IDE generated stuff .... */
    [WebMethod]
    public HelloWorldResponse HelloWorld(HelloWorldRequest request)
    {
        DocHelloWorld = new DocHelloWorld();
        return DocHelloWorld(request);
    }
}

We can also provide an RPC-centric version by adding a RpcHelloWorldWebService like this:

public class RpcHelloWorldWebService : System.Web.Services.WebService, IRpcHelloWorld
{
    using HelloWorld.Interface;
    using HelloWorld.Implementation;
    /* .... IDE generated stuff .... */
    [WebMethod]
    public string HelloWorld(string personName)
    {
        RpcHelloWorld = new RpcHelloWorld();
        return RpcHelloWorld(personName);
    }
}

Obviously, you would need to also do a few other things to complete the web service, such as giving a non-default namespace, etc.

Wrap-up

So, there you have it. An example of how to properly design a service in .NET. While not terribly difficult, it certainly is a totally new way of thinking about system design for many people. Hopefully this is of some benefit to you, who knows it might save you from a costly code refactoring when you suddenly have to support a new transport or service endpoint type.

In conclusion, I would also like to point out a few excellent resource books that are relevant to the subject:

"Advanced .NET Remoting", Ingo Rammer
"Expert Service-Oriented Architecture in C#", Jeffrey Hasan

Wednesday, August 03, 2005 6:39:11 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

Designing a Service, Part 1


Last week I hooked up with a few other smart guys after the Atlanta .NET User Group meeting. We were at the regular post-nerd-meeting hangout - Buffalo Wild Wings on Mansell Road.

We talked about a number of interesting topics, including AJAX/Atlas, Vista, and Indigo. At one point, someone questioned the validity of Remoting in light of WebServices, and wondered why anyone was using it now that is is supposedly on the way out. And my perspective was "why choose at all?". It is perfectly feasible and dare I say it "good practice" to design your system to support both. In fact, if you do things right, you will even be prepared for a smoother migration to Indigo or WSE.

So what is the secret trick to designing a service like this? Simple: it's called "contract-first design". Back in the days of COM, you might have known it as "interface-first".


Contract-First Service Design

The concept is surprisingly simple. A "service", whether it be a Web Service, system Service, or logical in-process service, can be defined as a unit of system functionality that operates independently of other units, and provides a well-known and concise interface. What this means is that no matter how complex a peice of functionality may be, it can be described in terms of some abstract interface. In Service nomenclature, this interface is called the "contract". The "contract" states the individual methods (operations) that a given service implements.

When we talk about Web Services, the "contract" is expressed as a WSDL document. When we talk about Remoting, this contract is expressed as compiled IL in a shared assembly. In COM, the contract was expressed in the Type Library. Indigo's name for this idea is by far the most explicit and clear, the "ServiceContract".

When you define a contract, you are declaring the exact functionality that a service MUST provide, and which a client MAY consume. The Service is not allowed (theoretically) to provide funtionality that exceeds or falls short of that contract, and the Client is not able to request any functionality that falls outside the scope of the contract. Furthermore, the contract also defines the specifics of HOW the functionality is to be addressed... for example, an operation requires a specific set of inputs, and will not respond (or should not) to inputs which are invalid or incomplete.

Therefore, a Service Contract ensures a consistent and predictable interaction between client and service.

So HOW do we go about designing a .NET solution in such a manner?

Plan ahead.

It is far easier to produce an adaptable architecture if you plan ahead for the adaptability. This means thinking about your interface before you think about the implementation.

At a minimum, start with defining your interface(s). You will need to define a seperate interface for each logical service. If you are a SOA purist, then this is where you bust out with the XSD editor and define your messages, operations, and portTypes. If you are not an SOA purist, then this is where you start laying out .NET interface definitions (yes, the language constructs that are defined with the "interface" keyword). Even if you are going the pure-SOA route, you will want to run the xsd.exe compiler against your schema to create some .NET stubs.... then change those from "class" to "interface" definitions. Either way, on the .NET side you want a set of interface definitions for the services, and class definitions for the complex structures.

And here you must make a choice. Do you want to (A) design a completely document-centric service, or (B) an rpc-centric service? If you want document-centric services, then you will want to create explicit types for every operation/method, one for the input and one for the output... even if the content is not complex. If you want RPC-centric services, then you can save a small amount of effort by only defining explicit types for cases where you wish to return or accept complex structures.

  Design Method A
    OperationAResponse = {complex type or simple type...}
    OperationARequest = {complex type or simple type...}
    OperationA = function(OperationARequest), returns OperationAResponse
  Design Method B
    OperationB = function(complex or simple type(s)), returns simple type or void

One word of warning: stay away from "ref" or "out" parameters if you choose to follow Design Method A.

One thing you might notice about Design Method A is that all operations will have a similar pattern, regardless of inputs and outputs. This is not coincidental. The Request and Response structures are what we call "documents" in a document-centric SOA (also called "message-centric"). There is great flexibility and power in a message-centric design.

Define Seperate Endpoints and Implementations

This is what fouls up most attempts at designing a good service. Having the contract pre-defined is not enough - you must also differentiate the service from the consumer. In the real world, your service will almost always be centralized, but the consumer(s) will be dispersed. This means that one (1) service may have multiple (N) endpoints. An endpoint is merely an identifiable address where the implemented service functionality can be found.

The problem is that most people do not distinguish between the endpoint and the implementation. They simple fire up a new asmx file and start dumping code into it. This is a BAD PRACTICE. The asmx is simply and endpoint, and should not be doing any work except passing the calls over to a real implementation. In other words, the asmx should be nothing more than a Facade.

A better approach is to implement the service itself in an external class... better yet, in an external assembly. This implementation class should implement the defined interface, just as you would implement any other .NET interface in a class. Then, you also implement that very same .NET interface in your asmx, however in the asmx you simply forward the calls to an instance of the external implementation class. Your Web Service methods become extremely simple shells that are marked up with the [WebMethod()] attribute and delegate the work to the external class. Very clean and tidy.

Once you have implemented your service like this, many more options become available. You get the flexibility to create new endpoints, possibly with differing authorization abilities (external facing ones might enforce WSE security, while internal ones might not). You also get the ability to very easily expose the service via other transports... such as Remoting. Either create a new endpoint object that derives from MarshalByRefObject, or make the service implementation itself derive from MBRO. Then you can add a entry to an application config file, and your service is now exposed via Remoting, with an identical interface to the web service! And finally, you enable the consumer of your service to simply reference it directly as an assembly, if that is desired for performance or security.

Wrap-up

That wraps it up for this (somewhat basic) discussion of how to design good services. My next post will follow up with a concrete example of this process.

In conclusion, I would also like to point out a few excellent resource books that are relevant to the subject:

"Advanced .NET Remoting", Ingo Rammer
"Expert Service-Oriented Architecture in C#", Jeffrey Hasan

 

Wednesday, August 03, 2005 6:26:59 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Friday, July 29, 2005

Cool Blackberry Download

Josh pointed out this cool little Blackberry utility to me today: Berry 411. It's always the simple, elegant tools that are the best.

 

Well yeah, OK. So I admit I am finally getting the hang of the Blackberry interface. I always said I would never get one because they were so awkward. But I guess walking was also pretty awkward too at first.

 

Friday, July 29, 2005 10:40:08 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Friday, May 06, 2005

Protecting Assets

Picture this:

Scenario 1 - You are a consultant. You write software for various clients, often remotely. Your clients pay you a lot of money, and you keep them very happy with the exceptional work you perform for them. Because you are a meticulous developer, and understand the value of versioning, you have been using Visual Sourcesafe for 6 years to store every code revision you have ever made for your clients. You also keep every one of your important emails for the last 10 years. Your Outlook .pst file contains every significant conversation you have ever had with your clients - and it also contains personal knowledge such as your complete contacts list and website registration confirmations for dozens of sites, including your 401K portfolio on Ameritrade.

Scenario 2 - You know nothing about code. You drive trucks for a living. However, you DO like to have gadgets and gizmos, and you really love your new Media Center Edition machine. In fact - it's so great that you have digitized ALL of your CD collection and even bought a portable media center for while out on the road. At this point you have over 5 gigs of music digitized, as well as the last season of stargate and family guy to watch as you have time. You never use your CDs anymore, and honestly have no clue where many of them are - since many have been "loaned" or misplaced.

Both of these fictional situations is missing something extremely important. Even though your digitized data is extremely valuable to you, and difficult if not impossible to recreate, you have no backup and recovery strategy!

How to correct this oversight?

There are several options. You could always purchase a simple external drive or tape system and perform regular backups. This is fairly easy, but is really only slightly better than having no backup at all... your backups are still in the same location as the protected systems. A house fire (not as uncommon as you might think) would wipe you out. If you are a consultant supporting a client with deep pockets (who would be forced to sue for negligence if you lost their source code), you cannot afford such a risk.

A better solution is off-site backup services. With off-site services, your valuable data is stored in a remote facility (usually encrypted). For a monthly fee, you can configure your systems to regulary upload changes. Fees tend to be quite small - especially for the individual consumer who often only needs a few Gig of storage. Setup and maintenance also tend to be fairly simple - about on par with traditional tape or disk based backup systems.

I have recently been evaluating two vendors in this space: NetCentric Solutions and Storage Guardian. NetCentric's offering is very easy to install and configure, but has fewer features and a slightly higher price tag. Storage Guardian's offering on the other hand is more difficult to install and configure (due to being more complex) but is much more flexible in deployment.

NetCentric supports File-system backups only, and is quite simple to get running. They also have a nice feature that allows you to access your backup sets from any internet location, essentially using your backup set as a file-sharing mechanism. The NetCentric product costs about $40 a month for up to 5G of compressed data.

Storage Guardian is a more "network aware" product. Rather than a simple single-install program like NetCentric, this product follows a client/server design, with a server process that performs the actual backups (oddly called "ds-client") and a client program that performs set management and inspection (called "ds-user"). This product has the ability to perform direct backups of SQL Server and Exchange databases, as well as NT/2K/XP/2K3 System State, permissions and registries. The installation is slightly complicated, and it is recommended that the installation guide actually be read carefully prior to attempted install. The Storage Guardian product costs about $30 a month for up to 10G of compressed data.

I ended up going with the Storage Guardian product for my personal needs. I really liked the ability to directly back up my Exchange and SQL databases. If you don't need those two things however, the NetCentric solution may be a better fit - since it's considerably easier to configure. My current volume needs are about 5G, so the Storage Guardian solution also allows me a little more room to grow.

In the end, $30 a month for peace of mind is a great bargain.

 

Friday, May 06, 2005 1:37:38 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [5] | 

# Monday, April 04, 2005

Async Programming Presentation Slides and Source Code

First of all, thank you to everyone who came out tonight to the C# User Group meeting in Atlanta. Paul and I are very pleased with how this group continues to grow.

I am posting the slides and source code from my presentation ".NET Async Programming Demystified" here for download.

This is essentially the same presentation I intent to deliver to the .NET Code Camp next month, although I will need to strip out about a quarter of the slides to keep myself within the alloted time budgets there. I chose to present it to the C# group first, since a large number of our attendees were not able to register for the Code Camp (due to it selling out in only 5 days).

I did make two corrections to my material before posting the slides:

  1. I incorrectly stated that each AppDomain has it's own thread pool. This should have been "each Process has it's own thread pool".
  2. I forgot to include the third type of Timer object - the System.Timers.Timer. Chalk that one up to lack of sleep, I simply forgot to include it in my outline.

Both of these corrections are reflected in the downloadable files.

 

Monday, April 04, 2005 11:29:17 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [1] | 

# Thursday, March 31, 2005

Getting a better handle on CSS

I have used CSS for years now, but I never knew about this. Sure, it's sort of implied by the documentation, but I have never seen anyone come out and say "hey, this is how you organize it". So here is what I learned this weekend.

1. You can define specific hierarchies of nested tags to apply a style to. For example "DIV.outerdiv DIV.innerdiv {color:red;}" will set the font color of a div with a class attribute of "innerdiv" to red, but only if it is contained within another div with a class of "outerdiv". Thats super handy for complex layouts.

2. You can define a style more than once. The styles are then merged at runtime in the order which they are encountered (last writer of a style property wins). This means that you can seperate layout CSS from colors CSS and from text, or whatever. You can have a "colors.css" file that only contains color definitions, making it MUCH easier to update later. Super SUPER handy!

3. You can assign styles based on element IDs, not just Classes. Everyone using CSS knows you can use a style selector of ".somestyle" to apply a style to all elements with a class= "somestyle"... but most don't know that you can use a selector of "#element_id" to apply the style only to the element with id= "element_id". This is SUPERB for layout style properties. Generally, the colors/text/etc styles are used on many elements in a page, which is easily done using class="somestyle". But id values are supposed to be unique in a page, and they are all at different positions, with different layout rules (even though they share the same color/font/etc). Assigning layout style properties based on element id and other properties based on class allows you to have your cake and eat it too!

These are very simple concepts to pick up, but they have done wonders for my web design/layout abilities.

And I guess I should also give the disclaimer that I didn't know these things until I customized my dasBlog theme, and saw how it's CSS was designed. Very impressive work there.

 

Thursday, March 31, 2005 12:32:25 AM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [0] | 

# Sunday, March 27, 2005

Thanks Michael!

Michael Earls is the man.

This afternoon I asked him a simple question: How do I get Master Pages type functionality without upgrading to Fx2.0 beta?

I am building a small administration application for a client, and I am tired of using "copy & paste templates" or "UserControl chunks" to build a reusable/consistent site design. I know he has done this type of thing a few times in the past, which is why I asked him.

He gave me some very concise and (most importantly) correct insights into the ASP.NET rendering engine, and where to place hooks to do what I wanted. He gave me just enough information to get it working, without giving me too much. The essentials: override the AddParsedSubobjects() method in a server control to re-parent the child page's contents to a placeholder.

While there are a few tricks and snags to overcome, that is essentially exactly what I had to do. I created a new server control, changed it to inherit from Web.UI.Page, instantiated LiteralControls for my static content, as well as a number of server controls that I needed in my base class (for example, the Coalesys PanelBar for navigation) in the new class's constructor. I did some footwork in the AddParsedSubobjects() method to relocate the child page's Form contents into a new HtmlForm that I maintain within my new class. I also had to copy the properties of the child form (enctype, name, method, etc)... so that should the child page developer decide to change any of it, their changes would carry over.

I did do a few things my own way. One of the ideas Michael gave me was to use regular expressions to construct the master page's LiteralControls (he used string constants for each Literal, which I am sure was probably a bit tedious when a change needed to be made). So instead of that approach, I added some well-known comment tags to my master page's source to demarcate the literal sections. I then used my NVEdit tool to store the entire master page as a single template resource. A simple regular expression was used to extract out all the literal sections (this was not hard at all since I used well-known comments to mark the sections). As a result, I can modify the master page layout without too much trouble (yes, it still requires a recompile). Perhaps a future exercise could be to make it fully dynamic, with the ability to use multiple master layouts on the fly. Hmm.

Anyways, the other features I added was a simple way to link external CSS (a new AddStylesheetReference(string url) method), as well as the obligatory Title property that alters the header tag appropriately. A side bonus is that the AddParsedSubobjects code is smart enough to read in existing Title and linked stylesheets from the child pages, and automatically set them with no developer code required. I will likely also extend this mechanism to support themes via CSS at a later time too. That would work nicely along with dynamic master page sourcing...

So I am now very happy. I effectively have master pages development in VS 2003. I don't have the ability to see the master while editing a child page in the designer, but I don't really care about that little bit. I can still link my child page to the site's CSS and get the right "look and feel". This is great.

Thanks again Michael!

 

Sunday, March 27, 2005 11:56:16 PM (Eastern Daylight Time, UTC-04:00) #  Disclaimer | Comments [1] | 

# Sunday, March 06, 2005

SOA Misunderstanding and Misuse

Part of my new job is making sure our applications meet scalability, reliability, and performance requirements once we deploy to "the wild". Damn near anything will work in a development or even QA environment (assuming you are like 95% of all shops and don't bother doing load analysis until a problem comes up). But just because something works on a development machine is no guarantee it will continue to work once it is placed under pressure. "But it works on my machine" has ended many a developer's career and/or project's budget.

Anyways, I am rambling, so on to the point of this post...

I am truly appalled at this "webservice.htc" IE behavior component. True enough, I can visualize the thought process at Microsoft that eventually arrived at creating this thing. But I do not agree with it at all.

First, what it does:

If you are unfamiliar with webservice.htc, essentially it is a chunk of javascript code that wraps calls to web services (via the XmlHttp IE COM component). It knows all about SOAP and WSDL. In fact, in order to use a SOAP web service, you simply point it to a WSDL document (it expects something along the lines of "BlahBlah.asmx?WSDL", in other words - always late-bound service discovery). It then allows you to interact with the web service using the SOAP operations ("methods"), not too unlike the "developer experience" of using a web service proxy class in managed .NET code.

It is very easy to use in fact.

All you need to do is attach the behavior to any DHTML tag... for example a DIV like this:

<div id="MyService" style="display:none;behavior:url(webservice.htc);" />

Then in your page somewhere (anywhere, but most often done in the onLoad event of the body tag), you load the WSDL:

<body onload="MyService.useService('BlahBlah.asmx?WSDL', 'svcBlahBlah');">....</body>

Then calling the operations ("methods" for OO-heads) is as simple as this code which can be used anywheres in your page javascript:

<script language=javascript>
var param = "some parameter...";
MyService.svcBlahBlah.callService(DisplayResult, "SomeOperationOnBlahBlah", param);
function DisplayResult(result)
{
   alert("Result: " + result.value);
}
</script>

Now that I have explained how it works in a nutshell, let me explain why I think its a really bad idea...

Second, my beef with it:

Each time useService() is called, the WSDL is downloaded to the browser. Chances are, a complete WSDL is massive overkill for the data a web application is wanting to pull via OOB (out of band). In the web service I am looking at right now, there are four operations supported. Each returns a simple string, and accepts one to three string parameters. However, the WSDL is over 8k! Since each page transition in the browser tosses all previously loaded script, this "wsdl setup" must happen at least once every time a page is loaded that needs to make web service calls.

On top of the WSDL cost, there is the cost of the HTC code itself. The webservice.htc file is 50K by itself! And since it is just linked javascript, it too must be reloaded every time a page transition or postback occurs.

So far, those costs are fairly hidden from view of the web developer. Not many will think adding the behavior style and calling a single method just once on a page will incur from 50k upwards to 100k or more bandwidth (the size of that WSDL can grow quite quickly as your web service becomes more complex). For reference, a page that is 100k *in total size* is considered effectively unusable in low-bandwidth settings, such as 56k dial-up or dedicated circuits. Not to mention if you ever hope to handle more than say three users or so....

On top of those hidden costs is the actual cost of the SOAP payload itself. Most developers are at least aware of this aspect, since there has been a great deal of discussion/ranting over the years about SOAP message bloat. In my test case here, this actually turned out to be the lesser cost when compared to the setup overhead. Each SOAP conversation is costing me roughly 2k for the request and 1k for the response, which honestly is not too bad. It's worth mentioning though that these tiny costs are exacerbated by the latency costs of the connections themselves, and if your design is inherently "chatty", these costs will add up very quickly.

Real Numbers:

So you might be rolling your eyes, thinking "so what. this guy is griping about a few extra bytes, he has too much spare time to dig this crap up". I would be skeptical as well. So I did some real analysis of this application using the simple ACT tool that comes with Visual Studio. This examined application uses Infragistics controls heavily, and I was expecting them them to be the worst offender, but measurements proved otherwise:

In ten minutes, my test ran 431 iterations of a simple "browsing" script, which logs in, does a few searches, and pokes around most of the application pages without modifying any data. In each test iteration, a total of 105 unique URLs were requested, though obviously many were hit multiple times. Between the pages themselves, images, and linked css and javascript files, a total of 82,224 requests were issued. 5,000 of those ended as "socket error", which indicates that I was hitting right up against the limits of my testing machine (Win XP) which was starting to have trouble handling the requests and thus dropped a few. 4,294 requests resulted in a 403 http error, which indicates also that the server was becoming saturated.

While only 441 calls were made to the web service in actual method calls (essentially once per iteration), 5,485 calls were made to retrieve the WSDL! In fact, this works out to roughly 12 times per iteration. No other resource was requested so often from the server... the next highest were about three aspx pages each requested roughly 3,500 times (they are reused in IFrames a lot, so this is somewhat understandable). The webservice.htc was also requested a total of 859 times, or roughly twice per iteration. So essentially, 8% of the total # requests, and 10% of the bandwidth of this application (even factoring in image files) was being expended simply to read a few strings of data without forcing a server postback.

In comparison, the notoriously bandwidth-heavy Infragistics grid, datetimepicker, and outlookbar controls had a combined impact significantly lower than the web service calls.

So, how to fix this?

The best approach is to just not do this. Chances are, an application that relies on OOB tricks probably should have been a Windows Forms application to begin with. But assuming you are 99% through the development (nobody ever thinks about load tests until then...), I assume a rewrite is out of the question. So, the question is "how to minimize the problem?".

There are three approaches I can think of to remedy this situation without resorting to drastic re-architecture.

  1. Roll-Your-Own. It is rather painless in my opinion to simply define your own (or go with "standard" XML-RPC) out of band mechanism. XmlHttp is quite easy to use, and writing the XML DOM code to construct requests and extract results/errors is not at all difficult. On the server end, its also not very hard to produce XML to match your needs manually, and simply write it out to the response. Using an HttpHandler would make it pretty lightweight too. This approach eliminates the huge webservice.htc downloads, and also eliminates the WSDL calls, since well... there wouldn't be any WSDL. Of course, the result is 100% proprietary, which may be considered a drawback.
  2. Use SOAP without WSDL. An extension to approach #1, you would interact with XmlHttp directly using your own javascript. However, you continue to use an ASMX web service on the server. As long as the parameters are simple datatypes, you can use the HTTP GET method and pass the parameters in the query string. Then, its just a matter of dealing with the (relatively clean) SOAP response. No WSDL to contend with in this approach, and no webservice.htc. You also get to use the strong tools support server-side for the ASMX web service. The downside is that you have to keep to simple parameters, and you have to code the response handling in javascript.
  3. Relocate the behavior code to a parent frame. By making an outer-level frameset with a single contained frame panel, you can link the behavior and associated WSDL calls in that one place, and call it from within nested frames. This has the benefits of retaining the SOAP conformance (allowing you to continue using ASMX), while reducing the WSDL and HTC requests to once per browser session. The downside is that now you have to pay more attention while coding. Calls to the webservice proxy have to be made by traversing the frameset (iterating through parent frames).

These are all decent ways to place a band-aid on the problem. But the problem itself is, to me, indicative of a much larger problem that I see widespread in the industry today...

Misuse of SOA tools and components

Web Services are a component of SOA (Service Oriented Architecture). WSDL and associated discovery/description technologies are tools of SOA. More and more, I keep seeing people attempt to use, or more accurately "misuse", these things. Web Services are implemented as if there was zero cost in performance/latency/reliability. WSDL is fetched at run-time to configure service proxies on the fly. This is NOT how these technologies were intended to be used, and they perform and scale miserably when used like that.

The purpose of WSDL is to define service contracts. This is for the purpose of being able to understand and properly interface with a service. You interpret the WSDL ONCE and ONLY ONCE (preferably at design time!). Your code will obviously depend on the semantics of the contract declared in the WSDL, and so it makes zero sense to interrogate for WSDL at run-time! In fact, once you go to production, its a pretty good idea to completely disable the WSDL handler in ASMX web services, if nothing else than to lower your DoS vulnerability profile (after all, generating WSDL in asmx isn't free).

And the invocation of Web Service operations themselves is not intended for such scenarios as OOB web browser activity. Web Services are a way of enabling applications to communicate via messages, which is useful in Enterprise Architecture (ESB's) and Integration (EAI), but of little value in a contained web browser application.

Microsoft really should be more responsible and put a warning on this thing. The documentation on MSDN reeks like a marketing brochure. It discusses all the wonderful benefits, but makes no mention whatsoever about the potential problems. Nor does it even mention the fact that accessing web services from a browser is a dubious idea from the outset.

 

Sunday, March 06, 2005 4:23:55 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [8] | 

# Wednesday, February 16, 2005

Oh dear....

This will work in SQL 2005...

 

Create trigger trgHmm1 on DATABASE for CREATE_TABLE, DROP_TABLE, ALTER_TABLE

AS

   ROLLBACK;

GO

Create trigger trgHmm2 on DATABASE for CREATE_TRIGGER, DROP_TRIGGER

AS

   ROLLBACK;

GO

not sure about this one though:

Create trigger trgHmm3 on DATABASE for CREATE_TRIGGER, DROP_TRIGGER, DISABLE_TRIGGER

AS

   ROLLBACK;

GO

 

Kinda makes a strong case for really keeping tight control over who has db_ddladmin in a database doesn't it?


 

Wednesday, February 16, 2005 6:34:07 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [1] | 

# Thursday, February 10, 2005

NVEdit Downloads available now

The Powerpoint Presentation and Source Code I used in monday's Visual Studio Integration topic can now be downloaded from this link.

If you just want to install the NVEdit Add-In (with Core Library and Help File), you can download it from this link.

If you have no idea what I am talking about:

NVEdit is a simple Visual Studio 2003 Add-In that I created as a working example for my presentation to the Atlanta C# Users Group. This Add-In allows you to store large "non-volatile" textual data (SQL queries, XML/XSD documents, etc) as an embedded resource in your assemblies. The NVEditor keeps seperate name/value collections (Dictionaries) for SQL, XML, XSL, XSD, and HTML. The values are accessible in a very easy manner at runtime using static members, not too unlike the ConfigurationSettings class of the .NET Framework. Unlike the ConfigurationSettings class however, the NVData values are not stored in the .config file (which is succeptible to tampering).

The whole idea is that this gives you a very viable alternative to cramming stuff that doesn't belong into .config files or into hard-coded string constants.

Here you can see a screen capture of the simple NVEditor UI:

NVEditUI.jpg

I apologize for the delay in getting this uploaded. I tried (again) to get the RichTextBox to perform colorizing the way I wanted it, and it just wasn't working out (jeez that control really sucks). All of that stuff is ripped out now, and I cleaned up a few other bits since my presentation.

And I also compiled a help file that gets installed as well now (Hooray for NDoc!). Even though it really didn't need a help file. I mean, come on, this is a pretty darn simple tool!

 

Thursday, February 10, 2005 2:13:11 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [4] | 

# Thursday, February 03, 2005

Upcoming Atlanta C# User Group presentation

This coming monday, I expect to be doing a presentation to the local C# group. The last time I presented to the group, it was a hastily prepared practical examination of WSE2 programming (without ASMX), schema-oriented messaging, service agents, and other basics of using WSE2 in a SOA environment.

The presentation this month is completely different.

This time, I will define a common issue/concern with application design: the handling of non-volatile textual data. Stuff like SQL queries, HTML fragments, template XML documents, and so forth that often pepper the codebase of our projects, making maintenance difficult.

I will present a solution to this issue in two parts: A simple core library to provide an extremely friendly "developer experience", and also a Visual Studio Add-In that works hand-in-hand with the core library to make the solution very simple to use.

We will talk about the issues I encountered while building the core library, the process of building and deploying a VS Add-In, and also the issues that may arise during construction of an Add-In.

At the conclusion, we will end up with a functional and useful tool that can be employed in our code projects, along with a better understanding of how sometimes extending the Visual Studio environment can provide a good return on investment.

So, if this subject interests you at all (I find it extremely interesting - but I am truly a geek), or if you simply want to get your hands on this useful project tool, then make sure to attend this months Atlanta C# User Group meeting!

 

Thursday, February 03, 2005 5:10:37 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [0] | 

# Monday, January 24, 2005

"library not registered" when creating new c# project item or project

What a stupid and obscure bug in VS.NET 2003. Thankfully someone figured it out and posted the fix on their blog (which didn't take all that long to find in Google). Making a note of it here in case it happens again. Who would have ever thought a DLL from the Visual C++ directory tree in Visual Studio would get unregistered simply by running a VS add-in deployment package install/uninstall a few times... and then would keep C# project wizards from working??

At least the fix is very simple and quick:

regsvr32 "C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\vcpackages\csproj.dll"

Yes, thats cSproj.dll in the VC7 folder.

 

Monday, January 24, 2005 1:16:19 AM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [3] | 

# Tuesday, January 11, 2005

More garbage collector woes

I couple weeks ago, I recounted some trouble I was experiencing with the .NET garbage collector. My efforts to dispose and clean up objects, and eliminate unnecessary allocations (essentially, to be miserly with resources) brought a great deal of stability, performance, and peace of mind.

However, while much more rare, I still continued to experience random crashes in the application. This time, however, I was able to get Windbg to trap the TerminateProcess, allowing me to perform post-mortem. Here is what I discovered:

In one screen of my application, there is a PictureBox. This screen is displayed while the application is performing lengthy operations, the PictureBox contained an animated GIF. The purpose of this was to keep the user preoccupied while waiting (just like the waving flag at the top right of your browser while pulling down a page). Sounds pretty innocuous.

Well, as it turns out... behind the scenes, either the framework or Windows itself (I did not dig into the PictureBox MSIL to check) actually spins up a thread to pump the frame changes of the animation. Yes, this is a recipe for trouble. A rogue thread running in the system, whose obvious task is to update the GUI (it is pumping the animation). Hard Application crashes are a well-known side-effect of a background thread that does not synchronize all shared (UI) resource access with the owning thread.

So what was happening (confirmed by the smattering of PictureBox and lots of native WinAPI calls over the thread that triggered the crash) is that the animation pump thread was not properly coordinating with the UI thread that actually owned the PictureBox. Or perhaps it was behaving well under normal circumstances, but it was not observing the Garbage Collector's request to pause while the GC does it's thing. You see... the crashes all also happened to occur while the GC was performing a heap walk. While the GC thread is active, all other threads are *supposed* to be paused. But the animation thread was not. And thereby the waking of that animation thread to pump another animation frame, if it happened to wake while the GC was still walking the heap, would cause a very nasty crash.

Simply replacing the graphical content of the PictureBox with a static image (such as a JPG) eliminated the problem by virtue of the offending thread never being created in the first place (it is only created if you have an animated image loaded).

So my primary lesson from this is to simply not use an animated GIF in a PictureBox control. My secondary lesson is that it is still a very wise thing to explicitly clean up after yourself using Dispose() and Clear() on objects that support those semantics, and setting references to null for all non-value types as soon as they are no longer needed. Work *with* the garbage collector, not against it!

 

Tuesday, January 11, 2005 5:30:52 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [3] | 

# Saturday, January 01, 2005

Tips and Tricks: String.Intern()

In my recent pilgrimage through the .NET memory model, the garbage collection process, and the fine art of improving performance through runtime profiling and heap inspection, I came across this little piece of knowledge that may one day (maybe today?) benefit you as well. This is a short discussion of the subtle and rarely mentioned static function:

String.Intern()

From the MSDN documentation, this method is explained as such:

“The Intern method uses the intern pool to search for a string equal to the value of str. If such a string exists, its reference in the intern pool is returned. If the string does not exist, a reference to str is added to the intern pool, then that reference is returned.”

Sounds innocuous, but this little core feature may end up having great impact on your applications. Essentially, when you use String.Intern(), instead of making a copy of the string data on the managed heap, you instead are returned a reference to a shared copy of that same literal string. When you embed literal string values (string myString = “some value”) in your program code, the compiler automatically does this for you. Embedded literal strings will be “interned” for you without any additional effort.

So you might be wondering, “OK so now why do I care about this, if the compiler already does this for me?”. The answer is that the compiler only handles hard-coded string values. Consider this snippet of code:

public string[] BurnBabyBurn(string Input)
{
    string[] MyHugeArrayOfJunk = new string[2000];
    for (int Index = 0; Index < 2000; Index++)
    {
        MyHugeArrayOfJunk[Index] = Input;
    }
   return MyHugeArrayOfJunk;
}
[...]
string[] test = BurnBabyBurn("0123456789");

As you have probably guessed, this will make 2000 copies of the same literal string “0123456789“. Not very friendly to your memory footprint. So let us alter the previous code sample to make use of String.Intern():

public string[] ByNiceToMe(string Input)
{
    string[] MySmallerArrayOfJunk = new string[2000];
    for (int Index = 0; Index < 2000; Index++)
    {
        MySmallerArrayOfJunk[Index] = String.Intern(Input);
    }
    return MySmallerArrayOfJunk;
}
[...]
string[] test = BeNiceToMe("0123456789");

The difference is significant. This second version of the same logic will now make 2000 references to the same string value, rather than 2000 copies of it. This is a huge difference in terms of memory allocation on the managed heap (don't believe me? Try both routines and use CLR Profiler to see how much String data is sitting on your heap!).

Now sure, this example is simple and contrived (you really wouldnt ever build a 2000 element string array with the same values would you?). However, it illustrates a very useful way to reduce your memory churn in an application (and therefore reduced footprint, GC time, etc). Supposing you had a DataSet filled with values. Suppose there were 10 string columns, and 500 rows. Assume much of those 5000 string values were duplicates (result of enumeration domains, looked up values, etc)... I wonder what the memory savings (and GC savings when you Dispose() it!) would be? A hint: big enough to pay attention to.

Saturday, January 01, 2005 9:10:46 PM (Eastern Standard Time, UTC-05:00) #  Disclaimer | Comments [2] | 
View Keith Rome's profile on LinkedIn

On this page....

A Simple Sound Effects Engine for Silverlight 2
Awesome tool for Media Center - Lifextender
Performing Seek operations in Silverlight on a MediaElement
Silverlight Bug: Using a packaged image source for multiple Image elements
10 interview tips that will help you land a better job
Quick Tip - Input Controls in Silverlight
Longest Posts EVAR
Adventures in Databinding ~ Part 2 ~ Simple versus Complex Binding
Adding a Google or MSDN2 search to VS2005
Planning pays off
Interested in AJAX?
Weird HTTP header issue in Internet Explorer 6
VMWare Player
C# UG Presentation: Engineering an Application Foundation using Code Generation
C# UG Presentation: Custom Exceptions
Designing a Service, Part 2
Designing a Service, Part 1
Cool Blackberry Download
Protecting Assets
Async Programming Presentation Slides and Source Code
Getting a better handle on CSS
Thanks Michael!
SOA Misunderstanding and Misuse
Oh dear....
NVEdit Downloads available now
Upcoming Atlanta C# User Group presentation
"library not registered" when creating new c# project item or project
More garbage collector woes
Tips and Tricks: String.Intern()

Archives

Navigation

Categories

About

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

Sign In

Certification Logo Certification Logo Certification Logo Certification Logo Certification Logo

Powered by: newtelligence dasBlog 2.3.9074.18820