Monday, November 02, 2009
Its "Dyslexia Awareness Week" here in the UK, and as a person that benefits from being a Dyslexic developer, I thought I should highlight the specific strengths to programmers of being dyslexic...
All of the benefits are due to a neurological difference that presents as a larger right-hemisphere in the brain and many more neural connections are formed than is normally found. While this can make it difficult for others to follow the actual thought process the benefits outweigh the cost of this and the random symbol orientation problems that most dyslexic people suffer from:
- 3-D visualization ability
- creative problem solving skills
- intuitive people skills
- visually interpreting information in 3d while applying a 4th dimension, reasoning. (e.g. value, logic, action, purpose, possibility, personality, emotion, sentiment and action, etc.)
If only our education systems would take advantage of these differences...

Sunday, October 25, 2009
Well, nothing like hitting the ground running, my first job at SSW was to join the TFS Migration Team, it was a fun experience, let me tell you how it went.
Adam put a few guys together:
- Adam Cogan (Australia) – The team lead who checks everything and makes us follow the rules to better TFS.
- Eric Phan (Australia) – Created an excellent "Rules to a successful migration from TFS 2008 to TFS 2010 guide”
- Justin King (Australia) – Justin seems to play the part of devil’s advocate. I looked him up in the company directory and he is a previous employee…I guess you never really leave SSW.
- Me (Scotland) – The implementer
- Allan Zhou (Beijing) – My co-conspirator for the implementation
We started at 2:30am (GMT+1) on Saturday morning and we did it in 5 major steps:
- Backed up TFS 2008 databases (Some 14GB of data)
- Restored databases to new 64 bit server
- Installed TFS 2010 Beta 2 64 bit
- Run the Upgrade of 2008 data to 2010 Beta 2
- Tested the deployment
We completed the migration at 9:15am (GMT+1) on Saturday morning so all in the migration took just less than 7 hours.
Figure: Web Access – Working
Figure: Visual Studio - Working
Well done to the SSW team.
Well done also to the guys involved in the TFS team, the same migration from TFS 2005 to TFS 2008 was a much more painful experience taking days of work, but the guys from SSW made this process easy and straight forward…Preparation does that for a project…
A possible claim to fame: In addition we might have been the first company (SSW is a company of 52 employees and contractors) to migrate. So far I have not seen any blog posts about other companies migrating everything over to Beta 2. I am a TFS MVP and no-one on that list has posted about a migration yet (I can just imagine Justin King having another fit when he finds that out).
If you get a chance, check out SSW’s Rules. I am sure you will find something that will make you more productive and happier…

In the last 2+ years at Aggreko I have worked with Visual Studio 2008 Team Foundation Server, Office SharePoint Server 2007 and a number of WPF, Silverlight and ASP.NET projects.
There had been some discussion of a new role within Aggreko in the solution architecture arena. I also spoke to Adam Cogan who has the title “SSW Chief Architect and Microsoft Regional Director”…
This fortuitous communication, which turned into an interview, resulted in an offer from Adam Cogan of employment as a Senior Software Architect at SSW…
I got through the interview and I decided to take a role at SSW…
If you know of Adam, then you will know that he has rules and standards for everything. If you have not heard of him, then I suggest that you have a read of those rules and see what they are all about.
The first set of rules that I read was the Rules to better Email and they helped me be more productive even before I accepted the job…
Check out rule #32 in SSW’s “Rules To Being Software Consultants Working In A Team”
#32 Do you enjoy your job?
The expectation from Adam is:
- #1 is to put your heart into your job and enjoy yourself
- Get your Employee Responsibilities (Scheduled recurring events) done
- Improve SSW to a better place every week
- Improve yourself to better person every week
If you find yourself not enjoying your job this is not necessarily a bad thing. You should make a commitment to give it a go and try to make it work. When you have decided you are unhappy you should talk to your boss and figure out what is making you unhappy. The fact is that there are some jobs that you are not suited to. It is probably best for everyone that you start to think about moving on and trying something that may make you happier.
I totally agree with this and at Aggreko I was supported by many people. I spoke to my boss Andre Vermeulen about the things I was not happy with, and we came to an understanding, but it is difficult for a large company to move at the same pace that I do. I found working with SharePoint 2003 is really just unacceptable.
In my new role at SSW I will be tasked with:
- bringing SSW’s rules to European clients
- helping organisations be more proactive with the Visual Studio 2010 ALM offering,
- migrating TFS 2005 and TFS 2008 customers to the joys of TFS 2010
- enabling SSW to have 24 hour operations
On top of this I will be using SharePoint 2010 and CRM 2005 in order to implement intranets and CRM for clients.
Its going to be a fun ride, and if you want to take your company to the next step and you are in Europe, please contact me.
If you get a chance, check out SSW’s Rules. I am sure you will find something that will make you more productive and happier…

Tuesday, October 20, 2009
As Microsoft have separated Install with configuration, so I have separated my posts! You will need TFS2010 installed prior to the steps below.

This is my configuration experience...This wizard is excellent. If you had ever tried to install TFS in the past and it taken you a long time (took me 7 days the first time in 2005) Then you need to give this a go...

You can pick basic and it is...well...basic. It will install everything to the defaults.
I'm picking Advanced because I want to be able to select a pre-existing SQL Express instance...

You can enter a label if you want to have more than one TFS Configuration database in the same SQL instance.
If you are wanting to run on a network, maybe with an externally accessible URL, then you may need to pay attention to the security, but I don't really care for this install... 
If you want to ever be able to connect Visual Studio 2005 clients to the server you MUST remove the virtual directory as Team Explorer 2005 will not be able to anything but the default collection.
Ok, I have a default collection, but only because I am lazy...
All done, now to apply it.
No, wait, we need to check all of the system requirements!
Now, usually this is the time to break out a cup of team, and maybe have a siesta. Lets see how long it takes...
..30 seconds...
...50 seconds...
.. 1 minute...
..Whoa, that was less than 2 minutes for the whole process.
Just to prove that this whole process took less than 12 minutes, here is the beginning and end of the log file:
[Info @12:06:41.111] ====================================================================
[Info @12:06:41.183] Team Foundation Server 2010 Administration Log
[Info @12:06:41.186] Version : 10.0.21006.1
[Info @12:06:41.203] DateTime : 10/20/2009 13:06:41
[Info @12:06:41.203] Type : Configuration
[Info @12:06:41.206] Activity : Deploy
[Info @12:06:41.208] Area : ApplicationTier
[Info @12:06:41.216] User : DOMAIN\martihins
[Info @12:06:41.216] Machine : ED0919
[Info @12:06:41.229] System : Microsoft Windows NT 6.0.6002 Service Pack 2 (AMD64)
[Info @12:06:41.229] ====================================================================
... shortened ...
[Info @12:18:28.147] Ending the Install operation on the ApplicationTier tier.
Whoa, that was fast! Compared to previous versions I was done before I started, like crossing an international date line. Another one is... no documentation... nope, I didn't look at it once! I would not recommend this approach, at least have a look to make sure you are installing the correct version on the correct URL's and to learn what the terms are.
P.S. Visual Studio 2005 and Visual Studio 2008 any version without the Team Foundation Server 2010 compatibility pack WILL NOT CONNECT! The Visual Studio Team System 2008 Service Pack 1 Forward Compatibility Update for Team Foundation Server 2010 is available, but 2005 will not be available until RTM.
Visual Studio Team System 2008 Service Pack 1 Forward Compatibility Update for Team Foundation Server 2010
I should note that you should not complain about the limited support for 2005. Microsoft expects the install base to be less than 5% by the time Visual Studio 2010 is released, and they were not going to support it at all. That there is any support at all is due to the lobbying of the Team System MVP community and TAP customers and excelent communication with the product teams...
New in Visual Studio 2010 is the ability to install TFS on XP, Vista and Windows 7. You can use SQL 2008 Express, so no large overhead, and the Basic version you use for this does have the reporting and SharePoint requirement that the main install does. That does not mean that you can't upgrade later :)
Once you have TFS2010 installed you will need to configure it...
New logo, new install. Microsoft have changed the, lets face it, horrible install, and split it into two separate pieces. Install and Configuration.
First The Install: The only options are wither you install server and build... nice...



Total install time: 3 minutes (which includes the time to take these screenshots and save them)
Now that I have TFS2010 installed I will need to configure it...
I was recently contacted by Colin Mackay, the chairman of Scottish Developers about doing an interview with them. Colin has been pestering me for a while now to do some speaking engagements, but I am still not comfortable with that! (Yes, I am too chicken), so I capitulated…
My interview appears in the October edition of their newsletter and although I think I rambled a little, understatement of the year, I do think I came across ok, if a little scatter brained…
Monday, October 19, 2009
Visual Studio 2010 Beta 2 is now available on MSDN for download!

With 2010 comes new SKU's. Microsoft is trying to simplify the layout and features that you can get.
Visual Studio IDE now comes in these flavours:
· Microsoft® Visual Studio® 2010 Professional
· Microsoft® Visual Studio® 2010 Professional with MSDN
· Microsoft® Visual Studio® 2010 Premium with MSDN
· Microsoft® Visual Studio® 2010 Ultimate with MSDN

