DyslexicDev (Martin Hinshelwood's Blog)

A Scottish dyslexic software developer: Team System MVP, .NET architect, developer, evangelist, technology enthusiast and multi-dimensional free thinker
posts - 391, comments - 517, trackbacks - 57

My Links

News

Disclaimer

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




TwitterCounter for @hinshelm



Locations of visitors to this page

Subscribe

Personal

Twitter












Tag Cloud

Article Categories

Archives

Post Categories

Image Galleries

Blogs I read

Blogs of Friends

Multi-Dimentional Free Thinking Bloggers

Personal

Projects

VSTS

Monday, November 02, 2009

Dyslexia Awareness Week

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...

British Dyslexia Association: Dyslexia Awareness Week: Dyslexia Strenths

posted @ Monday, November 02, 2009 4:17 PM | Feedback (2) | Filed Under [ Personal Dyslexia ]

Sunday, October 25, 2009

Deploying Visual Studio 2010 Team Foundation Server Beta 2 - Done

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:

  1. Backed up TFS 2008 databases (Some 14GB of data)
  2. Restored databases to new 64 bit server
  3. Installed TFS 2010 Beta 2 64 bit
  4. Run the Upgrade of 2008 data to 2010 Beta 2
  5. 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.

 image

Figure: Web Access – Working

VS2010

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…

RulestoBetter

posted @ Sunday, October 25, 2009 3:46 AM | Feedback (5) |

A change for the better #2

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…

SSWLogo

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…

RulestoBetter

posted @ Sunday, October 25, 2009 12:39 AM | Feedback (0) |

Tuesday, October 20, 2009

Configuring Visual Studio 2010 Team Foundation Server on Vista in 12 minutes

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

clip_image001

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...

Team Foundation Server Configuration

You can pick basic and it is...well...basic. It will install everything to the defaults.

Team Foundation Server Configuration - Advanced

I'm picking Advanced because I want to be able to select a pre-existing SQL Express instance...

Team Foundation Server Configuration - Advanced - Install

Team Foundation Server Configuration - Advanced - Database

You can enter a label if you want to have more than one TFS Configuration database in the same SQL instance.

Team Foundation Server Configuration - Advanced - Account

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... Team Foundation Server Configuration - Advanced - Application Tier

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.

Team Foundation Server Configuration - Advanced - Project Collection

Ok, I have a default collection, but only because I am lazy...

Team Foundation Server Configuration - Advanced - Review All done, now to apply it.

 Team Foundation Server Configuration - Advanced - Rediness Checks

No, wait, we need to check all of the system requirements!

Team Foundation Server Configuration - Advanced - Configure

Now, usually this is the time to break out a cup of team, and maybe have a siesta. Lets see how long it takes...

Team Foundation Server Configuration - Advanced - Configure after 20 seconds

..30 seconds...

Team Foundation Server Configuration - Advanced - Configure after 40 seconds

...50 seconds...

Team Foundation Server Configuration - Advanced - Configure after 50 seconds

.. 1 minute...

Team Foundation Server Configuration - Advanced - Configure after 60 seconds

..Whoa, that was less than 2 minutes for the whole process.

Team Foundation Server Configuration - Advanced - Configure - Complete 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...

posted @ Tuesday, October 20, 2009 2:25 PM | Feedback (2) |

Installing Visual Studio 2010 Team Foundation Server on Windows Vista in 3 minutes

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 :)

image

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...

Microsoft Team Foundation Server 2010 Install

Microsoft Team Foundation Server 2010 Install - Start Page

 

Microsoft Team Foundation Server 2010 Install - Options Page

Microsoft Team Foundation Server 2010 Install - Install Page

Microsoft Team Foundation Server 2010 Install - Finish Page

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...

posted @ Tuesday, October 20, 2009 1:48 PM | Feedback (5) |

Interview with Scottish Developers

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…

 

posted @ Tuesday, October 20, 2009 6:45 AM | Feedback (3) |

Monday, October 19, 2009

Visual Studio 2010 Beta 2 is available Now!

Visual Studio 2010 Beta 2 is now available on MSDN for download!

clip_image001

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

clip_image002

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

posted @ Monday, October 19, 2009 7:03 PM | Feedback (0) |

Monday, August 31, 2009

Wpf Scale Transform Behaviour

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.

