Using DispatchTimer to help in UI Behavior

Wednesday, October 21, 2009 12:49:49 AM (GMT Daylight Time, UTC+01:00)

I have ran into this problem a number of times developing UI.  You have business logic that needs to execute when a user is done entering some piece of data or criteria.  The business logic may be complicated and takes longer then what is considered an acceptable threshold (somewhere around 3 seconds). 

Your first thought might be to use the TextChanged, SelectionChanged or KeyUp events to detect change in the targeted control and run the business logic… but after you have implemented the approach you find out that the logic is just too expensive and the UI appears locked or sluggish.  The LostFocus event doesn’t work well either… it doesn’t get fired in some situations (clicking on a toolbar or selecting a menu).  

The approach that I have used both in WinForms and WPF development is to monitor the TextChanged event and when event has gone dormant for a specified duration, run the business logic. The key is not to wait an extended period of time to run the logic, but rather execute the logic at a tight enough interval that the application appears responsive after the user is has completed their data entry.

This is what the code does.

In the constructor we setup a DispatchTimer to fire at a default interval and subscribe to the TextChange event of our target control.

public ExecuteOnDormant(TextBox ctl, Action action)
{
    _control = ctl;
    _action = action;
    _dormantTimer = new DispatcherTimer();
 
    _defaultTimeout = new TimeSpan(0, 0, 0, 0, _timeOut);
 
    if (_timeOut == 0)
        _dormantTimer.IsEnabled = false;
    else
    {
        _dormantTimer.Interval = _defaultTimeout;
        _dormantTimer.IsEnabled = false;
        _dormantTimer.Tick += new EventHandler(RunBusinessLogic);
 
        //assumging that it is only a textbox we are watching... if we want to make this more generic... then add a  to KeyUp or SelectionChanged
        _control.TextChanged += new TextChangedEventHandler(ctl_TextChanged);
    }
}

The handler for the TextChange event then resets the DispatchTimer… this allows the user to complete typing the value before complex business logic is run.

/// <summary>
/// Handles the TextChanged event of the control that we are monitoring to run business logic on.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.Windows.Controls.TextChangedEventArgs"/> instance containing the event data.</param>
private void ctl_TextChanged(object sender, TextChangedEventArgs e)
{
    ProcessActivity();
}
 
/// <summary>
/// Processes the activity(keyboard and mouse) in the shell to ensure that it stays active and does not fire the 
/// IdleTimeOut event.
/// </summary>
private void ProcessActivity()
{
    //reset the timer, the control has changed.
    _dormantTimer.IsEnabled = false;
    _dormantTimer.Interval = _defaultTimeout;
 
    //now enable the timer, as we want it to fire when it times out.
    _dormantTimer.IsEnabled = true;
}

When the user has stopped typing long enough for the interval to expire the business logic is executed.  To make this class usable across different controls we use an Action delegate to define the business logic.

private void RunBusinessLogic(object sender, EventArgs e)
       {
           //disable the timer, so it doesn't continue to run the business logic
           _dormantTimer.IsEnabled = false;
           Dispatcher.CurrentDispatcher.InvokeOnUIThread(_action);
       }

The business logic definition looks like this:

public partial class Window1 : Window
    {
        private ExecuteOnDormant _dormantWatcher;
        private Random _randomVal = new Random();
        public Window1()
        {
            InitializeComponent();
 
            _dormantWatcher = new ExecuteOnDormant(txtCriteria, ExecuteComplexBusinessLogic(txtCriteria));
        }
 
        private Action ExecuteComplexBusinessLogic(TextBox control)
        {
           return  () =>
                       {
                           Mouse.SetCursor(Cursors.Wait);
                           
                           //run complex business logic...
                           int val = _randomVal.Next(1,3);
 
                           //make it seem like a long time
                           Thread.Sleep(1000);
                           
                           //give the rich feedback on whether the item passed validation
                           control.Background = (val == 1) ? Brushes.LightGreen : Brushes.Salmon;
 
                           Mouse.SetCursor(Cursors.None);
                       };
        }
    }

You will notice that it uses a extension method off of the CurrentDispatcher called InvokeOnUIThread.  This method ensures that the business logic is executed in the correct thread context.  Below is the implementation for the extension method.

/// <summary>
/// Invokes on the UI thread if necessary.
/// this.Dispatcher.InvokeIfNecessary(() =>
/// {
        //do something that might need to be invoked
/// });
/// </summary>
/// <param name="dispatcher">The dispatcher.</param>
/// <param name="action">The action.</param>
public static void InvokeOnUIThread(this Dispatcher dispatcher, Action action)
{
    if (Thread.CurrentThread == dispatcher.Thread)
    {
        action();
    }
    else
    {
        dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate { action(); });
    }
}

You can download the demo project.

.Net Best Practices Event

Wednesday, April 01, 2009 3:32:59 AM (GMT Daylight Time, UTC+01:00)

Here is the deck and code samples I presented for the .NET Best Practices event that was hosted in St. Louis, Missouri on March 24th and in Bloomington, Mn on March 31st.

Building Loosely Coupled Applications

DeckAndCode.zip (2.58 MB)

Binding TextBlock to RichText

Thursday, October 02, 2008 4:16:41 AM (GMT Daylight Time, UTC+01:00)

I was doing some updates to a debugging tool for WPF... I was adding a filter feature that would highlight the characters in yellow as you entered them.

image

I was originally using a TextBlock to bind to by information and that worked fine until I was adding the rich text feature of highlighting the search string.  You can't bind to the Text property of a TextBlock and pass in a Inline object (shown below, which is what supports the rich text within a TextBlock).

image

The only time you could pass Inlines to the TextBlock was in the constructor... that wasn't going to work with a binding.