So no Team Edition, and bits of Team Suit has been split between Premium and Ultimate. I addition all of the editions above will include a Team Foundation Server CAL which will make licensing a lot simpler.
Although Premium and Ultimate will continue to be in the ALM space there are also other elements like the Test Elements and Lab management that are new for 2010 that also sit in this space with Team Foundation Server.
· Microsoft® Visual Studio® Test Elements 2010 with MSDN
· Microsoft® Visual Studio® Team Foundation Server 2010
· Microsoft® Visual Studio® Team Lab Management 2010
If you have not already heard there will be a Team Foundation Server Express product that is able to be installed on Vista and Windows 7.
Check out the new channel 9 videos:
10-4 Episode 33: Downloading and Installing Visual Studio 2010 Beta 2
How to create record and playback Test Cases in Visual Studio Beta2
Technorati Tags: ALM,Visual Studio ALM
Monday, August 31, 2009
Although this post is called Scale Transform Behaviour you could use any transform / animation in its place. The purpose is to have a slider control in a menu be able to alter the scale of any number of controls within MVVM views.
This behaviour allows you to add any Framework Elements to a list of attached controls by adding an attached property of GlobalScaleTransformBehaviour.IsScaled to your controls.
Public Class GlobalScaleTransformBehaviour
Private Shared sm_AttachedControls As List(Of FrameworkElement)
Public Shared ReadOnly IsScaledProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsScaled", GetType(Boolean), GetType(GlobalScaleTransformBehaviour), New UIPropertyMetadata(False, New PropertyChangedCallback(AddressOf GlobalScaleTransformBehaviour.IsScaledChanged)))
Private Shared sm_CurrentScale As Double = 1
Shared Sub New()
sm_AttachedControls = New List(Of FrameworkElement)
End Sub
Public Shared Function GetIsScaled(ByVal element As DependencyObject) As Boolean
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
Return element.GetValue(IsScaledProperty)
End Function
Public Shared Sub SetIsScaled(ByVal element As DependencyObject, ByVal value As Boolean)
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
element.SetValue(IsScaledProperty, value)
End Sub
Private Shared Sub IsScaledChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim itemToResize As FrameworkElement = TryCast(obj, FrameworkElement)
If (Not itemToResize Is Nothing) Then
If Object.Equals(e.NewValue, True) Then
sm_AttachedControls.Add(itemToResize)
itemToResize.LayoutTransform = New ScaleTransform(sm_CurrentScale, sm_CurrentScale)
Else
sm_AttachedControls.Remove(itemToResize)
itemToResize.LayoutTransform = New ScaleTransform(1, 1)
End If
End If
End Sub
End Class
As you can see, there is an attached dependency Boolean property defined with a PropertyChangedCallback. When the PropertyChangedCallback method is called we test to see if it is a True or False value and either add the control to a static list and set the current Transform, or remove the control from the list and reset the transform to 1.
This works grate and you can manipulate the list of controls at runtime by changing the dependency property.
<igWindows:TabItemEx
xmlns:igDP="http://infragistics.com/DataPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Hinshlabs.WpfHeatItsmDashboard"
xmlns:igWindows="http://infragistics.com/Windows"
xmlns:igDock="http://infragistics.com/DockManager"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
xmlns:igEditors="http://infragistics.com/Editors"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ic="clr-namespace:Microsoft.Expression.Interactivity.Core;assembly=Microsoft.Expression.Interactions"
x:Class="CallsView" x:Name="CallsView" MinWidth="30" MinHeight="50">
<igWindows:TabItemEx.Resources>
<local:NinjectDataProvider
x:Key="ViewModel"
d:IsDataSource="True" ObjectType="{x:Type local:CallsViewModel}"
/>
<local:DateTimeSecondsToBooleanConverter x:Key="DateTimeSecondsToBooleanConverter" />
</igWindows:TabItemEx.Resources>
<igWindows:TabItemEx.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded"/>
</igWindows:TabItemEx.Triggers>
<igWindows:TabItemEx.Header>
<igEditors:XamTextEditor Text="{Binding Source={StaticResource ViewModel},Path=Header, diag:PresentationTraceSources.TraceLevel=High}" />
</igWindows:TabItemEx.Header>
<DockPanel local:GlobalScaleTransformBehaviour.IsScaled="True" DataContext="{Binding Source={StaticResource ViewModel}}">
<Border DockPanel.Dock="Top" Background="LightGray" MinHeight="20">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource ViewModel},Path=IsLoading, diag:PresentationTraceSources.TraceLevel=High}" Value="False">
<Setter Property="Border.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Label Content="Loading data..." />
</Border>
<Border DockPanel.Dock="Top" Background="LightGray" MinHeight="20">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Source={StaticResource ViewModel},Path=IsSyncing, diag:PresentationTraceSources.TraceLevel=High}" Value="False">
<Setter Property="Border.Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Label Content="Syncing data..." />
</Border>
<igDP:XamDataGrid DataSource="{Binding Calls}" Theme="Office2k7Blue">
<igDP:XamDataGrid.Resources>
<Style x:Key="{x:Type igDP:DataRecordCellArea}" TargetType="{x:Type igDP:DataRecordCellArea}">
<Style.Triggers>
<DataTrigger Binding="{Binding DataItem.TypeOfCall, Converter={StaticResource DateTimeSecondsToBooleanConverter}, ConverterParameter=1}" Value="True">
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Green"/>
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</igDP:XamDataGrid.Resources>
<igDP:XamDataGrid.FieldSettings>
<igDP:FieldSettings AllowRecordFiltering="true" FilterEvaluationTrigger="OnCellValueChange" AllowSummaries="True" FilterOperatorDropDownItems="All" />
</igDP:XamDataGrid.FieldSettings>
<igDP:XamDataGrid.FieldLayoutSettings>
<igDP:FieldLayoutSettings AutoGenerateFields="true" FilterUIType="LabelIcons" />
</igDP:XamDataGrid.FieldLayoutSettings>
</igDP:XamDataGrid>
</DockPanel>
</igWindows:TabItemEx>
There is quite a lot of Wpf here, so I have highlighted the DockPanel to which the dependency has been applied. All we now need to do is provide a way to manipulate this value. We need to add a ScaleValue attached dependency property to our Behaviour that we can bind to our single or set of control controls.
Public Class GlobalScaleTransformBehaviour
Private Shared sm_AttachedControls As List(Of FrameworkElement)
Public Shared ReadOnly IsScaledProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsScaled", GetType(Boolean), GetType(GlobalScaleTransformBehaviour), New UIPropertyMetadata(False, New PropertyChangedCallback(AddressOf GlobalScaleTransformBehaviour.IsScaledChanged)))
Public Shared ReadOnly ScaleValueProperty As DependencyProperty = DependencyProperty.RegisterAttached("ScaleValue", GetType(Double), GetType(GlobalScaleTransformBehaviour), New UIPropertyMetadata(CType(1, Double), New PropertyChangedCallback(AddressOf GlobalScaleTransformBehaviour.ScaleValueChanged)))
Private Shared sm_CurrentScale As Double = 1
Shared Sub New()
sm_AttachedControls = New List(Of FrameworkElement)
End Sub
Public Shared Function GetIsScaled(ByVal element As DependencyObject) As Boolean
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
Return element.GetValue(IsScaledProperty)
End Function
Public Shared Sub SetIsScaled(ByVal element As DependencyObject, ByVal value As Boolean)
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
element.SetValue(IsScaledProperty, value)
End Sub
Private Shared Sub IsScaledChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim itemToResize As FrameworkElement = TryCast(obj, FrameworkElement)
If (Not itemToResize Is Nothing) Then
If Object.Equals(e.NewValue, True) Then
sm_AttachedControls.Add(itemToResize)
itemToResize.LayoutTransform = New ScaleTransform(sm_CurrentScale, sm_CurrentScale)
Else
sm_AttachedControls.Remove(itemToResize)
itemToResize.LayoutTransform = New ScaleTransform(1, 1)
End If
End If
End Sub
Public Shared Function GetScaleValue(ByVal element As DependencyObject) As Double
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
Return element.GetValue(ScaleValueProperty)
End Function
Public Shared Sub SetScaleValue(ByVal element As DependencyObject, ByVal value As Double)
If element Is Nothing Then
Throw New ArgumentNullException("element")
End If
element.SetValue(ScaleValueProperty, value)
End Sub
Private Shared Sub ScaleValueChanged(ByVal obj As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If Not Application.Current.Dispatcher.CheckAccess Then
Exit Sub
End If
sm_CurrentScale = e.NewValue
SyncLock sm_AttachedControls
For Each itemToResize In sm_AttachedControls.ToList
' Apply Tensform
itemToResize.LayoutTransform = New ScaleTransform(sm_CurrentScale, sm_CurrentScale)
Next
End SyncLock
End Sub
End Class
This value is stored so we can set new controls, and then applied to all of the currently attached controls. I have chosen to bind to a slider, but any way of passing in the required values is just fine.
<igRibbon:XamRibbonWindow x:Class="MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:igRibbon="http://infragistics.com/Ribbon"
xmlns:igEditors="http://infragistics.com/Editors"
xmlns:igWindows="http://infragistics.com/Windows"
xmlns:igDock="http://infragistics.com/DockManager"
xmlns:local="clr-namespace:Hinshlabs.WpfHeatItsmDashboard"
Title="Heat Itsm Dashboard" MinHeight="600" MinWidth="800" Icon="/Hinshlabs.WpfHeatItsmDashboard;component/HeatItsm.ico">
<igRibbon:XamRibbonWindow.Resources>
<local:NinjectDataProvider
x:Key="ViewModel"
ObjectType="{x:Type local:MainWindowViewModel}"
/>
</igRibbon:XamRibbonWindow.Resources>
<igRibbon:RibbonWindowContentHost DataContext="{StaticResource ViewModel}">
<igRibbon:RibbonWindowContentHost.Ribbon>
<igRibbon:XamRibbon local:XamRibbonBehaviour.IsEntryPoint="True" DockPanel.Dock="Top" AutoHideEnabled="True" Theme="Office2k7Blue" >
<igRibbon:XamRibbon.ApplicationMenu>
<igRibbon:ApplicationMenu RecentItemsHeader="{Binding Resources.RecentItemsHeader}" Image="/Hinshlabs.WpfHeatItsmDashboard;component/Images/heat.gif">
<igRibbon:ButtonTool Caption="Update" />
<igRibbon:ApplicationMenu.FooterToolbar>
<igRibbon:ApplicationMenuFooterToolbar>
<igRibbon:ButtonTool Command="{Binding ExitCommand}" Caption="{Binding Resources.ExitButtonCaption}"/>
</igRibbon:ApplicationMenuFooterToolbar>
</igRibbon:ApplicationMenu.FooterToolbar>
</igRibbon:ApplicationMenu>
</igRibbon:XamRibbon.ApplicationMenu>
<igRibbon:XamRibbon.Tabs>
<igRibbon:RibbonTabItem Header="{Binding Resources.Ribbon_HomeTab_Header}">
<igRibbon:RibbonGroup Caption="{Binding Resources.Ribbon_HomeTab_ViewsGroup_Caption}">
<igRibbon:ToolHorizontalWrapPanel>
<igRibbon:ButtonTool Caption="{Binding Resources.Ribbon_HomeTab_ViewsGroup_CallsViewButtonCaption}" Command="{Binding AddCallsViewCommand}" />
</igRibbon:ToolHorizontalWrapPanel>
</igRibbon:RibbonGroup>
<igRibbon:RibbonGroup Caption="{Binding Resources.Ribbon_HomeTab_OptionsGroup_Caption}">
<igRibbon:ToolHorizontalWrapPanel>
<igRibbon:ButtonGroup>
<igRibbon:ToggleButtonTool IsChecked="{Binding FickEnabled, Mode=TwoWay}" Content="{Binding Resources.Ribbon_HomeTab_OptionsGroup_Flick_ToggleButton_Caption}"/>
</igRibbon:ButtonGroup>
</igRibbon:ToolHorizontalWrapPanel>
<igRibbon:ToolHorizontalWrapPanel>
<igRibbon:ButtonGroup>
<Label Content="Scale" />
<Slider Minimum="0.5" Maximum="3" Width="200" local:GlobalScaleTransformBehaviour.ScaleValue="1" LargeChange=".5" SmallChange=".1" Value="{Binding Path=(local:GlobalScaleTransformBehaviour.ScaleValue),RelativeSource={RelativeSource Self}, Mode=TwoWay}">
</Slider>
</igRibbon:ButtonGroup>
</igRibbon:ToolHorizontalWrapPanel>
</igRibbon:RibbonGroup>
</igRibbon:RibbonTabItem>
</igRibbon:XamRibbon.Tabs>
</igRibbon:XamRibbon>
</igRibbon:RibbonWindowContentHost.Ribbon>
<AdornerDecorator>
<DockPanel>
<local:UpdateView DockPanel.Dock="Top" />
<igWindows:XamTabControl TabItemCloseButtonVisibility="Visible" TabStripPlacement="Top" ItemsSource="{Binding CallsViews}" SelectedItem="{Binding SelectedCallsView}" local:TabControlTimedBehaviour.IsTimedCycle="{Binding FickEnabled}" Theme="Office2k7Blue">
</igWindows:XamTabControl>
</DockPanel>
</AdornerDecorator>
</igRibbon:RibbonWindowContentHost>
</igRibbon:XamRibbonWindow>
As you can see I am heavily utilizing the Infragistics controls, but that would not affect this procedure. The result is the ability to smoothly scale your controls based on a global scale setting.
krsu46zvpt
Tuesday, August 25, 2009
You have probably heard me go on about Unity a couple of times:
I have been using what is now unity since the good old days (sooo not true, WPF is the Windows Forms killer, and good riddance) of WindowsForms and CAB (Client Application Block), but now there is a lightweight alternative: Ninject.
I decided on my latest project (a Wpf dashboard for HEAT ITSM) that I needed dependency injection. Whenever I start building a MVVM project I always end up needing some sort of dependency injection to keep everything nice and neat. It is only really needed once you get to a certain size and when you start wanting talk between ViewModels.
Anyway I was using a method of injecting my ViewModels into the Views using standard binding:
<igDock:ContentPane x:Class="SlaTodayView"
xmlns:igDP="http://infragistics.com/DataPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:WpfHeatItsmDashboard"
xmlns:igWindows="http://infragistics.com/Windows"
xmlns:igDock="http://infragistics.com/DockManager"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Header="Sla Today" MinWidth="30" MinHeight="50">
<igDock:ContentPane.Resources>
<ObjectDataProvider
x:Key="ViewModel"
ObjectType="{x:Type local:SlaTodayViewModel}"
/>
</igDock:ContentPane.Resources>
<igDP:XamDataGrid DataContext="{StaticResource ViewModel}" DataSource="{Binding Calls}" Theme="Office2k7Black" >
</igDP:XamDataGrid>
</igDock:ContentPane>
But once you move to dependency injection you do not want to keep all those fixed object definitions. These may become interfaces, or you may just want to replace, or dynamically replace, one of these types by a derived one at runtime.
That being the goal, we need some way to retrieve that type even in design mode. There is nothing worse than components or bits of components that make it difficult to work in both Visual Studio and Blend, and with the new binding features of Visual Studio 2010 for WPF 4 it will be even more important that your usage is as compatible as possible.
What I decided to do was create a custom DataSourceProvider, called the NinjectDataProvider that I could use instead of the ObjectDataProvider. This is the first version of that provider and it does nothing more than retrieve the type form the Ninject Kernel. Minimal changes to the WPF enable this:
<igDock:ContentPane x:Class="SlaTodayView"
xmlns:igDP="http://infragistics.com/DataPresenter"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:WpfHeatItsmDashboard"
xmlns:igWindows="http://infragistics.com/Windows"
xmlns:igDock="http://infragistics.com/DockManager"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Header="Sla Today" MinWidth="30" MinHeight="50">
<igDock:ContentPane.Resources>
<local:NinjectDataProvider
x:Key="ViewModel"
ObjectType="{x:Type local:SlaTodayViewModel}"
/>
</igDock:ContentPane.Resources>
<igDP:XamDataGrid DataContext="{StaticResource ViewModel}" DataSource="{Binding Calls}" Theme="Office2k7Black" >
</igDP:XamDataGrid>
</igDock:ContentPane>
As you can see, the only difference is highlighted above and shows the custom DataSourceProvider in action.
You can see from the image above that the designer capability is not affected with this actually loading from the database, nice!
So, what do we need to do to achieve this marvellous result. its actialy fairly simple, I got out my trusty reflector and found that there is really only one method to override.
Imports System.ComponentModel
Imports System.Threading
Public Class NinjectDataProvider
Inherits DataSourceProvider
Private m_objectType As Type
Public Property ObjectType() As Type
Get
Return Me.m_objectType
End Get
Set(ByVal value As Type)
If Not m_objectType Is value Then
m_objectType = value
Me.OnPropertyChanged("ObjectType")
If Not MyBase.IsRefreshDeferred Then
MyBase.Refresh()
End If
End If
End Set
End Property
Private Overloads Sub OnPropertyChanged(ByVal propertyName As String)
MyBase.OnPropertyChanged(New PropertyChangedEventArgs(propertyName))
End Sub
Protected Overrides Sub BeginQuery()
If m_objectType Is Nothing Then
Me.OnQueryFinished(Nothing, New InvalidOperationException("You must provide an ObjectType"), Nothing, Nothing)
End If
Dim result As Object
Try
result = Application.NinjectKernel.Get(m_objectType)
Me.OnQueryFinished(result, Nothing, Nothing, Nothing)
Catch ex As Exception
Me.OnQueryFinished(Nothing, ex, Nothing, Nothing)
End Try
End Sub
End Class
I do not yet need all the fancy features of Ninject yet so I have only implemented the bit that I need at the moment. If I am adding more (and get it working) I will blog about it in the future.
To get this working I needed to add an instance of an IKernel object to the “Application” file so I have a single Kernel instance through my application unless I want another, but this is a small price to pay and it could well have been done in the same way as the My.Unity.Resolve(Of Ninja) post I did on Unity.
Start your Ninja training today!
Friday, August 21, 2009
One of my colleagues is facing the maelstrom that is corporate blogjection and has become a geek with a blog. Have a heart as he is a poor under-paid support analyst who hits WAY above his pay grade.
Welcome Roddy… good first post on SQL Server Function to add working days on to a date, I always wanted to know how to do that!
Technorati Tags:
Blogging,
Personal
Thursday, August 20, 2009

Over the past week I have been reading the new book Silverlight 3 Programmer's Reference
from Wrox and I have found it one of the best books on Silverlight I have seen in a good while. It is concise without being boring and it provides a wealth of information on Silverlight 3.
And it is in Colour! I never would have thought that this would make such a difference, I don’t really know why I thought this as I hate looking at code in notepad, but it makes it much easier to read the code pages, both c#/vb and xaml.
Because I have been using WPF for a number of years this book is perfect for me, although this is a reference book, It has a nice layout that is conducive to both learning and reference.
Will I be hanging up my WPF hat and replacing it with a Silverlight one? Well, no… but Silverlight 3 is a big step forward…
Monday, August 17, 2009
I had previously created a Command Line Parser from Ray Hayes codeproject article Automatic Command Line Parsing in C#. I had adapted it to VB.NET and upgraded it to .NET 3.5 but I recently ran into the problem with wanting a single command prompt application to handle multiple processes and multiple parameters. This would allow you to group all of a particular tasks commands into a single application. With the advent of Power Shell this format is increasingly less relevant, but with the proliferation of Power Shell many people still prefer to use the good old command line.
So, staring from the original Command Line Parser v1.0 code I wanted to be able to add multiple commands, or even nest commands. The result is a nice simple commanding architecture conducive to creating multiple commands.

Using this model I can create a simple command…
Imports Hinshlabs.CommandLineParser
Imports System.IO
Imports System.Collections.ObjectModel
Imports System.Net
Public Class Demo1Command
Inherits CommandBase(Of Demo1CommandLine)
Private m_PortalLocation As Uri
Public Overrides ReadOnly Property Description() As String
Get
Return "demo 1 command demonstrates a sinle nested command"
End Get
End Property
Public Overrides ReadOnly Property Name() As String
Get
Return "Demo1"
End Get
End Property
Protected Overrides Function ValidateCommand() As Boolean
Return True
End Function
Public Overrides ReadOnly Property Title() As String
Get
Return "demo 1"
End Get
End Property
Public Overrides ReadOnly Property Synopsis() As String
Get
Return "demo 1 command"
End Get
End Property
Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
Get
Return CommandLine.Switches
End Get
End Property
Public Overrides ReadOnly Property Qualifications() As String
Get
Return String.Empty
End Get
End Property
Protected Overrides Function RunCommand() As Integer
Try
CommandOut.Warning("running Demo1")
Return -1
Catch ex As Exception
CommandOut.Error("Failed: {0}", ex.ToString)
Return -1
End Try
End Function
End Class
Or something more substantial:
Protected Overrides Function RunCommand() As Integer
Try
Dim x As New Proxies.MyApp.Configuration.ConfigurationServiceClient("BasicHttpBinding_IConfigurationService", m_PortalLocation.ToString)
x.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Delegation
Select Case CommandLine.Action
Case QuiesceAction.Offline
x.QuiesceSource(CommandLine.Source, CommandLine.Message, New TimeSpan(0))
Case QuiesceAction.Online
x.RestoreSource(CommandLine.Source)
End Select
CommandOut.Info("Source {0} has been made {1}", CommandLine.Source, CommandLine.Action.ToString)
Return 0
Catch ex As EndpointNotFoundException
CommandOut.Error("Unable to locate site. Check the value you selected for /Portal:{0}", CommandLine.Portal)
Return -1
Catch ex As Exception
CommandOut.Error("Failed: {0}", ex.ToString)
Return -1
End Try
End Function
If you are wondering where the variables come from, you can see form Demo1Command that a generic type of Demo1CommandLine is passed in. The application creates an instance of this which wraps the Ray Hayes parser to provide the values from Environment.CommandLine used on the shared methods on the CommandLineBase class.
''' <summary>
''' Created a command line object using the Environment.CommandLine information
''' </summary>
''' <typeparam name="TCommandLine">The concrete type of object to create</typeparam>
''' <returns>An instance of the object</returns>
''' <remarks></remarks>
Public Shared Function CreateCommandLine(Of TCommandLine As {New, CommandLineBase})() As TCommandLine
Return CreateCommandLine(Of TCommandLine)(Environment.CommandLine)
End Function
''' <summary>
''' Created a command line object using the Environment.CommandLine information
''' </summary>
''' <typeparam name="TCommandLine">The concrete type of object to create</typeparam>
''' <param name="CommandLine">The command line arguments to parse</param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function CreateCommandLine(Of TCommandLine As {New, CommandLineBase})(ByVal CommandLine As String) As TCommandLine
Dim instance As New TCommandLine
Dim parser As New Parser(CommandLine, instance)
parser.Parse()
instance.Parser = parser
Return instance
End Function
This parser then populates the CommandLine object with values from the CommandLine passed in. For example:
Imports Hinshlabs.CommandLineParser
Imports System.Collections.ObjectModel
Public Class Demo3CommandLine
Inherits CommandLineBase
Private m_value1 As String
Private m_value2 As Value2Values = Value2Values.Value1
<CommandLineSwitch("Value1", "Adds a string value named value1"), CommandLineAlias("v1")> _
Public Property Value1() As String
Get
Return Me.m_value1
End Get
Set(ByVal value As String)
Me.m_value1 = value
End Set
End Property
<CommandLineSwitch("Value2", "Adds and enum value called value2"), CommandLineAlias("v2")> _
Public Property Value2() As Value2Values
Get
Return Me.m_value2
End Get
Set(ByVal value As Value2Values)
Me.m_value2 = value
End Set
End Property
Public Enum Value2Values
Enum1
Enum2
Enum3
End Enum
End Class
Would allow you to call [consoleApp] Demo3 /v1:”Any value you like” /Value2:Enum3 and have the correct values populated at runtime.
I have also updated with a DelegateCommand class that would allow you to call a function in the right format from anywhere:
New DelegateCommand(Of Demo3CommandLine)("Demo2", AddressOf OnDemo2Run, "demo 2", "no additional information", "demo 2 command", "This command shows how to delegate the run method using the delegate command")
The delegate command is really easy in .NET 3.5 with the only change being the addition of a variable declared as a Func in the class:
Imports Hinshlabs.CommandLineParser
Imports System.IO
Imports System.Collections.ObjectModel
Imports System.Net
Public Class DelegateCommand(Of TCommandLine As {New, CommandLineBase})
Inherits CommandBase(Of TCommandLine)
Private m_Description As String
Private m_Title As String
Private m_Synopsis As String
Private m_Qualifications As String
Private m_name As String
Private m_RunCommand As Func(Of Integer)
Public Overrides ReadOnly Property Description() As String
Get
Return m_Description
End Get
End Property
Public Overrides ReadOnly Property Name() As String
Get
Return m_name
End Get
End Property
Protected Overrides Function RunCommand() As Integer
Try
Return m_RunCommand.Invoke
Catch ex As Exception
CommandOut.Error("Failed: {0}", ex.ToString)
Return -1
End Try
End Function
Protected Overrides Function ValidateCommand() As Boolean
Return True
End Function
Public Overrides ReadOnly Property Title() As String
Get
Return m_title
End Get
End Property
Public Overrides ReadOnly Property Synopsis() As String
Get
Return Synopsis
End Get
End Property
Public Overrides ReadOnly Property Switches() As ReadOnlyCollection(Of SwitchInfo)
Get
Return CommandLine.Switches
End Get
End Property
Public Overrides ReadOnly Property Qualifications() As String
Get
Return String.Empty
End Get
End Property
Public Sub New(ByVal name As String, ByVal runCommand As Func(Of Integer), ByVal title As String, ByVal qualifications As String, ByVal synopsis As String, ByVal description As String)
m_name = name
m_RunCommand = runCommand
m_Title = title
m_Qualifications = qualifications
m_Synopsis = synopsis
m_Description = description
End Sub
End Class
If you were wondering why there are so many properties, it is to allow the help to be created automatically. For example if you call the help function on Demo3Command you will get…
With the values coming from the relevant places:
It will also support inherited CommandLine objects to minimize duplication.
I hope that if you are building command line apps that you will have a look, just remember not to spend too much effort on cmd, when Power Shell is much more suitable and accessible to non developers.
Get Command Line Parser v2.0
Friday, August 14, 2009
A colleague of mine was having a bit of trouble getting drag and drop working in a way that fitted well with the MVVM pattern. This is really quite simple once you have a certain level of understanding of Patterns, but is a complete nightmare if you do not.
One of the founding principals of MVVM is that you should never be writing code in your code behind, it should all be encapsulated away and be bindable in XAML to achieve the result. Anyone who has tackled drag and drip will have suddenly found their code behind covered in code for handling both the drag and the drop, and multiplied up when dealing with multiple controls.
I cruised the web for information, of which I found plenty and settled on an example by Bea Stollnitz of Microsoft. In her post i had found one of the best and most intuitive examples of the Drag & Drop Behaviour written in C#.
I am not going to go into all of her code which she has available for download, just to say that it is nice, and is exactly what I am looking for even with the limitations that she described.
The functionality available allows you to drag a piece of data from one ItemsControl to another of the same data type or to reorder within itself. It provides for a floating template for the dragging item and a visual cue for the drop location.
I wanted to augment this to allow for other scenarios while keeping as much functionality as possible.
Likes:
- Drag functionality
- Drag templating – nice!
- Encapsulation of logic
Dislikes:
- No way to control drop behaviour
My version lets you inject additional functionality at runtime. The adjusted class diagram shows the relationships, but we only really use the DragDropBehaviour class
You can still use the standard options:
<DockPanel>
<Label DockPanel.Dock="Top" Content="Checkout" />
<ListBox hlb:DragDropBehaviour.IsDragSource="true"
hlb:DragDropBehaviour.IsDropTarget="true"
hlb:DragDropBehaviour.DragTemplate="{StaticResource MyTemplate}"
ItemsSource="{Binding Items}"
MinWidth="100"
MinHeight="100"
AllowDrop="True"
SelectionMode="Multiple">
</ListBox>
</DockPanel>
But I have added another bindable option of DropProcessor that allows you to override the default DropProcessor to achieve whatever you want.
<ListBox hlb:DragDropBehaviour.DropProcessor="{Binding DropProcessor}"
hlb:DragDropBehaviour.IsDragSource="true"
hlb:DragDropBehaviour.IsDropTarget="true"
hlb:DragDropBehaviour.DragTemplate="{StaticResource moo}"
ItemsSource="{Binding Items}"
MinWidth="100"
MinHeight="100">
In this example I have created a little gun shop called “Nutters R’ Us” where you can buy weapons and ordinance. You can see that there is an area for weapons, and area for ordinance and an area for your selected purchases.
I have added a custom DropProcessor only to the Checkout area that only applies when you drop items of type “OrdinanceViewModel”
Public Class CheckoutDropProcessor
Inherits DropProcessor
Public Overrides Function GetDropAdorner(ByVal behaviour As DragDropBehaviour, ByVal adornerLayer As System.Windows.Documents.AdornerLayer) As DropAdorner
If TypeOf behaviour.TargetItemContainer.DataContext Is WeaponViewModel Then
If TypeOf behaviour.SourceItemContainer.DataContext Is OrdnanceViewModel Then
Return New OrdnanceToWeaponDropAdorner(behaviour, adornerLayer)
End If
End If
Return MyBase.GetDropAdorner(behaviour, adornerLayer)
End Function
Public Overrides Function IsDropAllowed(ByVal behaviour As DragDropBehaviour, ByVal draggedItem As Object) As Boolean
If Not behaviour.SourceItemContainer Is Nothing AndAlso TypeOf behaviour.SourceItemContainer.DataContext Is OrdnanceViewModel Then
If Not behaviour.TargetItemContainer Is Nothing AndAlso TypeOf behaviour.TargetItemContainer.DataContext Is WeaponViewModel Then
Return True
End If
Return False
End If
Return MyBase.IsDropAllowed(behaviour, draggedItem)
End Function
Public Overrides Sub Drop(ByVal behaviour As DragDropBehaviour, ByVal draggedItem As Object, ByVal dropEffect As System.Windows.DragDropEffects)
If Not behaviour.TargetItemContainer Is Nothing AndAlso TypeOf behaviour.TargetItemContainer.DataContext Is WeaponViewModel Then
If TypeOf behaviour.SourceItemContainer.DataContext Is OrdnanceViewModel Then
CType(behaviour.TargetItemContainer.DataContext, WeaponViewModel).AddOrdinance(CType(behaviour.SourceItemContainer.DataContext, OrdnanceViewModel))
Dim indexRemoved As Integer = -1
If ((dropEffect And DragDropEffects.Move) <> DragDropEffects.None) Then
indexRemoved = Utilities.RemoveItemFromItemsControl(behaviour.SourceItemsControl, draggedItem)
End If
If (((indexRemoved <> -1) AndAlso (behaviour.SourceItemsControl Is behaviour.TargetItemsControl)) AndAlso (indexRemoved < behaviour.InsertionIndex)) Then
behaviour.InsertionIndex -= 1
End If
Exit Sub
End If
End If
MyBase.Drop(behaviour, draggedItem, dropEffect)
End Sub
End Class
This class inherits from the base class “DropProcessor” that provides the same functionality as the original article, but I have
overridden couple of methods. The first, “GetDropAdorner” test to make sure that you are dropping a OrdinanceViewModel onto a WeaponViewModel and provides a different and custom DropAdorner that instead of providing the lovely insertion visual it just applied a “IsDropTarget” property to the ListBoxItem to allow a template to control the visual. The IsAllowedDrop also test for this case, as does the Drop method. In all cases they are just testing for a special case of Drop and call the base classes methods.
The diagram for the demo app is a little large, but you can see how much I still suck at MVVM, and although I have learned a lot doing this demo, I am still tempted to share ViewModels… but that is a bad habit.
I have highlighted the two main classes, and we have already discussed the CheckoutDropProcessor. This allows you the flexibility to augment your drop scenarios without all of your developers having to get too deep in the guts on the behaviour, thus leaving them plenty of time for the real work of actually building something useful.
I have put this up on Codeplex, and both the source and binaries are available.
Thursday, August 06, 2009
Have you been waiting for a long time for Windows 7? Well I have.. I have been able to use Beta 1 and the RC for a good while now, and it surprised me that the Windows 7 Beta 1 was more stable, responsive and cleaner than Vista was after Service Pack 3.
Today (06/08/2009) Windows 7 RTM will be available for Developers on MSDN and a lovely free copy for those that participated in the invitation only Beta program.
What a joy this day is, we finally get an OS that is both up to date and that we can use… I was nearly ready to hang up my PC and buy a MAC. Well, when I say NEARLY I really mean as far away as possible with a hazmat suit on, but you get the idea. Those of you that use Vista and like it, at least over the aging XP will love Windows 7. It is what Windows Vista should have been but wasn’t.
If you have not yet seen Windows 7 then head on over to the Windows 7 site, if you have then it will not be long until it is available. September will be the official “buy it in the shops” day, but many new PC’s already come with an automatic upgrade.
Technorati Tags:
Windows,
Windows 7
Thursday, July 30, 2009
Another nice feature of Outlook 2010 that I like is the Calendar preview:
Very effective for seeing quickly wither you can attend :)
I am always pulling out the Unity assemblies. Maybe it is just because I am lazy, but I really can’t be bothered rolling my own dependency injection and mapping framework!

I am going to use Unity only as a mapping frame work for now, I want to be able to pass an Interface into a method, and get back the correct data access class.
The plan is to meet the diagram on the left. Only the factory and the interfaces are accessible, but are all set to “friend” which will require an assembly tag to be added to both the Factory and the Interfaces assemblies to allow explicit access.
<Assembly: InternalsVisibleTo("Hinshlabs.TfsDynamicPolicy.Services.Server")>
This allows classes in the named assembly to access all classes and methods that have been prefaced with the “Friend” access level. Its a sneaky way of helping to maintain your layer integrity. So with this reference added only the Services.Server assembly, the one with the web service implementation, can access the factory and the interfaces, making them un-callable from any other assembly.
note: if you use only the assembly’s friendly name and not the explicit signed name then someone could just create an assembly of the same name as above and they will be able to call as if they were Services.Server. This is not to prevent hacking, just those pesky developers (me) who may try to bypass the service tier…
If, like me you are running tests you will also need to add an InternalsVisibleTo entry for your test assemblies or you will be plagued by “Assessors”..
First is my IDataAccess class, this is really only exists so there is some validation on my generics.
' Assembly: Hinshlabs.TfsDynamicPolicy.DataAccess.Common
Public Interface IDataAccess
End Interface
I also have a more specific Interface that allows for the loading of Artefacts. I have not yet implemented anything more than get and add, but you can see that it inherits (yes an interface and inherits in the same sentence) from the IDataAccess interface so we can pass it as a generic type that complies with IDataAccess.
'Assembly: Hinshlabs.TfsDynamicPolicy.DataAccess.Common
Imports Hinshlabs.TfsDynamicPolicy.Common
Public Interface IArtifactDataAccess(Of T As Artifact)
Inherits IDataAccess
Function GetArtifact(ByVal id As Guid) As T
Function GetArtifact(ByVal Uri As Uri) As T
Function GetArtifacts() As ArtifactCollection
Function AddArtifact(ByVal artifact As T) As Boolean
End Interface
Wow… that was hard, IDataAccess may have a little more than nothing in the future, but for now that is it. Now, what I need is a class that I can inherit from that will provide the functionality I need for any factory. This is mainly coz factories bread factories.

'Assembly: Hinshlabs.TfsDynamicPolicy.Common
Imports System.IO
Imports System.Globalization
Imports System.Xml.Serialization
Imports System.Configuration
Imports Microsoft.Practices.Unity
Imports Microsoft.Practices.Unity.Configuration
Imports Hinshlabs.TfsDynamicPolicy.Common
Imports System.Reflection
Friend MustInherit Class UnityFactoryBase(Of T As {New, UnityFactoryBase(Of T)})
#Region " Singleton"
Private Shared m_Instance As T
Friend Shared ReadOnly Property Instance As T
Get
If m_Instance Is Nothing Then
m_Instance = New T
m_Instance.Initialize()
End If
Return m_Instance
End Get
End Property
Protected Sub New()
' Do nothing in here
End Sub
#End Region
Private m_UnityContainer As IUnityContainer
Private Sub Initialize()
' Create Unity container
m_UnityContainer = New UnityContainer()
' Configure Unity
Dim config As Configuration
Dim fm As New ExeConfigurationFileMap
fm.ExeConfigFilename = ConfigFilename
Dim configFileLocation As String = Path.Combine(Path.GetDirectoryName(Assembly.GetCallingAssembly.Location), ConfigFilename)
If Not File.Exists(configFileLocation) Then
Throw New FileNotFoundException(String.Format(CultureInfo.InvariantCulture, "Unable to load config file with the name {0}", fm.ExeConfigFilename), configFileLocation)
End If
config = ConfigurationManager.OpenMappedExeConfiguration(fm, ConfigurationUserLevel.None)
Dim section As UnityConfigurationSection = CType(config.GetSection("unity"), UnityConfigurationSection)
If section Is Nothing Then
Throw New AccessViolationException(String.Format(CultureInfo.InvariantCulture, "There is no unity section called {0} in the config file {1}", UnityContainerName, ConfigFilename))
End If
section.Containers(UnityContainerName).Configure(m_UnityContainer)
End Sub
Protected ReadOnly Property UnityContainer As IUnityContainer
Get
Return m_UnityContainer
End Get
End Property
Protected MustOverride ReadOnly Property UnityContainerName As String
Protected MustOverride ReadOnly Property ConfigFilename As String
End Class
The UnityFactoryBase class provides a couple of features. It provides a Singleton pattern so that we make sure that there is only ever one instance per factory type T and we initialise a UnityContainer for each Instance of factory T. You can see that on the class definition the generic type T that will be passed in is of the same type as the class itself. This makes sure that the class that represents T inherits from UnityFactoryBase(Of T As {New, UnityFactoryBase(Of T)}). When configuring the Unity Container we get both the container name and the config file to load from the child class. This allows for both different config files per factory and the option to have a single config file and multiple unity configuration elements. Its up to you…
We need to inherit from UnityFactoryBase(Of T As {New, UnityFactoryBase(Of T)}) to create our factory and provide a config file to configure the UnityContainer. The inherited class is pretty simple as we have done most of the heavy lifting in the base class.

Imports Hinshlabs.TfsDynamicPolicy.DataAccess.Common
' Assembly: Hinshlabs.TfsDynamicPolicy.DataAccess
Imports Hinshlabs.TfsDynamicPolicy.Common
Imports System.Globalization
Friend Class DataAccessFactory
Inherits UnityFactoryBase(Of DataAccessFactory)
Public Sub New()
' Do nothing in here
End Sub
Protected Overrides ReadOnly Property UnityContainerName As String
Get
Return "DataAccess"
End Get
End Property
Protected Overrides ReadOnly Property ConfigFilename As String
Get
Return String.Format(CultureInfo.InvariantCulture, "{0}.Unity.config", System.Reflection.Assembly.GetExecutingAssembly().GetName.Name)
End Get
End Property
Public Function GetDataAccess(Of T As IDataAccess)() As T
Return UnityContainer.Resolve(Of T)()
End Function
End Class
The DataAccessFactory provides the data needed by the UnityFactoryBase(Of DataAccessFactory) as well as the method that I need to retrieve an IDataAccess class. I will show you how it is called, but first there is the little matter of config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
</configSections>
<unity>
<typeAliases>
<!-- Lifetime manager types should be inserted if you need lifetime managers -->
<typeAlias alias="singleton" type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager, Microsoft.Practices.Unity" />
<typeAlias alias="external" type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager, Microsoft.Practices.Unity" />
<!-- User defined type aliases -->
<!-- An interface for artifact data access implementation -->
<typeAlias alias="IArtifactDataAccess`1" type="Hinshlabs.TfsDynamicPolicy.DataAccess.Common.IArtifactDataAccess`1, Hinshlabs.TfsDynamicPolicy.DataAccess.Common" />
</typeAliases>
<containers>
<container name="DataAccess">
<types>
<!-- Lifetime managers specified using the type aliases -->
<type type="IArtifactDataAccess`1" mapTo="Hinshlabs.TfsDynamicPolicy.DataAccess.Xml.ArtifactDataAccess`1, Hinshlabs.TfsDynamicPolicy.DataAccess.Xml">
<lifetime type="singleton" />
</type>
</types>
</container>
</containers>
</unity>
</configuration>
I am not going to explain the config file as it is well (ish) documented, but it effectively mapped all generic calls to retrieve IArtifactDataAccess`1 to the implementation in the XML assembly of ArtifactDataAccess`1 and makes the resultant object a singleton.
To call this lovely package after you have populated it with many many DataAccess goodies all you need to do is this:
Dim dal As IArtifactDataAccess(Of DynamicPolicy) = DataAccessFactory.Instance.GetDataAccess(Of IArtifactDataAccess(Of DynamicPolicy))()
Lovely and simple, and to add a new implementation all I need is an Interface like IArtifactDataAccess.. lets call it IHypotheticalDataBits, a concrete implementation… lets call it HypoDataFromTwitter and a mapping in the config file:
<type type="IHypotheticalDataBits" mapTo="Hinshlabs.TfsDynamicPolicy.DataAccess.Xml.HypoDataFromTwitter, Hinshlabs.TfsDynamicPolicy.DataAccess.Xml">
<lifetime type="singleton" />
</type>
And you can then call:
Dim dal As IHypotheticalDataBits = DataAccessFactory.Instance.GetDataAccess(Of IHypotheticalDataBits)()
Any easier and it would be writing for you :)
Monday, July 27, 2009
I have always been annoyed with the mechanics of the Team Foundation Server check-in policies. I understand the limitations, but having to have a specific policy installed on every developers computer before you can use it is slightly ridicules and practically unmanageable. Why is there not a way to have a single installation that allows you to select any policy you want and have it execute in the desired manor on every client, including web clients?
I think it is. With the advent of the Dynamic Language Runtime (DLR) it should be possible to have a single (well one for each version of Visual Studio) policy that allows you to pick a DLR policy and have it run. One better would be to have a site that encapsulated those policies and you could just pick the one you want from a list. This could then open up a bunch of other features, like saved policy sets or composite policies for specific methodologies, or many others. But keeping to the KISS (Keep It Simple Stupid) principal all I need are two or three things, a Web Service based data store, a Policy and an editor.
I am currently working on the Web Services and how to pass and store the data I will need, but it looks promising and my initial investigations certainly worked quite well even though I am hampered by my lack of IronPython or IronRuby experience.
Once I have the web services up to scratch (WCF by the way) I will try my hand at the Editor (WPF) and then move onto the Web interface (ASP.NET MVC) all in Visual Studio 2010. A mammoth task, but one I think I can manage… famous last words…
I might need to learn a little Ruby :)
Sunday, July 26, 2009
I am not sure if this is a good idea, but I was bored one day and decided to add a TFS Error Log provider for Elmah. There are 2 ways you can do this. You can create a new WorkItem type and log an error report for each of the errors or you can create one work item for each error type/title. To do this you can create a title that is the combination of error message and application name and then search TFS for an existing work item. If it exists then add the error to it, if it does not then create a work item for that instance. You can use any work item type, and the errors are added as Elmah xml log files.
There are a number of things you need to override when you inherit from Elmah.ErrorLog. The first is the Log method.
''' <summary>
''' Logs the error as an attachment to an existing work item, or adds a new work item if this error has not occurred.
''' </summary>
''' <param name="error">The error to be logged</param>
''' <returns>The ID of the error</returns>
''' <remarks></remarks>
Public Overrides Function Log(ByVal [error] As [Error]) As String
Dim errorId = Guid.NewGuid().ToString()
Dim timeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddHHmmssZ", CultureInfo.InvariantCulture)
Dim Filename = String.Format("error-{0}-{1}.elmah", timeStamp, errorId)
Dim temp = System.IO.Path.Combine(".", Filename)
' Temp Log to disk
Using writer = New XmlTextWriter(temp, Encoding.UTF8)
writer.Formatting = Formatting.Indented
writer.WriteStartElement("error")
writer.WriteAttributeString("errorId", errorId)
ErrorXml.Encode([error], writer)
writer.WriteEndElement()
writer.Flush()
End Using
Dim Title As String = String.Format("{0}-{1}", [error].ApplicationName, [error].Message)
Dim wi As WorkItem = GetWorkItemForException(Title, [error])
Dim a As New Attachment(temp, "Elmah error log")
wi.Attachments.Add(a)
If wi.IsValid Then
wi.Save()
Return String.Format("{0}|{1}", wi.Id, errorId.ToString)
Else
Dim message As New System.Text.StringBuilder
Dim results = wi.Validate()
Dim isFirst As Boolean = True
For Each r In results
message.AppendLine(String.Format(IIf(isFirst, "{0}", ", {0}"), r))
isFirst = False
Next
Throw New ApplicationException(String.Format("Unable to save the work item because the following fields produced a validation error '{0}'.", message.ToString))
End If
End Function
The idea is that you attach the Elmah log file to the work item with a .elmah extension. This will allow us to find all the error logs in the future. So we create the temporary log file, and then create our key/title for our work item. You can customize this by customizing your exception messages on the server side. We then get our work item, and add the file as an attachment.
Because I am doing this the quick and dirty way, i.e. just for fun, I have utilised the API’s provided in the Templates add-on for the Power Tools to customize the work items. So when we are creating the Work item:
Protected Function GetWorkItemForException(ByVal Title As String, ByVal [error] As [Error]) As WorkItem
Dim wi As WorkItem = GetExistingWorkItem(Title)
If wi Is Nothing Then
wi = CreateNewWorkItem(Title)
End If
m_TemplateDefault.Fields.ApplyFieldValues(wi, False)
ApplyErrorFieldValues(wi, [error])
Return wi
End Function
So, we either get an existing work item, or we create a new one, but then we need to apply some values to the work item. In the constructor of the class Elmah passes an IDictionary object that we use to pass the template names.
Public Sub New(ByVal config As IDictionary)
If config Is Nothing Then
Throw New ArgumentNullException("config")
End If
sm_Config = config
Dim store As ITemplateStore = GetStore()
m_TemplateDefault = GetTemplate("Defaults", store)
m_TemplateErrorMap = GetTemplate("ErrorMap", store)
If m_TemplateDefault Is Nothing Or m_TemplateErrorMap Is Nothing Then
Throw New ApplicationException("Unable to load the templates from the store.")
End If
End Sub
I created a Store (Microsoft.TeamFoundation.PowerTools.Client.WorkItemTracking.Templates.ITemplateStore) for the templates and attempt to load both a “defaults” template and a dynamic “mapping” template. The latter will need some special mapping, but as you can see from the GetWorkItemForException there is already a method on the Template object to Apply all of the values to a work item. Here is an example default template:
<?xml version="1.0"?>
<Template xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FieldValues>
<FieldValue>
<ReferenceName>System.AreaPath</ReferenceName>
<Value>TestProject1\TestArea1</Value>
</FieldValue>
<FieldValue>
<ReferenceName>System.IterationPath</ReferenceName>
<Value>TestProject1\TestIteration1</Value>
</FieldValue>
<FieldValue>
<ReferenceName>System.AssignedTo</ReferenceName>
<Value>Martin Hinshelwood</Value>
</FieldValue>
<FieldValue>
<ReferenceName>Microsoft.VSTS.CMMI.FoundInEnvironment</ReferenceName>
<Value>DEV</Value>
</FieldValue>
<FieldValue>
<ReferenceName>Microsoft.VSTS.Build.FoundIn</ReferenceName>
<Value>Build_v1.13_20090312.1</Value>
</FieldValue>
</FieldValues>
<WorkItemTypeName>Bug</WorkItemTypeName>
<TeamServerUri>http://tfs01.company.biz:8080/</TeamServerUri>
<TeamProjectName>TestProject1</TeamProjectName>
<Description />
</Template>
These values are now mapped onto the work item. But what about any dynamic values that we want to use from the Error. I added a second template called “ErrorMap” that will use the same format, but use something like:
<?xml version="1.0"?>
<Template xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FieldValues>
<FieldValue>
<ReferenceName>System.AreaPath</ReferenceName>
<Value>TestProject1\{ApplicationName}</Value>
</FieldValue>
<FieldValue>
<ReferenceName>System.Description</ReferenceName>
<Value>{WebHostHtmlMessage}</Value>
</FieldValue>
<FieldValue>
<ReferenceName>Company.Custom.MethodName</ReferenceName>
<Value>{Exception.TargetSite.Name}</Value>
</FieldValue>
</FieldValues>
<WorkItemTypeName>Bug</WorkItemTypeName>
<TeamServerUri>http://tfs01.company.biz:8080/</TeamServerUri>
<TeamProjectName>TestProject1</TeamProjectName>
<Description />
</Template>
We can then apply those values with a little reflection by parsing out the value and applying the retrieved object values to the work item.
Private Sub ApplyErrorFieldValues(ByVal wi As WorkItem, ByVal [error] As [Error])
For Each i In m_TemplateErrorMap.Fields
Dim value As String = GetPropertyValue(i.Value, [error])
If wi.Fields(i.ReferenceName).AllowedValues.Contains(value) Then
wi.Fields(i.ReferenceName).Value = value
Else
Throw New ApplicationException(String.Format("Unable to set the work item field '{0}' to '{1}' as '{1}' is not in the Allowed Values list.", i.ReferenceName, value))
End If
Next
End Sub
Private Function GetPropertyValue(ByVal path As String, ByVal target As Object) As String
Dim bits() As String = path.Split(".")
Dim ll As New LinkedList(Of String)
Array.ForEach(bits, Function(b) ll.AddLast(b))
Return GetPropertyRecurse(ll.First, target)
End Function
Private Function GetPropertyRecurse(ByVal node As LinkedListNode(Of String), ByVal target As Object) As String
' ToDo: add ability to support propertyName(0) [arrays]
Dim r As System.Reflection.PropertyInfo = target.GetType.GetProperty(node.Value, BindingFlags.Static Or BindingFlags.Public Or BindingFlags.GetField Or BindingFlags.GetProperty)
If r.PropertyType.IsClass And Not node.Next Is Nothing Then
Return GetPropertyRecurse(node.Next, r.GetValue(target, Nothing))
Else
Return r.GetValue(target, Nothing).ToString
End If
End Function
Like I said this is work in progress and it does not support arrays as sub values, but it does add a certain level of versatility to the logging. My last project used a logging system, not Elmah, to log errors to TFS in this way and I also added functionality to update the work item in different ways if it was Closed or Resolved to reactivate it depending on the Build number values.
We have now created a new work item, but what about loading an existing one?
Private Function GetExistingWorkItem(ByVal Title As String) As WorkItem
' Query for work items
Dim query As String = "SELECT [System.Id], [System.Title] " _
& "FROM WorkItems " _
& "WHERE [System.TeamProject] = @Project " _
& "AND [System.WorkItemType] = @WorkItemType " _
& "AND [System.Title] = @Title " _
& "ORDER BY [System.Id]"
Dim paramiters As New Hashtable
paramiters.Add("Project", m_TemplateDefault.TeamProjectName)
paramiters.Add("WorkItemType", m_TemplateDefault.WorkItemTypeName)
paramiters.Add("Title", m_TemplateDefault.WorkItemTypeName)
Dim y As WorkItemCollection = TfsWorkItemStore.Query(query, paramiters)
Return y(0)
End Function
This is a simple search for the title that we created and pass back the first match, just in case we have duplicates.
And that's all there is to saving your logs into VSTS, but how do we get them out! This is pretty easy as all of our log entries have now been saved to a TFS work item and if you remember from before we used the “String.Format("{0}|{1}", wi.Id, errorId.ToString)“ for the ID so we can find the work item again.
The two thing we have left is loading a single error, and loading all of the errors. Getting a single error is a little tricky, which is why we passed back the ID in a format that included the Work Item ID.
Public Overrides Function GetError(ByVal id As String) As ErrorLogEntry
Dim idBits() As String = id.Split("|")
Dim wiId As Integer
Dim errGuid As String
If Not idBits.Length = 2 Then
Throw New ArgumentException("Invalid ID, it must be made in the format {workItemId}|{guid}", "id")
End If
If Not IsNumeric(idBits(0)) Then
Throw New ArgumentException("The workItemId part of the ID must be an integer. Format: {workItemId}|{guid}", "id")
End If
wiId = CInt(idBits(0))
Try
errGuid = New Guid(idBits(1)).ToString
Catch ex As Exception
Throw New ArgumentException("The guid part of the ID must be an integer. Format: {workItemId}|{guid}", "id")
End Try
Dim wi As WorkItem = TfsWorkItemStore.GetWorkItem(wiId)
If wi Is Nothing Then
Throw New ApplicationException("A work item with that id does not exits")
End If
Dim a = (From attachemnt As Attachment In wi.Attachments Where attachemnt.Name.Contains(errGuid) Select attachemnt).SingleOrDefault
If a Is Nothing Then
Throw New ApplicationException("The attachment does not exits or has been removed")
End If
Return GetErrorLogEntryFromTfsAttachement(wi, a)
End Function
In this method we do a little validation while parsing out the Work Item ID and the Elmah ID, we then load the specified work item, and find the attachment, and return it. I have a little helper method to make a log item from an attachment, but it fairly simple:
Private Function GetErrorLogEntryFromTfsAttachement(ByVal wi As WorkItem, ByVal a As Attachment) As ErrorLogEntry
Using reader = XmlReader.Create(a.Uri.ToString)
If Not reader.IsStartElement("error") Then
Return Nothing
End If
Dim errid = String.Format("{0}|{1}", wi.Id, reader.GetAttribute("errorId"))
Dim [error] = ErrorXml.Decode(reader)
Return New ErrorLogEntry(Me, errid, [error])
End Using
Return Nothing
End Function
And voila! You havve a single Error Log Entry. As you have probably guesses, getting all the errors is easy now. We just need to find all attachements that have a .elmah extension in our project. A little linq can help with this.
Public Overrides Function GetErrors(ByVal pageIndex As Integer, ByVal pageSize As Integer, ByVal errorEntryList As System.Collections.IList) As Integer
If pageIndex < 0 Then Throw New ArgumentOutOfRangeException("pageIndex", pageIndex, Nothing)
If pageSize < 0 Then Throw New ArgumentOutOfRangeException("pageSize", pageSize, Nothing)
' Query for work items
Dim query As String = "SELECT [System.Id], [System.Title] " _
& "FROM WorkItems " _
& "WHERE [System.TeamProject] = @Project " _
& "AND [System.WorkItemType] = @WorkItemType " _
& "ORDER BY [System.Id]"
Dim paramiters As New Hashtable
paramiters.Add("Project", m_TemplateDefault.TeamProjectName)
paramiters.Add("WorkItemType", m_TemplateDefault.WorkItemTypeName)
Dim y As WorkItemCollection = TfsWorkItemStore.Query(query, paramiters)
' Query work items for attachments
Dim wiats = From wi As WorkItem In y, a As Attachment In wi.Attachments Where a.Name.Contains(".elmah") Order By a.Name Select a, wi
If Not wiats Is Nothing Then
' Select specific attachemnts
Dim results = From wiat In wiats Skip pageIndex * pageSize Take pageSize Select wiat
' Add to output
For Each el In results
errorEntryList.Add(GetErrorLogEntryFromTfsAttachement(el.wi, el.a))
Next
End If
' return count
Return errorEntryList.Count
End Function
And there we go, errors from Elmah saved into Team Foundation Server and then loaded back out. I don’t know how useful this would be in the real world, but it was good for a little boredom relief.
Full Source
Imports Elmah
Imports Microsoft.TeamFoundation.Client
Imports Microsoft.TeamFoundation.WorkItemTracking.Client
Imports Microsoft.TeamFoundation.PowerTools.Client.WorkItemTracking.Templates
Imports System.Globalization
Imports System.Xml
Imports System.Text
Imports System.Web
Imports System.Reflection
Public Class TfsErrorLog
Inherits ErrorLog
Private Shared m_TemplateDefault As Template
Private Shared m_TemplateErrorMap As Template
Private Shared sm_Tfs As TeamFoundationServer
Private Shared sm_TfsStore As WorkItemStore
Private Shared sm_TfsProject As Project
Private Shared sm_Config As IDictionary
Public ReadOnly Property TfsServer() As TeamFoundationServer
Get
If sm_Tfs Is Nothing Then
sm_Tfs = GetTeamFoundationServer()
End If
Return sm_Tfs
End Get
End Property
Public ReadOnly Property TfsWorkItemStore() As WorkItemStore
Get
If sm_TfsStore Is Nothing Then
sm_TfsStore = GetTeamFoundationServerWorkItemStore()
End If
Return sm_TfsStore
End Get
End Property
Public ReadOnly Property TfsProject() As Project
Get
If sm_TfsProject Is Nothing Then
sm_TfsProject = GetTeamFoundationServerProject()
End If
Return sm_TfsProject
End Get
End Property
Public Sub New(ByVal config As IDictionary)
If config Is Nothing Then
Throw New ArgumentNullException("config")
End If
sm_Config = config
Dim store As ITemplateStore = GetStore()
m_TemplateDefault = GetTemplate("Defaults", store)
m_TemplateErrorMap = GetTemplate("ErrorMap", store)
If m_TemplateDefault Is Nothing Or m_TemplateErrorMap Is Nothing Then
Throw New ApplicationException("Unable to load the templates from the store.")
End If
End Sub
Private Function GetStore()
Dim TfsWorkItemTemplateStore As String = GetStorePath()
Try
Dim storeProvider As New FileSystemTemplateStoreProvider
Return New TemplateStore(storeProvider, TfsWorkItemTemplateStore, ":)Store")
Catch ex As Exception
Throw New ApplicationException(String.Format("Unable to load the store from '{0}'.", TfsWorkItemTemplateStore), ex)
End Try
End Function
Private Function GetStorePath() As String
Dim storePath As String = sm_Config("TfsWorkItemTemplateStore")
If String.IsNullOrEmpty(storePath) Then
Throw New ApplicationException("Tfs Server Name is missing for the TFS based error log.")
End If
Try
If storePath.StartsWith("~/") Then
storePath = HttpContext.Current.Server.MapPath(storePath)
End If
Return storePath
Catch ex As Exception
Throw New ApplicationException(String.Format("Unable to produce the store path from '{0}'.", storePath), ex)
End Try
End Function
Private Function GetTemplate(ByVal TemplateName As String, ByVal store As ITemplateStore) As ITemplate
Try
Dim t As ITemplate
If Not store.TemplateExists("/", TemplateName) Then
t = store.CreateTemplate()
t.Name = TemplateName
t.ParentFolder = "/"
t.TeamServerUri = "https://tfs01.codeplex.biz:443"
t.TeamProjectName = "RDdotNet"
t.WorkItemTypeName = "WorkItem"
store.AddTemplate(t)
End If
Return store.GetTemplate("/", TemplateName)
Catch ex As Exception
Throw New ApplicationException(String.Format("Unable to load the template '{0}' from the store.", TemplateName), ex)
End Try
End Function
Private Function GetTeamFoundationServer() As TeamFoundationServer
Dim tfs As TeamFoundationServer = Nothing
Try
tfs = New TeamFoundationServer(m_TemplateDefault.TeamServerUri)
tfs.Authenticate()
If Not tfs.HasAuthenticated Then
Throw New ApplicationException("Unable to authenticate against TFS server")
End If
Catch ex As Exception
Throw New ApplicationException("Failed to authenticate against TFS server", ex)
End Try
Return tfs
End Function
Private Function GetTeamFoundationServerWorkItemStore() As WorkItemStore
Dim store As WorkItemStore = Nothing
If TfsServer.HasAuthenticated Then
store = DirectCast(TfsServer.GetService(GetType(WorkItemStore)), WorkItemStore)
End If
Return store
End Function
Private Function GetTeamFoundationServerProject() As Project
Dim Project As Project = Nothing
Try
If TfsServer.HasAuthenticated Then
Project = TfsWorkItemStore.Projects(m_TemplateDefault.TeamProjectName)
End If
Catch ex As Exception
Throw New ApplicationException("Unable to retrieve Tfs Project", ex)
End Try
If Project Is Nothing Then
Throw New ApplicationException(String.Format("Unable to locate project with the name '{0}'", m_TemplateDefault.TeamProjectName))
End If
Return Project
End Function
Public Overrides Function GetError(ByVal id As String) As ErrorLogEntry
Dim idBits() As String = id.Split("|")
Dim wiId As Integer
Dim errGuid As String
If Not idBits.Length = 2 Then
Throw New ArgumentException("Invalid ID, it must be made in the format {workItemId}|{guid}", "id")
End If
If Not IsNumeric(idBits(0)) Then
Throw New ArgumentException("The workItemId part of the ID must be an integer. Format: {workItemId}|{guid}", "id")
End If
wiId = CInt(idBits(0))
Try
errGuid = New Guid(idBits(1)).ToString
Catch ex As Exception
Throw New ArgumentException("The guid part of the ID must be an integer. Format: {workItemId}|{guid}", "id")
End Try
Dim wi As WorkItem = TfsWorkItemStore.GetWorkItem(wiId)
If wi Is Nothing Then
Throw New ApplicationException("A work item with that id does not exits")
End If
Dim a = (From attachemnt As Attachment In wi.Attachments Where attachemnt.Name.Contains(errGuid) Select attachemnt).SingleOrDefault
If a Is Nothing Then
Throw New ApplicationException("The attachment does not exits or has been removed")
End If
Return GetErrorLogEntryFromTfsAttachement(wi, a)
End Function
Public Overrides Function GetErrors(ByVal pageIndex As Integer, ByVal pageSize As Integer, ByVal errorEntryList As System.Collections.IList) As Integer
If pageIndex < 0 Then Throw New ArgumentOutOfRangeException("pageIndex", pageIndex, Nothing)
If pageSize < 0 Then Throw New ArgumentOutOfRangeException("pageSize", pageSize, Nothing)
' Query for work items
Dim query As String = "SELECT [System.Id], [System.Title] " _
& "FROM WorkItems " _
& "WHERE [System.TeamProject] = @Project " _
& "AND [System.WorkItemType] = @WorkItemType " _
& "ORDER BY [System.Id]"
Dim paramiters As New Hashtable
paramiters.Add("Project", m_TemplateDefault.TeamProjectName)
paramiters.Add("WorkItemType", m_TemplateDefault.WorkItemTypeName)
Dim y As WorkItemCollection = TfsWorkItemStore.Query(query, paramiters)
' Query work items for attachments
Dim wiats = From wi As WorkItem In y, a As Attachment In wi.Attachments Where a.Name.Contains(".elmah") Order By a.Name Select a, wi
If Not wiats Is Nothing Then
' Select specific attachemnts
Dim results = From wiat In wiats Skip pageIndex * pageSize Take pageSize Select wiat
' Add to output
For Each el In results
errorEntryList.Add(GetErrorLogEntryFromTfsAttachement(el.wi, el.a))
Next
End If
' return count
Return errorEntryList.Count
End Function
''' <summary>
''' Logs the error as an attachement to an existing work item, or adds a new work item if this error has not occured.
''' </summary>
''' <param name="error">The error to be logged</param>
''' <returns>The ID of the error</returns>
''' <remarks></remarks>
Public Overrides Function Log(ByVal [error] As [Error]) As String
'TODO: Log
Dim errorId = Guid.NewGuid().ToString()
Dim timeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddHHmmssZ", CultureInfo.InvariantCulture)
Dim Filename = String.Format("error-{0}-{1}.elmah", timeStamp, errorId)
Dim temp = System.IO.Path.Combine(".", Filename)
' Temp Log to disk
Using writer = New XmlTextWriter(temp, Encoding.UTF8)
writer.Formatting = Formatting.Indented
writer.WriteStartElement("error")
writer.WriteAttributeString("errorId", errorId)
ErrorXml.Encode([error], writer)
writer.WriteEndElement()
writer.Flush()
End Using
Dim Title As String = String.Format("{0}-{1}", [error].ApplicationName, [error].Message)
Dim wi As WorkItem = GetWorkItemForException(Title, [error])
Dim a As New Attachment(temp, "Elmah error log")
wi.Attachments.Add(a)
If wi.IsValid Then
wi.Save()
Return String.Format("{0}|{1}", wi.Id, errorId.ToString)
Else
Dim message As New System.Text.StringBuilder
Dim results = wi.Validate()
Dim isFirst As Boolean = True
For Each r In results
message.AppendLine(String.Format(IIf(isFirst, "{0}", ", {0}"), r))
isFirst = False
Next
Throw New ApplicationException(String.Format("Unable to save the work item becuse the following fields produced a validation error '{0}'.", message.ToString))
End If
End Function
Protected Function GetWorkItemForException(ByVal Title As String, ByVal [error] As [Error]) As WorkItem
Dim wi As WorkItem = GetExistingWorkItem(Title)
If wi Is Nothing Then
wi = CreateNewWorkItem(Title)
End If
m_TemplateDefault.Fields.ApplyFieldValues(wi, False)
ApplyErrorFieldValues(wi, [error])
Return wi
End Function
Private Function GetExistingWorkItem(ByVal Title As String) As WorkItem
' Query for work items
Dim query As String = "SELECT [System.Id], [System.Title] " _
& "FROM WorkItems " _
& "WHERE [System.TeamProject] = @Project " _
& "AND [System.WorkItemType] = @WorkItemType " _
& "AND [System.Title] = @Title " _
& "ORDER BY [System.Id]"
Dim paramiters As New Hashtable
paramiters.Add("Project", m_TemplateDefault.TeamProjectName)
paramiters.Add("WorkItemType", m_TemplateDefault.WorkItemTypeName)
paramiters.Add("Title", m_TemplateDefault.WorkItemTypeName)
Dim y As WorkItemCollection = TfsWorkItemStore.Query(query, paramiters)
Return y(0)
End Function
Private Function CreateNewWorkItem(ByVal Title As String) As WorkItem
Dim wit As WorkItemType = (From t As WorkItemType In TfsProject.WorkItemTypes Where t.Name = m_TemplateDefault.WorkItemTypeName).SingleOrDefault
If wit Is Nothing Then
Throw New ApplicationException(String.Format("Unable to find the work item type '{0}' in the project '{1}'", m_TemplateDefault.WorkItemTypeName, TfsProject.Name))
End If
Dim wi As New WorkItem(wit)
wi.Title = Title
Return wi
End Function
Private Sub ApplyErrorFieldValues(ByVal wi As WorkItem, ByVal [error] As [Error])
For Each i In m_TemplateErrorMap.Fields
Dim value As String = GetPropertyValue(i.Value, [error])
If wi.Fields(i.ReferenceName).AllowedValues.Contains(value) Then
wi.Fields(i.ReferenceName).Value = value
Else
Throw New ApplicationException(String.Format("Unable to set the work item field '{0}' to '{1}' as '{1}' is not in the Allowed Values list.", i.ReferenceName, value))
End If
Next
End Sub
Private Function GetPropertyValue(ByVal path As String, ByVal target As Object) As String
Dim bits() As String = path.Split(".")
Dim ll As New LinkedList(Of String)
Array.ForEach(bits, Function(b) ll.AddLast(b))
Return GetPropertyRecurse(ll.First, target)
End Function
Private Function GetPropertyRecurse(ByVal node As LinkedListNode(Of String), ByVal target As Object) As String
' ToDo: addd ability to support propertyName(0) [arrays]
Dim r As System.Reflection.PropertyInfo = target.GetType.GetProperty(node.Value, BindingFlags.Static Or BindingFlags.Public Or BindingFlags.GetField Or BindingFlags.GetProperty)
If r.PropertyType.IsClass And Not node.Next Is Nothing Then
Return GetPropertyRecurse(node.Next, r.GetValue(target, Nothing))
Else
Return r.GetValue(target, Nothing).ToString
End If
End Function
Private Function GetErrorLogEntryFromTfsAttachement(ByVal wi As WorkItem, ByVal a As Attachment) As ErrorLogEntry
Using reader = XmlReader.Create(a.Uri.ToString)
If Not reader.IsStartElement("error") Then
Return Nothing
End If
Dim errid = String.Format("{0}|{1}", wi.Id, reader.GetAttribute("errorId"))
Dim [error] = ErrorXml.Decode(reader)
Return New ErrorLogEntry(Me, errid, [error])
End Using
Return Nothing
End Function
End Class
Wednesday, July 22, 2009
Even though this sounds like a really simple thing, what if you do not know the name of the controls, and you do not want to have to add a bit of code that you, or another may developer may forget to every piece of code with a timer in it. The problem I have is that if you have a DropDownList on the same page as a update panel that updates based on a timer, you get a little interference.
If the user has the DropDownList open when the update occurs then the DropDownList closes. Very annoying.
The standard FindControl does not work as it requires an ID, so what if all you have is a type?
Well, you need a little extension method :)
Imports System.Runtime.CompilerServices
Imports System.Web
Imports System.Web.UI
Module WebExtensions
<Extension()> _
Friend Sub FindControls(Of T)(ByVal control As Control, ByVal list As List(Of T))
If control.HasControls Then
Dim timers = control.Controls.OfType(Of T)()
list.AddRange(timers)
Dim subcontrols = From c In control.Controls Where Not timers.Cast(Of Control).Contains(c)
For Each c In subcontrols.Cast(Of Control)()
c.FindControls(Of T)(list)
Next
End If
End Sub
<Extension()> _
Friend Function FindControls(Of T)(ByVal control As Control) As List(Of T)
Dim l As New List(Of T)
control.FindControls(Of T)(l)
Return l
End Function
End Module
I am pretty sure this is not the most efficient of code, and any recommendation on improving it are welcome…
Once this is in place it is easily called and actioned upon:
Sub OnPagePreRender(ByVal sender As Object, ByVal e As EventArgs)
' Fix for pages with drop down lists
FixForDropdownListsAndTimers()
End Sub
Private Sub FixForDropdownListsAndTimers()
' Provcess timers
Me.FindControls(Of System.Web.UI.Timer).ForEach(New Action(Of System.Web.UI.Timer)(AddressOf ProcessTimers))
End Sub
Private Sub ProcessTimers(ByVal t As System.Web.UI.Timer)
t.Enabled = Not DisableTimers
End Sub
DisableTimers is set at the page level and filters down to the control so we can now disable all timers on a page when we want.
The FindControls method can find a list of all instances of a control type from an entire page, regardless of the nesting…
UPDATE: OK, so you have probably guessed that I am a complete muppet.. I have changes my UpdatePanels to UpdateMode=“Conditional” and with a few extra lines of code solved my problem the correct way! I will be keeping this little bit of code as you never know when you need to find all instances of a type of control :)… I am such a donkey…
I was asked by a colleague to provide a list of all files that were changed under a particular iteration. Rather than delving into the data, I made a couple of API calls to TFS to output a text file with the list.
This is probably not the most efficient method and it is hard coded, but it does output the goods:
Dim tfs As New TeamFoundationServer("http://testtfs01:8080")
Dim store As WorkItemStore = tfs.GetService(GetType(WorkItemStore))
Dim version As VersionControlServer = tfs.GetService(GetType(VersionControlServer))
' Query for work items
Dim query As String = "SELECT [System.Id], [System.Title] " _
& "FROM WorkItems " _
& "WHERE [System.TeamProject] = @Project " _
& "AND [System.IterationPath] UNDER @IterationPath " _
& "ORDER BY [System.Id]"
Dim paramiters As New Hashtable
paramiters.Add("Project", "TestProject1")
paramiters.Add("IterationPath", "TestProject1\TestIteration1")
Dim y As WorkItemCollection = store.Query(query, paramiters)
Console.WriteLine(String.Format("Found {0} work items", y.Count))
' Query work items for attachments
Dim wiats = From wi As WorkItem In y, l As Link In wi.Links Where l.BaseType = BaseLinkType.ExternalLink Select l, wi
Console.WriteLine(String.Format("Loading {0} changesets...", wiats.Count))
Dim ChangeSets As New List(Of Changeset)
If Not wiats Is Nothing Then
Dim els = From i In wiats Where LinkingUtilities.DecodeUri(CType(i.l, ExternalLink).LinkedArtifactUri).ArtifactType = "Changeset"
For Each i In wiats
Dim el As ExternalLink = CType(i.l, ExternalLink)
Dim artifact As ArtifactId = LinkingUtilities.DecodeUri(el.LinkedArtifactUri)
If artifact.ArtifactType = "Changeset" Then
Dim cs As Changeset = version.ArtifactProvider.GetChangeset(New Uri(el.LinkedArtifactUri))
ChangeSets.Add(cs)
End If
Next
' ------------------------------
Console.WriteLine(String.Format("{0} changesets loaded", ChangeSets.Count))
Dim files = From f In ChangeSets, c In f.Changes Select c.Item Distinct
Using x = System.IO.File.CreateText("c:\Temp\files.txt")
For Each f In files
x.WriteLine(f.ServerItem)
Next
End Using
End If
If Debugger.IsAttached Then
Console.ReadKey()
End If
As you can see I have very bad naming and layout, but this is a one time use version of the code, so quick and dirty. If I am asked to do this again I would create a proper command line utility, or even a WPF interface to display the data prettily.
Sunday, July 19, 2009
I am having a little problem with Visual Studio 2008 that only started after I had installed Office 2010. I found this interesting post on Visual Studio 2008 Locks or Freezes in ASPX from Rinat Abdullin that was a complete match to the problem I am having.
Basically VS just bings at you whenever you click anywhere as if there is a model dialog open after opening and trying to edit an aspx file.
If you get this problem then there is a simple solution, well, one that worked for me. You need to run a repair on the “Microsoft Visual Studio Web Authoring Component” that is part of Office 2007.
You can find the setup in the following locations:
Windows 64bit
C:\Program Files (x86)\Common Files\microsoft shared\OFFICE12\Office Setup Controller\Setup.exe
Windows 32bit
C:\Program Files\Common Files\microsoft shared\OFFICE12\Office Setup Controller\Setup.exe
My guess is that when Office 2010 is installed it has a new version of this component that has not yet been made compatible with Visual Studio 2008…
It was a frustrating couple of hours this morning to figure it out with the bulk of the time taken up with an ineffectual repair of Visual Studio 2008 SP1, ahh well, now I know…
Thursday, July 16, 2009
Hey, well the dentist was fun! And I have had time to install Office 2010 on my wife's Windows 7 laptop. I will try to put together some of the interesting features…
This is nice. Much better than the Google Mail one and is starting to make more sense the more I use it. I am connecting to my Google Mail via IMAP and this example illustrates the feature nicely.
Here is a conversation that has three emails. Aaron sent the first message and both Brian and Neno replied. It shows Brian and Aaron’s email on the same line because Brian’s email is the most recent in the conversation. But Neno also replied and thus it is a split conversation. Think of it like a wee tree with Aaron’s email having 2 branches with Brian’s email being the latest. If you leave it rolled up you never see Aaron’s email, just the latest with the trail in it. This both minimizes the number of emails you have to read, and groups them together so you can follow the conversation.
If you open it out, it shows all three emails, with the replies with the little dots.
Here is an example when all of the replies come in order.
And expanded
Like I said… Nice…
I will need to be quick, I have a dentists appointment (arg!), but I could not leave without at least a peek at Office 2010, so Outlook being my weapon of necessity, that the choice:
You can’s see it from a still, but those colours are dancing about, and I just caught a glimpse of the magic words “Importing user settings”… I am a happy man…
So, its smile time…
I think this form of feedback is both informal and excellent for encouraging user participation…
Usual activation…
Woo, when you select an email you get a nice little tasks list, just what I wanted.

Nice… It does a little conversation stuff and from what I can see its a LOT better than Google… When will this functionality hit Live Mail I wonder…
Well, its dentist time, so that's all I have time for… Will try to install on Windows 7 tonight…
Now I have uninstalled Office 2007 32bit I am ready to get this 2010 thing going…
I am installing on Vista 64, I know, but I cant get Windows 7 in the office yet…
I always do a full install, coz I really hate not having that feature that you need that you did not know that you need ;)
Now, although I am not using very much of the resources on my computer, the install seems to be making it relatively unresponsive.
Not a big deal, but Visual Studio can be unresponsive anyway…so…where did all my productivity go :(
Woo, little smiles:
I am installing office 2010, and I ran into my first gotcha!
I have a 64 bit computer and I wanted to use the 64bit version of Office, but there is no upgrade option for this, you need to uninstall Office 2007 32bit (the only version there is) and put the 64bit on… Here is the message…
This sucks a little, and will suck even more if I can’t keep my settings. Mainly for Outlook, and why I need to uninstall Expression Studio Web I do not know, it may be due to some integration for creating html emails…
This will be a 64 bit adoption blocker for companies as this will be difficult to automate…
I think with the prevalence of 64bit hardware, the release of Windows 7, and the RAM now coming as standard in new computers getting above 3.5GB there will be a lot more 64 bit upgrades and this needs to be looked at… I don’t know if it is possible, but can’t the install just uninstall my previous versions? Maybe this is just a Technology Preview limitation…