# Saturday, September 30, 2006

For those who are friends or family or just plain curious we are moving to Germany in a couple of weeks.  I have setup a new website and blog for sharing stories and photos about our life and experiences.  It is a family site and several of us will be making use of it.  If you are curious you can check out the new blog at: http://www.isaksonfamily.net/Blogs/tabid/53/BlogID/5/Default.aspx

I now have 3 blogs.  Option Strict for my techie life, the new family blog, and my Jesus blog for sharing thoughts on the Church and spiritual life.  I decided to aggregate all of my blogs on a single page with a single feed over on the new family site for anyone who cares to follow all 3 in one place.

Come see us in Germany.  We look forward to making new friends and seeing old ones while we are there.

Family | Life
Saturday, September 30, 2006 12:20:58 AM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [3]  | 
# Friday, September 29, 2006

Some of you are requesting example applications that are using the PageFlow tools.  Please be patient.  I am working out bugs in the project on a real world rewrite of the Pets Best enrollment process.  Until I have it working well enough a robust example application is not possible.  There are still some aspects of workflow that are not completely clear and thus result in some challenges in the toolkit.  It is mostly working.  The page flow aspects are working great to drive navigation.  I am now pushing through the remaining challenges with sending objects (data) from a workflow back to the application in a way that will work consistently with persisted workflows.

Friday, September 29, 2006 5:35:53 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, September 26, 2006

In a few weeks I will be starting a new job in Germany.  As a result I have a couple large items I need to sell.  Contact me if you are interested.

1997 Suburban 1500 : 82,500 miles - $7750 (Boise)


See photos at: http://www.flickr.com/photos/14596724@N00/sets/72157594301357769/detail/

Excellent Condition
Seats 9
4WD shift on the fly
Electric everything
Rear air/heat!
CD / Cassette

Beautiful 2066 sq. ft. house : $229,000


MLS ID: 98263218

4+ bedrooms
3 full bathrooms
excellent neighborhood
walk to schools / parks
cheap utilities

3668 N. Tee Ave, Boise, ID  83704

Tuesday, September 26, 2006 7:52:42 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [3]  | 
# Tuesday, September 19, 2006
After a couple months of being sidetracked on a different project I am now back on the ASP.NET Page Flow project again.  I am pleased to announce that I also have a CodePlex project for it.  I encourage everyone who is interested to join the project, get the code and start making suggestions and contributions.  I will be putting it to its first use on the Pets Best Enrollment process.  Over the next 4 weeks I will be working with the Pets Best team to perfect the starter kit to a point where it can be trusted for a production release with their Pet Insurance enrollment process.
Tuesday, September 19, 2006 5:21:42 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
# Tuesday, September 05, 2006

Today I received an update to a suggestion I made to Microsoft on April 8, 2005.  http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=102180  That was before Beta 2 of Whidbey if memory serves me right.  My suggestion was to add features to the Membership API that would allow for a user to be renamed.  There are many scenerios where we choose to use the full Email address of a user as their username instead of having a seperate username.  This works great until the user changes their Email address.  (Another reason to have a permanent address that is not affiliated with your ISP)  I need to be able to allow users to update their Email address and thus their username/userid. 

Apparently my suggestion has been considered for Orcas, but isn't going to make it.  Still, I thought it was good that it was at least considered.

In the meantime, I have found that I can rename a user by directly updating the membership database.  Microsoft cautions against this when using multiple stores for Membership, Roles, and Profile data because user + application is the identity that is shared across disparate stores.  It is my opinion that they should have used a unique identifier instead.  The MembershipUser supports such an identity already.  Similiarly I wish Windows authenticated users and forms authenticated users shared a common data type for their identities.  I believe Microsoft would say that indeed they do and this is the string type that the username or User.Identity.Name.  For my purposes I created a database table to map a GUID to Windows identities so that I could normalize all of my UserId's to be unique and meaningless.  A Email address as a username carries meaning.  That makes a bad primary data key in many scenarios because of the lack of good support for renaming.  Keys should never need renaming.  Even a Windows UserId can change when its based on a persons name and their legal name changes.

What do you all think?

 

Tuesday, September 05, 2006 9:35:09 PM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
# Friday, September 01, 2006

Yesterday I was faced with a scenario that I have encountered many times before.  I am developing a system in which I will be tracking things like articles and putting them into categories.  I like my code to follow common patterns of abstraction and Domain design models.  Users of the dataset get change tracking for free.  Its time for the Domain Object community to get that as well.