That forced me down the road of investigating the use of a Label, as I could bind to the Content property and it would render the Inline elements I was creating just fine... this resulted in the rich text highlighting.  However, there was a couple of issues... since this is constantly taking information from the Win32 OutPutDebugString call (see DbgMon) it really needed to perform well when rendering.  I was noticing a little bit of a performance hit... not substantial, but noticeable (maybe that is substantial then).   In addition, wrapping text with in a Label turned out to be not so easy. 

So I went back to the TextBlock.  I tried to bind to the Inlines property.  There was a couple of issues with that... one it wasn't a DependencyProperty and the other issue was that it was read-only.

Solution:

Since I had written a converter to handle highlighting of the filtered text within the string, I was returning Inlines. I modified the convert so that I would return a TextBlock and since I was creating the inlines I was able to pass them into the constructor of the TextBlock.  Instead of binding to a TextBlock in the DataTemplate, I binded to a ContentControl, which would intern using the converter to get the TextBlock that was generated with the filter text highlighted.

The resulting converter looked like the following:

public class StringToHighlightConverter :  IValueConverter
   {
       public object Convert(object values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
       {
           //get the data found in the text
           string dataText = (string)values;
 
           if (!string.IsNullOrEmpty(dataText) && !string.IsNullOrEmpty(TextSearchFilter.SearchClause))
           {
               List<StringOccurance> highlightedWords = new List<StringOccurance>();
               foreach(string token in TextSearchFilter.TokenizedSearchClause)
               {
                   highlightedWords.AddRange(StringEx.FindStringOccurances(dataText, token));
               }
               
               if (highlightedWords.Count == 0)
                   return CreateTextBlock(dataText);
 
               int textStartIdx = 0;
               string plainWord = string.Empty;
               int wordOccuranceIdx = 0; //keep count of the words we have proccessed
               Span phrase = new Span();
 
 
               for (int i = textStartIdx; i < dataText.Length; i++)
               {
                   if (wordOccuranceIdx == highlightedWords.Count)
                   {
                       //We have reached the maximum word occurances, add the plain word span
                       Span wordSpan = CreateWordSpan(dataText.Substring(i), false);
                       phrase.Inlines.Add(wordSpan);
                       break;
                   }
 
                   //get the currrent word that needs to be highlighted
                   StringOccurance wordOccurance = highlightedWords[wordOccuranceIdx];
 
                   if (wordOccurance.StartIndex == i)
                   {
                       //add the plain word span
                       if (!string.IsNullOrEmpty(plainWord))
                       {
                           Span wordSpan = CreateWordSpan(plainWord, false);
                           plainWord = string.Empty;
                           phrase.Inlines.Add(wordSpan);
                       }
 
                       Span highLightedWordSpan = CreateWordSpan(wordOccurance.Value, true);
                       wordOccuranceIdx++;
                       //issue with this
                       i = i + (wordOccurance.Value.Length - 1);
                       phrase.Inlines.Add(highLightedWordSpan);
                       continue;
                   }
 
                   if (i >= dataText.Length)
                   {
                       //add the plain word span
                       //Span wordSpan = CreateWordSpan(dataText.Substring(i), false);
                       Span wordSpan = CreateWordSpan(plainWord, false);
 
                       phrase.Inlines.Add(wordSpan);
                       break;
                   }
 
                   plainWord = plainWord + dataText[i];
               }
 
               return CreateTextBlock(phrase);
 
           }
           return CreateTextBlock(dataText);
       }
 
       private TextBlock CreateTextBlock(Inline text)
       {
           TextBlock block = new TextBlock(text);
           block.TextWrapping = System.Windows.TextWrapping.Wrap;
           return block;
       }
       
       private TextBlock CreateTextBlock(string text)
       {
           Span textSpan = new Span();
           textSpan.Inlines.Add(text);
           return CreateTextBlock(textSpan);
       }
 
       private Span CreateWordSpan(string text, bool highlight)
       {
 
           Span wordSpan = new Span();
           if( highlight == true)
           {
               wordSpan.Background = Brushes.Yellow;
           }
               
           wordSpan.Inlines.Add(text);
           return wordSpan;
 
       }
    
       public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
       {
           throw new NotSupportedException("Not supported");
       }
   }

The xaml in the DataTemplate looked like:

<DataTemplate>
    <ContentControl Content="{Binding OutPutDebugString, 
                    Converter={StaticResource highlightConverter}}" 
                    VerticalAlignment="Top" />
</DataTemplate>

Updating Highlighted Search in WPF

Monday, September 29, 2008 3:00:44 AM (GMT Daylight Time, UTC+01:00)

I released a sample that both filters and highlights the results in a listbox a while back.  I got an email from A.V.Ebrahimi about highlighting multiple words when searching.  The sample that I had done... was done in about 20 minutes before a presentation to demonstrate another feature in WPF that didn't require GDI to draw embellishments on the control.  Anyway, to make a long story short... he gave me a solution on how to do it and I forgot to post it as promised:

Using WPF for Business Applications

Wednesday, March 12, 2008 3:14:38 AM (GMT Standard Time, UTC+00:00)

Here is the presentation that I did today at the Microsoft office in Minneapolis today.  It was a segment on real world WPF.  It discusses how WPF isn't just about the cool North Face demo or the New York Times Reader, but there is advantages for using it in every day enterprise applications.  The presentation and source can be found below.  The demo's include sample WPF implementations for Authorization, Application Activity Monitoring and Highlighted Search similar to what you experience when you search in Outlook.  None of the examples are currently at production quality, but with a little polish they could get there.

Authorization UI