image

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>
image

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.

image

 

 

 

krsu46zvpt

posted @ Monday, August 31, 2009 8:48 AM | Feedback (0) |

Tuesday, August 25, 2009

Wpf Ninject Dojo: The Data Provider

You have probably heard me go on about Unity a couple of times:

I have been using what is now 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: .

I decided on my latest project (a Wpf dashboard for HEAT ITSM) that I needed dependency injection. Whenever I start building a 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 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 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 in action.

image  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 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 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 .

Start your Ninja training today!

 

posted @ Tuesday, August 25, 2009 2:43 PM | Feedback (0) |

Friday, August 21, 2009

Second blogger from my office

One of my colleagues is facing the maelstrom that is corporate blogjection and has become . 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: ,

posted @ Friday, August 21, 2009 2:46 PM | Feedback (1) |

Thursday, August 20, 2009

Silverlight 3

Over the past week I have been reading the new book Silverlight 3 Programmer's Referencefrom 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…

Technorati Tags: ,,

posted @ Thursday, August 20, 2009 7:50 PM | Feedback (0) |

Monday, August 17, 2009

Updating the Command Line Parser

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.

image

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…

image

With the values coming from the relevant places:

image

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

Technorati Tags: ,,

posted @ Monday, August 17, 2009 1:11 AM | Feedback (1) |

Friday, August 14, 2009

Wpf Drag & Drop behaviour

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 of Microsoft. In her 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.

InsertionAdorner

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

image 

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.

image

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

image

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.

 image

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.

Technorati Tags: ,,,

posted @ Friday, August 14, 2009 4:16 AM | Feedback (2) |

Thursday, August 06, 2009

The long wait is over

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.

image

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: ,

posted @ Thursday, August 06, 2009 3:40 AM | Feedback (1) |

Thursday, July 30, 2009

Finding features: Calendar preview

Another nice feature of Outlook 2010 that I like is the Calendar preview:

 image

Very effective for seeing quickly wither you can attend :)

posted @ Thursday, July 30, 2009 9:38 PM | Feedback (0) |

Creating a Data Access layer using Unity

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!

image

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.

image

' 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.

image

'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.

image

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 :)

posted @ Thursday, July 30, 2009 4:57 AM | Feedback (0) |

Monday, July 27, 2009

A perfect match TFS and DLR

ConfigurationRequired 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.

ar123456585516148Once 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 :)

 

 

posted @ Monday, July 27, 2009 10:25 PM | Feedback (3) |

Sunday, July 26, 2009

Log Elmah errors in Team Foundation Server

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

posted @ Sunday, July 26, 2009 10:30 PM | Feedback (1) |

Wednesday, July 22, 2009

Disable a timer at every level of your ASP.NET control hierarchy

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…

posted @ Wednesday, July 22, 2009 10:26 PM | Feedback (0) |

List all files changed under an Iteration

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.

posted @ Wednesday, July 22, 2009 12:56 AM | Feedback (0) |

Sunday, July 19, 2009

Office 2010 gotcha 2: Visual Studio 2008 Locks

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 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.

image

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…

posted @ Sunday, July 19, 2009 8:51 PM | Feedback (12) |

Thursday, July 16, 2009

Finding features: Conversations

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.

image

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.

image

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.

image

And expanded

image

Like I said… Nice…

posted @ Thursday, July 16, 2009 8:55 AM | Feedback (0) |

Office 2010 First run

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:

image

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…

image

I think this form of feedback is both informal and excellent for encouraging user participation…

Usual activation…

image

Woo, when you select an email you get a nice little tasks list, just what I wanted.

image

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…

image

Well, its dentist time, so that's all I have time for… Will try to install on Windows 7 tonight…

posted @ Thursday, July 16, 2009 3:35 AM | Feedback (2) |

Office 2010 Install

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…

image

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 ;)

image

image

Now, although I am not using very much of the resources on my computer, the install seems to be making it relatively unresponsive.

image 

Not a big deal, but Visual Studio can be unresponsive anyway…so…where did all my productivity go :(

image

Woo, little smiles:

image 

posted @ Thursday, July 16, 2009 2:59 AM | Feedback (0) |

Installing Office 2010 gotcha 1

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…

image

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…

posted @ Thursday, July 16, 2009 1:55 AM | Feedback (1) |

Powered by: