# Thursday, August 16, 2007

I got caught up yesterday with a problem I should have recognized right away, but for whatever reason I didn't put the pieces together until it was pointed out by one of my co-workers.

I was writing a BizTalk Server 2006 orchestration that, amongst other things, sends a request to a remote server.  The remote server has an application installed that accesses these requests and initiates a few local (and unimportant) processes.  Information is passed to this application via the URL which is unique with each request and constructed by the orchestration.

Rather than use a dynamic one-way send port with the HTTP adapter, I decided to create a .NET helper project (a C# class library) and create a static method that allows me to pass in the URL and wait for a response.  The contents of the response itself is unimportant; all I require is a successful response.

I created the following class in my helper project (simplified for clarity):

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
namespace Messaging.Helper
{
    [Serializable]
    public class Statics
    {
        public void CallUrl(string url)
        {
            WebRequest request = WebRequest.Create(url);
            request.Method = "GET";
            request.GetResponse();
        }
    }
}

As you can see, the static method uses the System.Net.WebRequest class to issue a request to the specified URL.  To test this method, I created a command-line application that iterated 20 or so times and issued requests against the remote server.  The code was similar to the following (simplified for clarity):

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using Messaging.Helper;

namespace Messaging.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 20; i++)
            {
                Statics.CallUrl("http://www.someurl.com/");
            }
        }
    }
}

I immediately noticed that this code failed after it issued two concurrent requests.  No matter how many times I ran it, it always was successful the first two times and then timed-out and threw an error message similar to the following:

System.Net.WebException was unhandled
 Message="The operation has timed out"
 Source="System"
 StackTrace:
  at System.Net.HttpWebRequest.GetResponse()
  at ConsoleApp.Program.Helper.CallUrl() in D:\Test\ConsoleApp\Program.cs:line 41
  at ConsoleApp.Program.Main(String[] args) in D:\Test\ConsoleApp\Program.cs:line 20
  at System.AppDomain.nExecuteAssembly(Assembly assembly, String[] args)
  at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
  at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
  at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
  at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
  at System.Threading.ThreadHelper.ThreadStart()

Yes, I should have immediately picked up on the fact that it succeeded the first two times but then consistently failed on each additional request.  Instead, I tried all kinds of different scenarios (non-static methods, multi-threaded calls, etc.).  Fortunately, a co-worker stopped by and reminded me that, by design, there is a limit on the number of connections you can have to any given server.  By default, this limitation is set to two!

As soon as he said that I remembered the limitation (I guess I still have a few neurons that occasionally fire).  Immediately I created an application configuration file for my command-line application and added the following (of course, I had to look-up the exact syntax):

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.net>
        <connectionManagement>
            <add address="*" maxconnection="100" />
        </connectionManagement>
    </system.net>
</configuration>

This allows for up to 100 simultaneous connections to any server.  When I ran my command-line application again it worked perfectly -- 20 requests were issued to the URL.

After poking around a little bit, I found the actual specification in the HTTP/1.1 protocol that dictates this limitation.  See Hypertext Transfer Protocol - Section 8.1.4:

"Clients that use persistent connections SHOULD limit the number of simultaneous connections that they maintain to a given server. A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. A proxy SHOULD use up to 2*N connections to another server or proxy, where N is the number of simultaneously active users. These guidelines are intended to improve HTTP response times and avoid congestion."

Ah, this explains why Internet Explorer only allows me to download two files at the same time!

Now, in order to allow my BizTalk orchestration to proper issue more than two concurrent connections (remember, it too is just calling .NET code), I needed to modify the BTSNTSvc.exe.config file that services the BizTalk host instances.  This file is found in the BizTalk Server directory under Program Files:

BTSNTSvc.exe.config

Simply add the <system.net> ... </system.net> XML to the configuration file and then restart your host instances.  This will allow you to issue more than two concurrent requests to any given web server.

I hope this helps save you some time you would have otherwise lost!

posted on Thursday, August 16, 2007 12:40:40 PM (Central Standard Time, UTC-06:00)  #    Comments [6] Trackback
# Sunday, July 15, 2007

Edit: Try my own version of Hangman, written with Silverlight and WPF!