Demonstrates how to use attached properties on different UI elements to change whether they are hidden or disabled based on the users authorization for a specific UI Element.  The authorization UI is mocked up, but should give you an idea of how you can extend any UI Element with property metadata to help in the UI authorization scenario.

image

Application Activity Monitoring

Demonstrates how you can implement monitoring of the mouse and keyboard to determine if you should disable the UI of the application... in this implementation it will blur after 5 seconds of no activity.  This implementation still needs some work... you can still edit data after the screen has blurred and there is no login dialog which would pop up after the blur has taken place so you can resume work.

image

Highlighted Search

Similar to how Microsoft Outlook highlights the search term as you type, this implementation demonstrates a simplified version using a listbox.  There is a bunch of sample data... you can test it out with up to 10,000 records.  If you want to generate more seed data, you can use the seed data api that I put together.

image

Enumerating Exceptions using ForEach

Wednesday, September 05, 2007 4:51:50 AM (GMT Daylight Time, UTC+01:00)

Thought I would share this class I wrote the other day... it is a helper class that flattens the the Exception hierarchy and allows you to enumerate through the exceptions using ForEach. The following shows how the class is used.

 

EnumerableException items = new EnumerableException(ex);

 

foreach (EnumerableException item in items)

    System.Diagnostics.Debug.WriteLine(item.Exception.Message);

 

The following is the source: EnumerableException.zip

 

 

 

Example of deriving from a WPF User Control

Friday, June 22, 2007 3:24:31 PM (GMT Daylight Time, UTC+01:00)

I put together an example of how to derive from a base class for a WPF user control when the base class is found in the local assembly or a reference one.  If you want more detail on how to do the derivation you can read my previous post.

UserControlDerivationExample.zip (77.79 KB)

Using Word Automation to Update Word's Table of Contents

Wednesday, January 24, 2007 5:11:23 AM (GMT Standard Time, UTC+00:00)

This one took a while to figure out... so I thought I would post it for others.  So the problem was I am building a document using the Word Automation model within C#.  When I inserted text that had styles defined for the TOC (Header 1 for example) I couldn't get the new header text to appear in the TOC contents in my final document output.

My usual approach of recording a macro in Word and then reviewing the VBA code resulted in the followin:

Sub Macro2()
'
' Macro2 Macro
'
'

WordBasic.UpdateTableOfContents
End Sub

But there was no indication of how to "Update entire table", like the option shown in the UI.  No matter what I indicated it was always the same code as shown above.

I saw other approaches online that cycled through the Fields collection and called update, but again there was not option for "Update Entire Table".  When I tried it, it didn't update the TOC with the header 1 text I had just added to the document.

After digging a around a bit more I finally figured it out:

Word.Application _wordApp;

internal void UpdateTOC()
{
//cycle through all of the TOC's and Update them
foreach (Word.TableOfContents contents in _wordApp.Application.ActiveDocument.TablesOfContents)
{
contents.Update();
}
}

Bitwise operations on Enum Flags

Friday, October 27, 2006 4:13:37 AM (GMT Daylight Time, UTC+01:00)

A number of times I find my self looking up how to assign multiple enum values to a property to support options semantics on a control.  So to make it easier to find I am TidByteing it here. An example of this is the classic Border property that allows you to identify where you want lines drawn.  For example, lets say we have an enum that looks like this (note that I am using powers of two so they can easily be combined):

public enum Border

       {

           Top = 1,

           Bottom = 2,

           Left = 4,

           Right = 8

       }

And you want to indicate to draw only on the left and right borders would look something like this:

ctl.Border = Border.Left | Border.Right

To make this work you need to add the FlagsAttribute to the enum:

[Flags()]
public enum Border

 

Then to check to see if a flag is set you would do the following:

if ((propertyName & Border.Left) == Border.Left)

        //border left border needs to be drawn;

 

 

Problems with the Draw() on the IHMTLPainter interface

Friday, October 06, 2006 8:24:29 PM (GMT Daylight Time, UTC+01:00)

The project that I am working on right now we are making some customizations to the MSHTML editor that is hosted by Internet Explorer.  With that we have some custom tags that we need rendered in a specific way (for example wavy lines under misspelled words). 

This can be accomplished by a Binary Behavior that implements the IHTMLPainter interface.  When the behavior is loaded, MSHTML checks to see if the IHTMLPainter interface is supported, if it is supported it will call the appropriate methods on the interface to render the tag. 

The problem I was having was the Draw method was never called.  I could attach the behavior to the body tag and it would render the graphics I wanted, but as soon as a moved it to a span it wouldn't call the draw.  After a couple of hours playing around with it I finally stumbled on to the fact that if the span has a height and width set (even to 0) the Draw() method will be called.  I didn't find this documented anywhere so I thought I would post it for others.

Learn more about binary behaviors here.

Stop Watch Wrapper

Tuesday, September 19, 2006 4:45:06 AM (GMT Daylight Time, UTC+01:00)

With .NET 2.0 they introduced the StopWatch class which can be used to time the execution of your code to a very high resolution.  Here is a wrapper to help package it up.

public class PerformanceTimer : IDisposable

    {

        Stopwatch _stopwatch = new Stopwatch();

        private static string _elapstedTime;

        public PerformanceTimer()

        {

            PerformanceTimer._elapstedTime = string.Empty;

            _stopwatch.Start();

        }

        public static string ElapstedTime

        {

            get { return _elapstedTime; }

            set { _elapstedTime = value; }

        }

        public void Dispose()

        {

            _stopwatch.Stop();

            TimeSpan ts = _stopwatch.Elapsed;

 

            _elapstedTime =

                    String.Format("{0:00}:{1:00}:{2:00}.{3:00}",

                    ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);

        } 

    }