The scenario:  Domain object Article has a property called Categories that is a collection (think List<Category>).  The persistence medium is a database that has 3 tables for articles, categories, and an article to category linking table.  The UI must show all possible categories, indicate the ones the article is already in and handle changes by the user.

The shortcut:  In the past my solution to this scenario was to simply remove the article from all categories and then add it back to the ones that were selected.  That solution is very simple because you never have to worry about which categories the article was in before the update.

The ChangeAwareList<T>:  As I pondered my previous shortcut solution to this problem I concluded that it was time to do it right.  After all, some systems will have a problem if you are always recreating the article to category mappings.  At a minimum the database probably has to re-index the data that may have already existed, but was just deleted and recreated.  Also, the linking table may have other data in it that is being used elsewhere and the shortcut solution could cause problems in those scenarios.  What is needed is a Domain class that can track the changes to a collection.  Note, this is not for tracking changes within an object.  The would be handled with a base class or an interface that tracks the state of an object.  ChangeAwareList<T> is about tracking the state of a collection.

ChangeAwareList<T> inherits from List<T> and adds support for change tracking.  It can be used anywhere a List<T> would be used.  In my case I created a Categories class that simply inherits from ChangeAwareList<T>.     public class Categories : ChangeAwareList<Category> {}

In my data access layer my Get operations load up the collection, turn on the change tracking, then hand it to the business layer.  When the business layer hands back the collection it simply deletes all the items in the RemovedItems collection and adds all the items in the AddedItems collection.  Anything that did not change is left alone.     ChangeAwareList<T> allows you to work with the original collection, the new items, the deleted items, or the updated collection.

Here is the code for anyone interested.

  public class ChangeAwareList<T> : List<T>
   {
      List<T> _addedItems;
      List<T> _removedItems;
      bool _trackChanges;

      public ChangeAwareList()
      {
         _trackChanges = false;
         _addedItems = new List<T>();
         _removedItems = new List<T>();
      }

      #region Clear
      public new void Clear()
      {
         if (_trackChanges)
         {
            _removedItems.AddRange(base.FindAll(AlwaysTrue));
         }
         base.Clear();
      }
      #endregion
     
      #region Remove
      public new bool Remove(T t)
      {
         if (_trackChanges)
         {
            _removedItems.Add(t);
         }
         return base.Remove(t);
      }
     
      public new void RemoveAt(int index)
      {
         if (_trackChanges)
         {
           _removedItems.Add(base[index]);
         }
         base.RemoveAt(index);
      }

      public new void RemoveRange(int index, int count)
      {
         if (_trackChanges)
         {
            _removedItems.AddRange(base.GetRange(index,count));
         }
         base.RemoveRange(index, count);
      }
     
      public new void RemoveAll(Predicate<T> match)
      {
         if (_trackChanges)
         {
            _removedItems.AddRange(base.FindAll(match));
         }
         base.RemoveAll(match);
      }
      #endregion

      #region Add Methods
      public new void Add(T t)
      {
         if (_trackChanges)
         {
            _addedItems.Add(t);
         }
         base.Add(t);
      }
     
      public new void AddRange(IEnumerable<T> collection)
      {
         if (_trackChanges)
         {
            _addedItems.AddRange(collection);
         }
         base.AddRange(collection);
      }
      #endregion

      #region Insert
      public new void Insert(int index, T t)
      {
         if (_trackChanges)
         {
            _addedItems.Add(t);
         }
         base.Insert(index, t);
      }

      public new void InsertRange(int index, IEnumerable<T> collection)
      {
         if (_trackChanges)
         {
            _addedItems.AddRange(collection);
         }
         base.InsertRange(index, collection);
      }
      #endregion
 
      #region Change Tracking
      public List<T> AddedItems
      {
         get
         {
            return _addedItems;
         }
         set
         {
            _addedItems = value;
         }
      }

      public List<T> RemovedItems
      {
         get
         {
            return _removedItems;
         }
         set
         {
            _removedItems = value;
         }
      }

      public List<T> OriginalItems
      {
         get
         {
            List<T> original = base.FindAll(AlwaysTrue);

            foreach (T t in _addedItems)
            {
               original.Remove(t);
            }
            foreach (T t in _removedItems)
            {
               original.Add(t);
            }
            return original;
         }
      }

      private bool AlwaysTrue(T t)
      {
         return true;
      }

      public bool EnableChangeTracking
      {
         get
         {
            return _trackChanges;
         }
         set
         {
            _trackChanges = value;
         }
      }
      #endregion


   }

 

Friday, September 01, 2006 8:50:31 AM (Mountain Daylight Time, UTC-06:00)  #    Disclaimer  |  Comments [1]  |