In an effort to distract myself from more productive endeavors, I started playing Hangman on The Free Dictionary Web site today.  They have an outstanding plugin that you can also add to your Google homepage (which is actually how I discovered it).  The interface allows you to simply type the letter into the guess box (if you look below you'll see that my last guess was the letter "G"), and it updates it as you go:

image

During game play, the plugin constructs the man you are desperately trying to save.  You can only make ten mistakes before the man is hanged:

image

So, as I was wasting my time today, I started wondering if there's a strategy to winning Hangman.  Assuming that there is, I decided to try and figure it out.

I started with a few basic ideas:

  1. Certain letters are used more frequently than other letters.
  2. The frequency of letters are probably different given the total number of letters in the word.
  3. You can maximize your ability to win Hangman by playing the most common letter first, followed by the next most common letter, and so on.

I realize that there are probably more complicated theories (such as the most common letters changing based on discovered letters), but for this first part I decided that I wanted to keep it somewhat simple.  I can complicate it more later on.

(If you aren't interested in how I explored these ideas than jump to the end of this post to see my findings!)

My first order of business was to find a list of English words.  After performing a few searches, I found what I was looking for: Word-List.com.  They have word lists in many languages, and the lists are available as zipped text files separated by a newline.  This made it very easy to parse.

I unzipped the file and added it to a new Visual Studio 2005 Console Application project.  So that the file is always available in the root of the folder, I changed the Copy to Output Directory property to Copy always.  Next, I wrote a method that loads all the words into a string array and saves each word to a particular file, based on the length of the word.  For example, the words "dog" and "cat" are saved to the file "03.txt" whereas the words "father" and "mother" are saved to the file "06.txt".  Here's the code:

using (StreamReader input = new StreamReader("words.english.txt"))
{
    string contents = input.ReadToEnd().Trim();
    string[] wordArray = contents.Split('\n');

    foreach (string word in wordArray)
    {
        appendWord(word);
    }
}

Using the StreamReader, I loaded the file and split it into a string array based on the character '\n' (note the single, rather than double, quotes).  This allows me to iterate through an array of words.  I pass each word to the appendWord method, which calls the appendFile method.  Passed in is a file name that's based on the length of the word, as well as the word itself.

private static string wordByLength = "{0}.txt";
public static void appendWord(string word)
{
    appendFile(String.Format(wordByLength, word.Length.ToString().PadLeft(2, '0')), word);
}

The appendFile method simply creates an instance of a StreamWriter and appends the word to the file (if it exists) or creates a new file (if it doesn't exist).

public static void appendFile(string file, string text)
{
    using (StreamWriter writer = File.AppendText(file))
    {
        writer.WriteLine(text);
        writer.Flush();
    }
}

Once I wrote all this and let it run, it took about 20-30 minutes to complete (I don't know exactly because I went to eat dinner).  When I cam back, I had a directory of 23 files filed with sorted and ordered data:

image 

Pretty cool, but not quite there!

Before I moved on, I wanted to know how many words had only two characters, three characters, and so on, all the way to 24.  So, I wrote another method that loaded each of the aforementioned files, loaded them into an array, and determined the length of the array.

foreach (string fileByLength in Directory.GetFiles(wordByLengthDirectory))
{
    using (StreamReader input = new StreamReader(fileByLength))
    {
        string contents = input.ReadToEnd().Trim().Replace("\r\n", "\n");
        string[] wordArray = contents.Split('\n');
        int wordLength = wordArray[0].Length;

        appendFile(wordCountByLength, wordLength.ToString().PadLeft(2, '0') + ": " + wordArray.Length);
    }
}

Nothing really complicated here.  I loaded the files into a file array by using the Directory.GetFiles() method, replaced "\r\n" with "\n" since the Environment.NewLine creates carriage return and a newline, split the array, and appended the length of the array (along with the number of characters in the word) into a file.  Here were the results:

02: 61
03: 627
04: 2988
05: 7198
06: 14163
07: 20452
08: 27015
09: 29824
10: 29220
11: 25021
12: 19966
13: 14683
14: 9672
15: 5890
16: 3363
17: 1808
18: 838
19: 428
20: 197
21: 81
22: 40
23: 17
24: 5

Kind of interesting that there are more 9-letter words than any other words, eh?  It also makes a really pretty looking graph:

While it's interesting, it doesn't really help me with my stated goal.

So, with the twenty-three independent files, I decided to find the most common letters, and sort them according to popularity.  This, my friends, is the best part.  Not only did I decide to use a generic list to contain the details of these files, but I also decided to use predicates and delegates to help me with my task!  What fun!?!

First, I needed a collection to start the characters and counts in.  For example, when I load the 61 words that contain two characters, I need a collection that keeps track of the frequency each character is used.  To do this I defined a class called CharacterCounter, and then created a generic list.  First, here's the class I created.

public class CharacterCounter
{
    private char character;
    private int count;

    public char Character
    {
        get { return character; }
        set { character = value; }
    }

    public int Count
    {
        get { return count; }
        set { count = value; }
    }
}

Very simple class.  It has two public properties: Character and Count.  When used as a generic list, it allows me to create an object of characters with their associated counts (e.g. frequency used).

In determining the frequency each character is used based on the number of characters in the words, I first iterate through each of the files that contain the sorted words.  This allows me to first find the ordered frequency of characters for words with two characters all the way to twenty-four characters.  I then load the contents of each file into a string array, so that I can iterate through each of the words.  Next, I create a character array out of the word, and iterate through each of the characters.  It looks something like this:

foreach (string fileByLength in Directory.GetFiles(wordByLengthDirectory))
{
    List<CharacterCounter> characterCounters = new List<CharacterCounter>();
    int fileLength = 0;

    using (StreamReader input = new StreamReader(fileByLength))
    {
        string contents = input.ReadToEnd().Trim().Replace("\r\n", "\n");
        string[] wordArray = contents.Split('\n');
        if (fileLength == 0)
        {
            fileLength = wordArray[0].Length;
        }

        foreach (string word in wordArray)
        {
            char[] characterArray = word.ToCharArray();

            foreach (char character in characterArray)
            {
                //...
            }
        }
    }
}

Once I am iterating through each of the characters, I need to start keeping track of the number of times the character appears.  Notice that I created a generic list for my CharacterCounter class called characterCounters.  Before I increment the character count, I first check to see if the character in question already exists within my list.  This is performed using the following code:

CharacterCounter characterCounter = characterCounters.Find(delegate(CharacterCounter d) 
{ 
    return d.Character == character; 
});

If the character exists, then the characterCounter list returns the CharacterCounter object that has a Character that equals the character I specified.  For instance, if the specified character is "a", and it exists in the CharacterCounter list, then the characterCounter object is returned with an "a" for the Character and the number of times it has been found in the Count property.

If the character does not exist in the characterCounters list, then the characterCounter object is null.  If it's null, I create a new instance of a CharacterCounter, set my values accordingly, and add it to the list.  If it's not null, I increment the count:

if (characterCounter == null)
{
    characterCounter = new CharacterCounter();
    characterCounter.Character = character;
    characterCounter.Count = 1;

    characterCounters.Add(characterCounter);
}
else
{
    characterCounter.Count += 1;
}

This continues until I've gone through every word in the file.  At this point, I have a generic CharacterCounter list filled with the number of times a character is found.  Since I want to know the most commonly used characters, I need to sort my list so that the most common characters are first.

The following code will sort the characterCounters list by descending count:

characterCounters.Sort(delegate(CharacterCounter cc0, CharacterCounter cc1)
{
    return cc1.Count.CompareTo(cc0.Count);
});

Using an anonymous delegate, you can tell the list to sort itself from the largest number to the smallest.  Changing the code to the following will sort it from smallest to largest:

characterCounters.Sort(delegate(CharacterCounter cc0, CharacterCounter cc1)
{
    return cc0.Count.CompareTo(cc1.Count);
});

All this is done without having to use the IComparable interface!  Amazing!

At this point, our list is sorted.  All we need to do is create a string based on the ordered characters, and write it to a file:

foreach (CharacterCounter characterCounter in characterCounters)
{
    orderedCharacters += characterCounter.Character.ToString();
}
appendFile(orderedFrequencyByLength, fileLength.ToString().PadLeft(2, '0') + ": " + orderedCharacters);

The code simply iterates through the characterCounters list, appends the value of the Character property to a string, and then appends the data to a string.

After all of this work, we can finally show the list. 

# of characters in word: list of characters, from most frequent to least frequent

02: aoueyidbrtfslnmhgwzkvjcp
03: aoeuirstdylmghpknbwfczvxj
04: aeoirutslndpkymbhgcwfzvjxq
05: aeriosnltuycdmhpbgkwfvzxjq
06: earinoltsucdmphgbykfwvzxjq
07: eairnoltsucdmpghbykfwvzxjq
08: eaironlstucdmphgybfkwvzxjq
09: eiarontslcudmphygbfkvwzxqj
10: eiaorntslcupdmhygbfvkwzxqj
11: eiaorntslcupmdhygbfvkwzxqj
12: eiaontrslcupmhdygbfvzkxwqj
13: eiaontrslcpumhdygbvfzxkqwj
14: eioantsrlcpuhmdygbvfzxqkwj
15: ieoantsrlcpuhmydgbvfzxqkwj
16: eioatnrslcphumydgbvfzxqkwj
17: ieoatnrsclphumydgbvfzxqkwj
18: oeiatrnsclphmuydgbvfzxqkjw
19: oiaetnrsclhpmyudgbvzfxqkwj
20: oieatrnlchspymugdbvzfxjqk
21: oietanlcrshpymgdubzfvjx
22: oeticrahnslpymdugbxvzjq
23: oiaetlhscnprmdyugbfx
24: oihletapcyrdsnfgmzux

Going from the left to the right, we can tell the frequency of a letter in a word based on the number of characters in the word.  For example, in a nine-letter word, the most frequently used characters are: e, i, a, r, o, n, etc.

Now, here's the real question: does knowing all of this help us to be a better Hangman player?

Well, what don't you try for yourself.  Go to The Free Dictionary and play a game of Hangman.  Based on the number of characters in the word, try the list characters above in their given order.  From what I've seen, it doesn't work 100% of the time, but it certainly gets it correct more often than not.

In Part II (goodness, a Part II?!?!), I plan to write a test framework that uses the dictionary I downloaded to randomly calculate the accuracy of this method.  I'll use a large set of sample data, and see how often I can guess the word correctly based on the rules above without specifying ten incorrect answers.

In Part III (holy smokes!), I'd like to make the rules more intelligent by changing the most common letters used based on the known letters in the word.  For instance, if the word has an S and T, I'd like to know if an E or I is more likely to also be in the word.

Chances are it'll be awhile before I get to Part II and Part III, but I think I'll get there eventually.

I truly hope that at least someone else out there has fond some value in this post!  Here's the full source-code, along with all the parsed files (look in the debug directory).

Dictionary.zip (2.15 MB)

Please let me know if you have any thoughts!

posted on Sunday, July 15, 2007 8:28:23 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback

Years ago I made a serious mistake when I installed, and briefly used, iTunes -- I gave it the ability to reorganize my music folders!  Since then, all my nicely organized and sorted directories of (legally obtained) digital music have been disorganized and confused.

For example, I used to have a directory named "U2" where all my music for the group U2 was stored.  However, iTunes decided that my organization structure was too simple, and broke it out into folders derived from the contributing artists.  Now I have "Bono & The MDH Band", "Bono_Daniel Lanois", "Bono_Gavin Friday_Maurice Seezer", etc. It's a real mess.  Additionally, it left the folder "U2" with a bunch of empty subdirectories!  How rude!

I've made an effort, as of late, to try and clean-up this mess.  In doing so, I've ended up with a lot of empty directories that previously contained music and/or subdirectories.  After manually deleting a couple directories, I decided I needed to automate the process.  I was surprised to find that this process is not nearly as straightforward as I thought it would.

In going about this process, I decided to do the following:

  • Allow it to scan the directories first, and tell me which folders are empty (allows me to double-check!)
  • Ignore all hidden files (this is because Windows Media Player and iTunes create hidden album art which I don't want to preserve)
  • Log empty directories, deleted directories and errors
  • Scan first, delete second
  • Ignore read-only attributes (all my music is marked as read-only to prevent someone from doing what I'm doing)
  • Delete a directory if it is made empty because it's subdirectories are deleted

Let me break down how I accomplished these things.

To scan for empty directories, I created a method that starts with a predefined directory, and recursively iterates through all the subdirectories.  During this iteration, I scan the folder to see if it contains any files using the GetFiles method.  Rather than specify files with a specific file extension (like .mp3 or .wma) I decided to get all files by using the "*.*" pattern (directory happens to be DirectoryInfo object).

using System.IO;

...

FileInfo[] files;
files = directory.GetFiles("*.*");

However, many of the files are hidden (because WMP and iTunes creates album art that's hidden), so I decided to exclude hidden files.  I did this by checking the hidden attribute of the file, and if it was turned on I ignored the file.  If non-hidden files were found, I marked a local boolean value as true, indicating the folder contains files.

foreach (FileInfo file in files)
{
     // only look for files that are not marked as hidden
     if (!((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden))
     {
         notEmpty = true;
     }
}

Next, I want to check to see if the directory contains subdirectories, and if those subdirectories are empty.  To do this I get all the subdirectories and call the same method that is currently executing.  This provides a level of recursion that allows me to iteratively go through every subdirectory in a directory.  Since the existence of a subdirectory means that the directory is not empty, I automatically mark the current directory as not empty.

// look through directories recursively
DirectoryInfo[] dirs = directory.GetDirectories("*.*");
foreach (DirectoryInfo dir in dirs)
{
     scanForEmptyDirectories(dir);
     notEmpty = true;
}

Next, I add the directory path to a generic string collection so that I can later iterate through and delete the directories.  The code here is very simple:

private static List<string> directoriesToDelete;
directoriesToDelete = new List<string>();

...

if (!notEmpty)
{
    // add folder to collection
    directoriesToDelete.Add(directory.FullName);
}

This accomplished, I needed to write a method that logs empty directories, the deletion of directories, and errors.  I created a method that uses the StreamWriter and appends text to a file.  Very simple.

public static void logMessage(string logMessage)
{
    using (StreamWriter writer = File.AppendText(logFile))
    {
        writer.WriteLine(logMessage);
        writer.Flush();
    }
}

This method is called throughout the application, and writes any pertinent information to a log file (as you'll see below).

Now, to delete the directories, I iterate through my generic string collection.  Since some of my directories are marked as read-only, I elected to change the attributes of all directories marked for deletion to normal.  This removes the read-only flag, if it exists.  Next, I performed a similar operation on the files left in the directory (e.g. hidden files) and removed the read-only flag.  Once these two steps are complete, I can then delete the folder using the Delete method on the Directory object.

// iterate through the empty directories
foreach (string directory in directoriesToDelete)
{
    try
    {
        DirectoryInfo dir = new DirectoryInfo(directory);
        // set the attributes to normal; you cannot delete a readonly folder
        dir.Attributes = FileAttributes.Normal;

        string[] files = Directory.GetFiles(directory);
        // iterate through the files
        foreach (string file in files)
        {
            FileAttributes attributes = File.GetAttributes(file);
            if ((attributes & FileAttributes.ReadOnly) != 0)
            {
                // remove the readonly attribute from the files
                File.SetAttributes(file, ~FileAttributes.ReadOnly);
            }
        }

        // make sure to make the delete as recursive
        Directory.Delete(directory, true);
        // log the deletion
        logMessage(string.Format("Deleted: {0} ", directory));
    }
    catch (Exception e)
    {
        // log the error
        logMessage(string.Format("Error deleting: {0} ", directory));
        logMessage(string.Format("Error message: {0} ", e.Message));
        logMessage("Exiting application");
        // exit the console application
        Environment.Exit(0);
    }
}

Notice that I catch errors, log them, and then exit the application.  Also, the try/catch is embedded in the directory loop, so that the directory path can be included in the log (if it were the other way around, the directory path would be out of scope and unavailable).

Finally, given the above code, it is possible to make a parent directory empty by deleting its child directories.  To resolve this, I wrapped the execution of my code in a while statement that only exists when a given condition returns false.  This condition value returns false only if there hasn't been a single directory marked as empty.  If any directory is marked as empty, the application performs an additional scan following the deletion of the directories it found empty.  (Note: the runScan boolean value is a class-level variable that is set to true elsewhere in the code.)

while (runScan)
{
     // assume that we won't have to run again
     runScan = false;

     // if the value returns true, then run it again
     // it would return true 
     scanForEmptyDirectories(new DirectoryInfo(startingDirectory));
}

All in all, this was a very quick and efficient way to eliminate my empty music folders.  Your needs may be slightly different, so make changes accordingly.

I've gone ahead and included a zip file with the full source code for this console appliation.  Let me know if you have thoughts and/or suggestions!

EmptyFolders.zip (20.81 KB)

posted on Sunday, July 15, 2007 1:18:41 PM (Central Standard Time, UTC-06:00)  #    Comments [1] Trackback
# Wednesday, July 11, 2007

So, I've finally joined the 21st century, and purchased a Samsung Blackjack.  After waiting a two agonizing days, it finally arrived.  Man, it's a beauty!

Samsung Blackjack

I'll spare you a review of all it's features (this is covered in agonizing detail elsewhere), but there are a couple tidbits I'd like to share.

The Blackjack runs Windows Mobile 5.0 with the Messaging and Security Feature Pack.  One of the neatest features that this supports is Exchange ActiveSync w/ Direct Push Technology.  Fortunately, the company I work for has Exchange 2007 setup to push emails directly to smart phones, and this was the first thing I setup.  Worked like a charm!  Very easy.

Note: I know that Windows Mobile 6.0 is out.  If you have an application that runs Windows Mobile 6.0, then good for you!  Unfortunately, the Blackjack is still on 5.0, which is why this post is geared towards 5.0.

The second thing I looked into was how to setup a development environment that allows me to develop applications that I can deploy and run on my Blackjack.  Turns out, it's pretty simple.

My searches lead me to the Windows Mobile 5.0 Developer Resource Kit.  This kit contains (almost) everything you need to develop Windows Mobile 5.0 applications for a SmartPhone and Pocket PC.  I suggest you review the details surrounding the resource kit, and then download it and try it yourself.

As I said, my goal here is to setup a development environment and deploy a simple application to my Blackjack.  In order to consider this a successful test, I don't need the application to actually do anything except run.  Here are the steps I took.

Note: while this is written specifically for the Blackjack, the following steps will (largely) work for any kind of SmartPhone running Windows Mobile 5.0.

  1. Install all the prerequisites.  I already had all the required prerequisites installed (e.g. Visual Studio 2005 and ActiveSync).  I haven't yet tried any of the optional installations.
  2. Download the Windows Mobile 5.0 Developer Resource Kit.  This kit has everything you need to get going.
  3. Run the MSI and install the kit.
  4. Once the kit is installed, run the Windows Mobile 5.0 Developer Resource Kit application.  You can run this by clicking Start --> All Programs --> Windows Mobile 5.0 Developer Resource Kit --> Windows Mobile 5.0 Developer Resource Kit.
  5. Click Install the Developer Tools --> Install the Tools, and then in the detail pane slide down and click Windows Mobile 5.0 SDK for Smartphone (or Pocket PC, if you prefer).

    Windows Mobile 5.0 Developer Resource Kit
  6. This opens up File Explorer in the folder C:\Program Files\Windows Mobile 5.0 Developer Resource Kit\content\Developer Tools\Windows Mobile 5.0 SDKs\.  From here, click Windows Mobile 5.0 SDK for Smartphone.msi or Windows Mobile 5.0 SDK for Pocket PC.msi.
  7. Once the installation completes, Visual Studio will have been updated to include a new project type called "Windows Mobile 5.0 Smartphone" under "Visual Basic" and "Visual C#".  Open Visual Studio 2005, and click File --> New --> Project.

    Windows Mobile 5.0 Smartphone project
  8. Select "Device Application" and click OK.  Yes, I know the name and location are poor.  Change them if you want; remember, this is just a quick test/demo.
  9. The project template includes a Form1.cs file.  The designer shows a form embedded in a phone shell.  Drop a label on the form that says something (e.g. "Hello world!").

    image
  10. Make sure your Blackjack is connected to your computer via your USB cable, and confirm that ActiveSync is able to communicate and synchronize with your Blackjack.
  11. Samsung and Microsoft lock the Blackjack phone so that applications from unsigned publishers (like us!) will not run or deploy to the Blackjack phone.  In order to resolve this, take a look at these two discussions: Application Unlock Your Blackjack and Samsung Blackjack tips and tricks (the latter has some great tips).  I chose the method in the first link, and ran AppUnlock.cab.  Worked great.
  12. Go to Build --> Deploy DeviceApplication1.  A dialog box asks you where to deploy your application.  Choose Windows Mobile 5.0 Smartphone Device.  Click Deploy.

    Deploy Smartphone device
  13. The initial deployment will push a lot of CAB files used to run your .NET application.  Make sure you approve all these cabs from your Blackjack.  You will only have to do this the first time.
  14. Since our application was very simple, we'll have to browse to the executable through the file explorer.  Click Start --> Applications --> File Explorer.  Browse to \Program Files\DeviceApplication1, and click DeviceApplication1.
  15. As we have not signed the application, you will have to confirm that you want to run the application (I'll write a post some other time explaining how to sign the application).  Click Yes to continue.
  16. Lo and behold, our application runs and displays "Hello World!".

Yes, a completely useless and standard example, but it does serve to highlight how easy it is to setup a development environment that can publish applications to your SmartPhone (e.g. your Blackjack).

Now all I need to do is figure out a useful tool to build ...

I hope this helps!

posted on Wednesday, July 11, 2007 2:58:36 AM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
# Tuesday, June 19, 2007

As a Commerce Server developer, you are bound to come across a situation where the smart client business user applications (e.g. the Catalog Manager, Catalog and Inventory Schema Manager, Customer and Orders Manager, and Marketing Manager) do not satisfy a business requirement, need to reflect a company's brand or style, or your business users simply want it changed.

Fortunately, you can get the full C# source code for the business user applications (except for the Catalog and Inventory Schema Manager; see Max's comment in this thread) through the Commerce Server 2007 Partner SDK.  This gives you the ability to modify the business user applications and be a hero to all your colleagues and clients (chuckle!).

Yeah, yeah, this is all old news.  However, I just came across a great idea from Søren Spelling Lund in which he talks about his experiences using ClickOnce Deployment along with the business user applications.

What is ClickOnce Deployment?

ClickOnce deployment is a technology designed to ease the difficulty in creating self-updating Windows-based applications.  Using the Publish Wizard in Visual Studio 2005 (or mage.exe, mageui.exe, or MSBuild), you can publish your application in three different ways: to media (such as a CD-ROM), to a network file share, or to a Web page.

When an application is published, two files are created: an application manifest, and a deployment manifest.  The application manifest describes the contents of  the application, including the assemblies, dependencies, and the files that make up the application.  The deployment manifest describes how the application is deployed, the location of the application manifest, and the version of the application that should be run by the clients.

Here's an example of an application published to a Web page (this particular example is from the SpaceWar SDK for the XNA framework - yes, I truly aspire to be an XBOX 360 game developer):

Example of application published to a Web page via ClickOnce

(Note the deployment version number.)

Once the end-user installs the application from the deployment location, the application is, by default, added to the Start menu and the Add/Remove programs group in the Control Panel.  Nothing is added to the Program Files folder, the registry, or the desktop.  When I first played around with ClickOnce this last part caught me unaware -- I couldn't figure out where my application had been installed!  Also, no administrative rights are required to install the application.

Now, remember the deployment version number?  The best part about this technology is that, if a developer publishes a new version of the application, the deployment version number is incremented.  Consequently, the next time the end-user runs the application they are presented with an opportunity to update their application.  What's neat is that, as a developer, you actually have a lot of control: you can require an update, and you can even require that a user rolls back to an earlier version of the application (not that any of us would ever have to roll back to an earlier version ...).

All-in-all, it's neat stuff.  And, while it's not a perfect solution and has it's own problems, you can very rapidly integrate it into your application and quickly reap the rewards.  Oh, and you can easily become a hero to your colleagues and clients (are you starting to sense a pattern?).

Integrate ClickOnce Deployment into the Partner SDK?

So, we're back to the smart client business user applications.  I'm sure you're now asking yourself, what does this have to do with the Commerce Server 2007 business user applications (or maybe not, since I alluded to it above)?

Søren Spelling Lund posted an article about integrating ClickOnce Deployment into the business user applications.  It's a great idea, and one I will definitely use in the future.  Additionally, he takes the time to point out a problem him and his colleagues ran into when they tried to run the ClickOnce installer on the Customer and Orders Manager.  Essentially, it appears that the project file included a <TargetZone> element that interfered with the ClickOnce installer.  Removing the <TargetZone> element resolved the problem.  Check out his blog article for the full details.

What are the benefits of integrating ClickOnce Deployment into the Partner SDK?

  • Ease - Easily deployment of business user applications to end-users
  • Versioning - Make sure your end-users are running the latest and greatest
  • Safety - The ability to roll back to a previous version of the application (just in case!)
  • Security - The applications run in a security context that prevents users from doing malicious things
  • Fame - Yes, you too can be a hero ...

Thanks to Søren for a great idea and a great post!

What great ideas have you put into play?  Please let me know!

posted on Tuesday, June 19, 2007 8:50:33 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback
# Saturday, June 02, 2007

Having recently purchased a new server, I found myself configuring and setting up the various virtual machines I use for software development.  After I setup my development virtual machine, I moved some of my projects over to the D drive and double-clicked one of the project files.  After the IDE opened up, but before the project loaded, I received the following dialog message:

NotTrusted

"The project location is not trusted.  Running the application may result in security exceptions when it attempts to perform actions which require full trust."

This is something I've had to setup and configure many times, but since it's not something I do every day I had to go look up the command to add my D drive as a group with full trust.  To accomplish this, do the following:

  1. Select Start -> All Programs -> Microsoft Visual Studio 2005 -> Visual Studio Tools -> Visual Studio 2005 Command Prompt.  This opens up a command prompt with all the Visual Studio program folders in the PATH (type "path" to see).
  2. Type the following command:
caspol -q -machine -addgroup 1 -url file://d:/* FullTrust -name "D Drive"

That's it.  You have now added the D drive to your .NET runtime security policy with the Full Trust permission set.  You will not receive any security exceptions when running your .NET applications.

If you open up the .NET Framework 2.0 Configuration MMC, and browse to My Computer -> Runtime Security Policy -> Machine -> Code Groups -> All_Code, you will see the "D Drive" code group.

I hope this helps!

posted on Saturday, June 02, 2007 3:09:28 PM (Central Standard Time, UTC-06:00)  #    Comments [26] Trackback
# Friday, May 04, 2007

If you've worked with .NET 2.0 long enough, you've probably come across the following error:

The description for Event ID ( 0 ) in Source ( .NET Runtime ) cannot be found. The local computer may not have the necessary registry information or message DLL files to display messages from a remote computer. You may be able to use the /AUXSOURCE= flag to retrieve this description; see Help and Support for details. The following information is part of the event: Unable to open shim database version registry key - v2.0.50727.00000.

This behavior is caused because the ASP.NET application needs read/write access to a specific registry key but has only read access.

There is a hotfix available for this error: http://support.microsoft.com/kb/918642

Note the following fine print:

A supported hotfix is now available from Microsoft. But the hotfix is intended only to correct the problem that is described in this article. Apply this hotfix only to systems that are experiencing this specific problem. This hotfix may receive additional testing. Therefore, if you are not severely affected by this problem, we recommend that you wait for the next Visual Studio 2005 service pack that contains this hotfix.

This error is more of an annoyance because it fills up the application log.  It shouldn't actually affect any of your applications.  If you don't mind ignoring the error, then just ignore it and wait until the next service pack resolves the problem.

Best of luck!

posted on Friday, May 04, 2007 12:35:30 PM (Central Standard Time, UTC-06:00)  #    Comments [0] Trackback