The class is used in the following manner:

using(new PerformanceTimer())

{

    //Execute code you are timing here.

 

}

 

string result = PerformanceTimer.ElapstedTime;

Creating Seed Data - Updated

Tuesday, September 19, 2006 4:31:38 AM (GMT Daylight Time, UTC+01:00)

I did another update to my SeedData class, it now supports the creation of:

  • Job Titles
  • Addresses
  • States
  • Zip Codes
  • County

The city, states and zip codes all match appropriately, in other words if you request a random zip for example, it will return the zip with the related City, State and county.  The data is older (from the 1990 census) but it works great for generating real looking dummy data. 

The following code shows how to use the class

//Load external Seed Data files that aren't automatically loaded (check src to see which are)

SeedData.LoadSeedData("Titles", string.Format(@"{0}\SeedData_JobTitles.txt", Application.StartupPath), 0);

SeedData.LoadSeedData("Address", string.Format(@"{0}\SeedData_Addresses.txt", Application.StartupPath), 0);

 

PersonTableGateway gw = new PersonTableGateway();

 

using(new PerformanceTimer())

{

 

    for (int i = 0; i < 500; i++)

    {

        //create a record using the SeedData class

        Person p = new Person();

        p.FirstName = SeedData.GetFirstName().Trim();

        p.LastName = SeedData.GetLastName().Trim();

        p.NetworkUserID = p.FirstName.Substring(0, 1) + p.LastName;

        p.EMail = p.NetworkUserID.Trim() + "@sysknowlogy.com";

        p.Phone = SeedData.CreatePhone("612");

        p.Title = SeedData.GetRandomSeed("Titles");

        p.TitleDescription = "Description of the title goes here.";

 

        //create a street address

        p.Address1 = SeedData.Number(1000,9999) + " " + SeedData.GetRandomSeed("Address");

 

        ZipCode zip = SeedData.GetZip();

        p.Zip = zip.Zip;

        p.City = zip.City;

        p.State = zip.State;

        p.Branch = zip.City;

 

        gw.CreatePerson(p);

 

    }

}

 

 

 

SeedData1.zip (564.84 KB)

Automating the creation of Seed Data

Thursday, August 03, 2006 6:00:00 AM (GMT Daylight Time, UTC+01:00)
Just about every project I have had a need a need to create some seed data.  It’s a pain to come up with good seed data, especially around names of people.
 
The is the main purpose behind the SeedData.cs class that I wrote to create random names of people.  A quick search brought me to data gathered in the 1990 census.  There were over 80,000 last names and 5000 first names.  Pretty good mix for generating seed data.
 
I also added the capability to read in a file delimited by crlf to support creating other seed data based on lists in text files.
 
If you add any other seed data files, send the updates to me and I will post them.
 

SeedData.zip

Using the HtmlAgilityPack

Thursday, February 09, 2006 6:00:00 AM (GMT Standard Time, UTC+00:00)
Parsing Html with regular expressions can be an excruciating experience.  The cryptic syntax can take hours to get right.  What if you could treat malformed html like xml and use XPath to select the html tags you were looking for.
 
The HtmlAgility pack allows you to do just that.  It handles parsing the Html document and making navigable via XPath (implements the IXPathNavigable interface).  It also has facilities for downloading the HTML from a Url.  After the document has been parsed you have the capability to update the document.  In addition, there is examples of how to:
 
  • Harvest links from an Html document.
  • Convert the html document to text.
  • Convert the html document to xml.
  • Create an RSS feed from existing html content.
 
I recently used it to create an implementation for the RFC Standard 2557, also known as MIME Encapsulation of Aggregate Documents or Mht Web Archive files.  The same files you create in Internet Explorer by selecting File --> Save As and then modify the file type to be Web Archive. 
 
The implementation I wrote is based on this article Convert any URL to a MHTML archive using native .NET code that I found on CodeProject a while back.  The CodeProject implementation was written in VB.Net and used regular expressions heavily.  In addition, the first version of the implementation was file based and required all of the files to be written to disk before they were embedded into a single mht file.  The later version offers an in memory option so the files no longer need to be persisted to the disk. 
 
The implementation I wrote is based in C# and relies on the HtmlAgility pack to do the heavy lifting of parsing the Html for external resources.  I did have to use one regular expression to find the @import directives with in a style tag. 
 
Here is the code written to create a mht file:
 
string htmlLocation = "http://blogs.technet.com/justinbraun/archive/2005/11/07/413859.aspx";
 
WebResourceDownloader downloader = newWebResourceDownloader();
MhtDocument mht = newMhtDocument(htmlLocation, downloader);
string result = mht.CreateMht();
System.IO.File.WriteAllText("justinbraun.mht", result);
 
Below is an example capture of a web page using the implementation I wrote.  It's a snapshot of my brothers blog.
 
You will need to download the HtmlAgility library and then you can download the implementation for the Mht creator below:

Html2Mht.zip

Better way for debugging windows services

Saturday, January 28, 2006 6:00:00 AM (GMT Standard Time, UTC+00:00)
For the longest time I was using System.Diagnostics.Debugger.Launch() with #If DEBUG compiler directive… this allows me to step into services when I am in debug mode. 
 
I found another approach while perusing MSDN the other day…  you can set the debugger to launch and attach automatically with out having code in your application to do so. 
 
 

Creating Large Files

Tuesday, January 24, 2006 6:00:00 AM (GMT Standard Time, UTC+00:00)
Right now I am working on a extended version of the application updater block for a client, as part of the testing I needed to create some large files to test the download progress dialog.  It can actually be done pretty easily after spending a couple of minutes poking around the BCL.
 
System.IO.FileStream fs = new System.IO.FileStream(@"c:\test2.bin", System.IO.FileMode.Create);
fs.SetLength(1000000 * 1000); //creates about a gig
fs.Close();
 
Warning: Be care that you have enough space on your drive when you do this, you may run into some problems if you don’t.

C# Implementation for Balanced Matches

Saturday, December 24, 2005 6:00:00 AM (GMT Standard Time, UTC+00:00)
What is Balanced Matching?  Balanced matching can be used to parse expressions embedded into one another, or can be used to match an open token to its closing counterpart.  Examples of this would be parenthesis matching in expressions, HTML Tag matching and pairing tokens for a template engine.  For example, lets say you had the following expression:
 
(z(a + b(x + y)))
 
when parse it should look something like this:
 
x + y
a + b(x + y)
z(a + b(x + y))
 
 
After spending sometime trying to get a balanced match regular expression to support embedded expressions N levels deep, I ended up writing my own C# implementation. If you are interested in pursuing the Regular Expression implementation, these two articles reference an approach, but didn't give me the solution I was looking for (support for multiple levels of embedding).  You can find a pretty good article on Balanced Matching and regular expressions at Wes' Puzzle Blog, it looks like it was based on this article found on the BCL Team log. 
 
 
The approach I used was to mix Regex and a Stack class from the System.Collections namespace.  The basic logic is as follows:
 
  1. Use Regex to capture all of the "Start" tokens and "End" tokens.
  2. Sort them by the position they are found in the text. 
  3. Loop through all of the tokens:
    a) If the token is a start token, push it onto the stack.
    b) else pop it off of the stack, and use as the matching end token
  4. At the end if the stack is not empty, there is a token mismatch (all start tokens must have a matching end token).
 
You can download BalancedMatching.zip to see the implementation.
 
Note: You can also use regular expressions as Start and End tokens. 

Code Zone Launch

Wednesday, June 08, 2005 6:00:00 AM (GMT Daylight Time, UTC+01:00)
Checkout Code Zone.  It is being launched at Tech Ed this week.  It allows you to personalize the aggregation of information that is targeted at topics you are interested in.  The content has a rating system, so that you can see what others that have the same interest are saying about the content. 

Updated IPBlog

Saturday, November 27, 2004 6:00:00 AM (GMT Standard Time, UTC+00:00)
Updated the InfoPath form to pull the categories for the post. Here is what you will need to do:
 
  1. Open IPBlogV2.xsn in design view.
  2. Select Tools -> Data Connections
  3. Select GetCategories, Click Modify.
  4. Enter the url to the InfoPathService.asmx file on your web dasBlog website, click next.
  5. Select "GetCategores", click next.
  6. Complete the remaining dialogs using the defaults.
  7. You should be ready to open the IPBlogV2.xsn and complete your first post.
 
You can find the new download for IPBlogV2.xsn below.  If you need the entire code base you can get it here.

IPBlogv2.zip

InfoPath Blog (IPBlog) Editor for dasBlog

Thursday, November 25, 2004 6:00:00 AM (GMT Standard Time, UTC+00:00)
This version of IPBlog editor is a little more extensive then the original version I wrote back in November 2003.  I have to admit I am impressed with the changes they made to in Office 2003 Service Pack 1 for InfoPath. Some of those changes included the ability to handle file attachments, defaulting date values without using javascript and better integration with managed code.
 
This release of the IPBlog editor for dasBlog took advantage of updates in SP1 and added some additional functionality that wasn't there in the first version.
 
The improvements include:
  • New Web Service to support entries hosted in dasBlog.
  • Ability to attach files to the post.
  • Ability to include graphics in-line with text and have them published as part of the post.
  • Post to a specific category.  Unfortunately right now you have to know the categories as it is textbox for posting, but I will follow it up with the ability to select the categories you want to post to through a list box or a grouping of check boxes.
 
You can download the zip of the project at the bottom of the post.  I included binaries and source for the IPBlog service.  It was compiled against version 1.6.4121.1 of dasBlog.
 
Quick Start
  1. Copy the InfoPathService.asmx to the root of your dasBlog site.
  2. Copy Sysknowlogy.dasBlog.dll and newtelligence.DasBlog.Web.dll to your bin directory.
  3. Configure the IPBlog.xsn to post to the url of the InfoPathService.asmx:
    1. Open IPBlog.xsn in design view.
    2. Select Tools -> Data Connections
    3. Select Submit, Click Modify.
    4. Enter the url to the InfoPathService.asmx file on your web dasBlog website, click next.
    5. Select "Create Entry", click next.
    1. Make sure the Field or group option is selected, and enter the root node /my:InfoPathEntry as the selected parameter, click next.
    2. Complete the remaining dialogs using the defaults.
    3. You should be ready to open the IPBlog.xsn and complete your first post.

IPBlog.zip

Basic Assembly Configuration

Sunday, November 14, 2004 6:00:00 AM (GMT Standard Time, UTC+00:00)
Here is simple implementation that supports a configuration file for an assembly.  I say simple because it only takes in account the configuration for the dll, it does not include the app, machine or other configs that make up the configuration of an application.
 
The AssemblyConfiguration class by default requires the dll that contains the AssemblyConfiguration and the dll that you want to support configuration are in the same directory.  You can override the behavior by using the constructor that takes the path to the assembly and a reference to the assembly. 
 
    1 using System;
    2 using System.Configuration;
    3 using System.Reflection;
    4 using System.Xml;
    5 using System.Windows.Forms;
    6  
    7  
    8 using TidByte.Activation;
    9  
   10  
   11 namespace TidByte.Configuration
   12 {
   13     /// <summary>
   14     /// Supports the loading of a configuration file for a .dll
   15     /// </summary>
   16     public class AssemblyConfiguration
   17     {
   18         string        _cfgFile = string.Empty;
   19         XmlDocument    _doc = null;
   20         string        _appPath = string.Empty;
   21  
   22         /// <summary>
   23         /// Default constructor that assumes that the the TidByte.Configuration dll is located in the same directory as the
   24         /// .dll that requires the AssemblyConfiguration implementation.  If you need to override this behavior, you will need to
   25         /// use the constructor that supports the identification of a path of where the dll that required configuration is located.
   26         /// </summary>
   27         public AssemblyConfiguration() : this (AppDomain.CurrentDomain.BaseDirectory, Assembly.GetCallingAssembly()) {    }
   28  
   29         /// <summary>
   30         /// Supports identifying the path of the assembly that requires configuration.  Defaults to the calling assembly as the assembly
   31         /// that requires configuration.
   32         /// </summary>
   33         /// <param name="appPath">Path to assembly to requires identification</param>
   34         public AssemblyConfiguration(string appPath) : this (appPath, Assembly.GetCallingAssembly()) {    }
   35  
   36         /// <summary>
   37         /// Supply the path, and assembly that requires configuration.
   38         /// </summary>
   39         /// <param name="appPath"></param>
   40         /// <param name="asm"></param>
   41         public AssemblyConfiguration(string appPath, Assembly asm)
   42         {
   43             //set the dll config file name
   44             _cfgFile = asm.CodeBase + ".config";
   45  
   46             //load it into the DOM
   47             _doc = new XmlDocument();
   48             _doc.Load(new XmlTextReader(_cfgFile));
   49  
   50             _appPath = appPath;
   51  
   52         }
   53  
   54  
   55         /// <summary>
   56         /// Returns the configuration according to the configSection defined in the config file
   57         /// </summary>
   58         /// <param name="section">Section of the config file that supports the configuration settings.</param>
   59         /// <returns></returns>
   60         public object GetConfig(string section)
   61         {
   62             //gets the config section that is requested
   63             XmlNode sectionNode = _doc["configuration"]["configSections"].SelectSingleNode("./section[@name='" + section + "']");
   64  
   65             if (sectionNode != null)
   66             {
   67                 //get the type of the object to create
   68                 string typeDef = sectionNode.Attributes["type"].Value;
   69                 IConfigurationSectionHandler cfgHandler = (IConfigurationSectionHandler)ActivationHelper.ActivateFromPath(typeDef, _appPath);
   70  
   71                 XmlNode sectionXml = _doc["configuration"].SelectSingleNode("./" + section);
   72  
   73                 //Call the interface
   74                 return cfgHandler.Create(sectionNode.ParentNode,null,sectionXml);
   75  
   76             }
   77  
   78             return null;
   79         }
   80  
   81     }
   82 }
 
The next code snippet shows the implementation for how the ActivationHelper works:
    1 using System;
    2 using System.Reflection;
    3 using System.Windows.Forms;
    4  
    5 namespace TidByte.Activation
    6 {
    7     /// <summary>
    8     /// Summary description for ActivationHelper.
    9     /// </summary>
   10     public class ActivationHelper
   11     {
   12         const int AssemblyName    = 1;
   13         const int ClassName        = 0;
   14  
   15         //default constructor
   16         public ActivationHelper() {}
   17  
   18         /// <summary>
   19         /// Private helper function to assist in run-time activations. Returns
   20         /// an object from the specified assembly and type.
   21         /// </summary>
   22         /// <param name="assembly">Name of the assembly file (w/out extension)</param>
   23         /// <param name="typeName">Name of the type to create (namespace must be included)</param>
   24         /// <returns>Instance of the type specified in the input parameters.</returns>
   25         public static object Activate(string assembly, string typeName)
   26         {
   27             try
   28             {
   29            
   30                 return AppDomain.CurrentDomain.CreateInstanceAndUnwrap(assembly, typeName);
   31             }
   32             catch( Exception ex)
   33             {
   34                 throw ex;
   35             }
   36         }
   37  
   38         /// <summary>
   39         /// Private helper function to assist in run-time activations. Returns
   40         /// an object from the specified assembly and type, as defined in a standard config
   41         /// </summary>
   42         /// <param name="arArgs"> namespace.class (comma seperated),assembly filename</param>
   43         /// <returns>Instance of the type specified in the input parameters.</returns>
   44         public static object Activate(string assm)
   45         {
   46            
   47             string[] arArgs = assm.Split(new char[]{','});
   48             string assmname = arArgs[AssemblyName].Trim();
   49             string clsname = arArgs[ClassName].Trim();
   50                    
   51  
   52             //Dynamically load the deployment handler from the config file
   53             return Activate(assmname, clsname);
   54  
   55         }
   56  
   57         /// <summary>
   58         /// Activate a specific assembly from a specified path
   59         /// </summary>
   60         /// <param name="assm"></param>
   61         /// <param name="path"></param>
   62         /// <returns></returns>
   63         public static object ActivateFromPath(string assm, string path)
   64         {
   65  
   66            
   67             string[] arArgs = assm.Split(new char[]{','});
   68             string assmname = arArgs[AssemblyName].Trim();
   69             string clsname = arArgs[ClassName].Trim();
   70  
   71             Assembly lib = Assembly.LoadFrom(path + assmname + ".dll");
   72  
   73             return lib.CreateInstance(clsname);
   74         }
   75     }
   76 }
 
And last I am using a modified version of Craig Andreas "The last configuration handler I'll ever need". 
 
    1 using System;
    2 using System.Reflection;
    3 using System.IO;
    4 using System.Configuration;
    5 using System.Xml;
    6 using System.Xml.Serialization;
    7 using System.Xml.XPath;
    8 using System.Windows.Forms;
    9  
   10  
   11 using TidByte.Activation;
   12  
   13 namespace TidByte.Configuration
   14 {
   15  
   16     /*
   17     * When you map a section of your configuration file to this handler, using an entry in your config file that looks like this:
   18  
   19         <configuration>
   20             <configSections>
   21                 <section name="MyStuff" type="<Namespace>.XmlSerializerSectionHandler, Exe/DllName"/>
   22             </configSections>
   23         </configuration>
   24        
   25         What happens is that any time you ask for the MyStuff section of the config file with code that looks like this:
   26         AppConfig.GetTypeSafeConfig("MyStuff");
   27  
   28         It will actually go look in the config file to figure out who knows how to deal with the “MyStuff” section.
   29         When it sees that it’s the XmlSerializerSectionHandler, it’ll create an instance of this object and call Create,
   30         passing it (via the section parameter) a reference to the relevant portion of the config file. In our case, it might look something like this:
   31             <configuration>
   32                 <!-- configSections element goes here -->
   33                 <MyStuff type="SomeNamespace.MyStuff, CraigsConfig">
   34                     <Foo>1.234</Foo>
   35                     <Bar>A bunch of information</Bar>
   36                 </MyStuff>
   37             </configuration>
   38  
   39         What my handler does is to look for the “type” attribute on this bit of XML, in the form “classname, assemblyname”.
   40         In this case, it’s a class called MyStuff in a namespace SomeNamespace. It then – and here’s the good bit –
   41         uses the XmlSerializer to initialize an instance of this type from the XML in the configuration file.
   42         The XmlSerializer is a very cool piece of the .NET libraries, and you should check it out if you haven’t already.
   43         Basically, you write a type like this:
   44  
   45             public class MyStuff
   46             {
   47                 private float foo;
   48                 private string bar;
   49  
   50                 public float Foo
   51                 {
   52                     get { return foo; }
   53                     set { foo = value; }
   54                 }
   55  
   56                 public string Bar
   57                 {
   58                     get { return bar; }
   59                     set { bar = value;
   60                 }
   61             }
   62  
   63         And the XmlSerializer takes care of turning it into the XML above and vice versa. There are even a bunch of
   64         attributes you can use to control the process.
   65        
   66         [XmlRoot("RootElemenet")]                - allows the naming of a root element
   67         [XmlElement("FileName")]                - overrides a property name
   68         [XmlAttribute("FileName")]                - stores a property as an attribute
   69         [XmlArray("CollectionName")]            - assigns a collection a name
   70         [XmlArrayItem("CollectionChildName")]    - assigns the children of the collection a name
   71  
   72         Calling:
   73  
   74         MyStuff ms = (MyStuff) AppConfigEx.GetTypeSafeConfig("MyStuff");
   75     */
   76  
   77     public class XmlSerializerSectionHandler :    IConfigurationSectionHandler
   78     {
   79         public object Create(
   80             object parent,
   81             object configContext,
   82             System.Xml.XmlNode section)
   83         {
   84             try
   85             {
   86                 //get the type that we need to create to support the configuration
   87                 XPathNavigator nav = section.CreateNavigator();
   88                 string typename = (string) nav.Evaluate("string(@type)");
   89  
   90                 //Get the location of this dll. We assume that all dll's loaded through the config are installed to a private
   91                 FileInfo fi = new FileInfo(Assembly.GetExecutingAssembly().Location);
   92                 Type t = ActivationHelper.ActivateFromPath(typename, fi.DirectoryName + @"\").GetType();
   93                 XmlSerializer ser = new XmlSerializer(t);
   94  
   95  
   96                 //xpath will select all nodes below the parent and use those to create the type;
   97                 return ser.Deserialize(new XmlNodeReader(section.SelectSingleNode("*")));
   98                
   99             }
  100             catch (Exception ex)
  101             {
  102                 Console.WriteLine(ex.ToString());
  103                 throw ex;
  104             }
  105         }
  106  
  107     }
  108  
  109 }

Impersonation on Different Threads in an ASP.Net Web application

Monday, March 08, 2004 4:05:49 AM (GMT Standard Time, UTC+00:00)
I had written some code to make some batch web service requests utilizing delegates.  One of the things we ran into was that any new threads created in the ASP.Net application we're being created under the ASPNet account rather then a service account we were using.  This caused some problems when we were accessing resources in our application.  The ASPNet account didn't have access to those areas.
 
I was surprised how easy it was to implement impersonation in the code.  The solution looked something like this.
 
In the Page_Load event:
 
//show who we are running as - should be customuser
System.Security.Principal.WindowsIdentity ident = System.Security.Principal.WindowsIdentity.GetCurrent();
Debug.WriteLine("Page_Load: " + ident.Name);
 
//retrieve the security token , and cache it in a static variable so it can be used by the other thread.
_intsecuritytoken = System.Security.Principal.WindowsIdentity.GetCurrent().Token;
 
//create the batch request object and submit a request
AsyncThreadRequest request = new AsyncThreadRequest();
request.Add(new SendReportRequestDelegate(this.FuncToRun));
request.SubmitRequest(true, 20);
 
The FuncToRun method looked like this:
 
privatevoid FuncToRun(string testval)
{
//show who we are running as (should be ASPNet)%
System.Security.Principal.WindowsIdentity ident =        System.Security.Principal.WindowsIdentity.GetCurrent();
Debug.WriteLine("FuncToRun() - Before Impersonation: " + ident.Name);
 
//create a new windows identity, with the cached security token from customuser
System.Security.Principal.WindowsIdentity windid = new System.Security.Principal.WindowsIdentity(_intsecuritytoken);
windid.Impersonate();
 
//show who we are running as - should be customuser
ident = System.Security.Principal.WindowsIdentity.GetCurrent();
Debug.WriteLine("FuncToRun() - Before Impersonation: " + ident.Name);
}
 
You can get the complete example here:
ImpersonateAsyncThread.csproj.zip (10.14 KB)

404 for any .aspx after installing IIS

Tuesday, December 23, 2003 5:41:55 AM (GMT Standard Time, UTC+00:00)

After setting up  IIS6 on my Windows 2003 Server I tried to run an ASP.NET application.  I was getting a 404 even though the aspx file existed.  If I hit any non ASP.NET extension (txt, xml, html, htm, asp) it worked fine.  I remapped the aspx files (Knowledge Base Article) with no luck.  After removing IIS and then reinstalling, making sure that I included ASP.NET in the IIS install it worked.

Copying and Compilation Errors during Solution Builds

Sunday, December 21, 2003 1:50:18 AM (GMT Standard Time, UTC+00:00)
The IDE can be irritating at times.  Do you ever get this compilation error…

Compiler Error Message: CS0006: Metadata file 'c:\windows\microsoft.net\framework\v1.1.4322\temporary asp.net files\xxxx\934254a0\5606a5c7\assembly\dl2\56405119\009facbd_39b0c301\xxxx.dll' could not be found

To fix this restart IIS.
 
If you get…
 
"Could Not Copy Temporary Files to the Output Directory" which seems to be a pretty popular message when working with multiple projects check out this KB article:
 
 
What has worked for me is to close the solution and compile the solution file with out opening the the VS.NET IDE…  Here is the contents of the .reg file that I use to compile using the context menu in Explorer (notice the framework paths are hard coded, you may need to modify).
 
[HKEY_CLASSES_ROOT\VisualStudio.Solution.7.1\Shell\Build Solution Debug]
 
[HKEY_CLASSES_ROOT\VisualStudio.Solution.7.1\Shell\Build Solution Debug\Command]

@="C:\\Program Files\\Microsoft Visual Studio .NET 2003\\Common7\\IDE\\devenv /rebuild debug \"%1\" /out .\build.txt"


 Note that it will create a build.txt file in the same directory to allow you to view compilation errors, which of course, I am sure there will not be any :-\.

Defining Interfaces

Wednesday, October 15, 2003 4:15:24 AM (GMT Daylight Time, UTC+01:00)
This is from a C# primer presentation for fellow consultants at the company I work for. I have been concentrating on the finer points of interfaces and how they work, what there purpose is and some example implementations.

When I had implemented interfaces before I always assumed that if you declared an interface like:
interface IFix { void Heal(); }

The following two implementations of void Heal() would meet the interface contract in the same way:





    class Person : IFix 
{
public void Heal()
{
Console.WriteLine("Person::Save");
}
}
equal to:
    class Person: IFix 
{
//explicit
void IFix.Heal()
{
Console.WriteLine("Patient::Save");
}
}

Well it's not a stylistic difference like I first thought. Take a look at these examples for yourself:

ISemantics.cs

using System;

namespace InterfaceSemantics
{
    interface IFix { void Heal(); }

    class Person : IFix
    {
        public void Heal()
        {
            Console.WriteLine("Person::Save");
        }
    }

    class Doctor : Person
    {
        public new virtual void Heal()
        {
            Console.WriteLine("Doctor::Save");
        }
    }

    class Surgeon : Person, IFix
    {
        //explicit

        public new virtual void Heal()
        {
            Console.WriteLine("Surgeon::Save");
        }

    }

    class Patient : IFix
    {
        //explicit
        void IFix.Heal()
        {
            Console.WriteLine("Patient::Save");
        }            
    }
}

form1.cs (partial - Form1_Load)

private void Form1_Load(object sender, System.EventArgs e)
        {

            Doctor doc = new Doctor();
            doc.Heal();

            Person basecls = (Person)doc;
            basecls.Heal();

            //This impl you can access the interface through the class.
            Surgeon cutter = new Surgeon();
            cutter.Heal();

            //still works
            IFix isurg = (IFix)cutter;
            isurg.Heal();

            //Using explicit declaration of the interface
            Patient pat = new Patient();
            IFix iface = (IFix)pat;
            iface.Heal();

           //This impl needs a request for the interface
            //Here we get a compilation error:
            //        'InterfaceSemantics.Patient' does not contain a definition for 'Heal'
            //
            //for code:
            
            Patient pat = new Patient();
            pat.Heal();
       
            //because we used void IFix.Heal() as the prototype to fulfill the interface contract
           

        }


Exposing Additonal Type Information in the Debugger

Sunday, October 12, 2003 10:11:24 PM (GMT Daylight Time, UTC+01:00)

Use mcee_cs.dat (found at C:\Program Files\Microsoft Visual Studio .NET\Common7\Packages\Debugger\) to expose addtional type information in the debugger. If you have used C++ in the past, this is the equivalant to the autoexp.dat. There is very little documentation in MSDN. However it is pretty easy to add type information to the mcee_cs.dat file. By doing this, additional information will be added to the watch / locals and debugger ToolTips during debugging. The mcee_cs.dat. changes are only read at startup, so if you make changes while the IDE is open, you will need to shut it down and restart it.

Unfortunately the mcee_cs.dat is very limited. You may only call properties of the type ( no methods ). I also found that VSTweak has the ability to edit the mcee_cs.dat file (although the implemenation is still a bit rough).  I would suggest editing by hand. You can find the VS Power Toys (which includes VSTweak) for the IDE on GotDotNet.