WPF app crashes after migration to TFS 2017

By Mirek on (tags: AssemblyVersion, msbuild, resource dictionary, TFS 2017, WPF, XAML, categories: architecture, tools)

Recently we’ve migrated our projects repository from TFS 2010 to TFS 2017. Unfortunately it soon turned out that WPF projects doesn’t cooperate with new TFS.

Well, actually they did cooperate well with TFS but didn’t cooperate with the User. The build process went well with no warnings and the application was properly deployed. However after the app was deployed and we wanted to run it the problem revealed.  The application crashed just after start with no exception or message. The only trace left could be found in the events log:

Application: eidias.wpfapp.wpf.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.InvalidOperationException at System.Windows.ResourceDictionary.SetDeferrableContent(System.Windows.DeferrableContent) at System.Windows.Baml2006.WpfSharedBamlSchemaContext+<>c.<Create_BamlProperty_ResourceDictionary_DeferrableContent>b__297_0(System.Object, System.Object) at System.Windows.Baml2006.WpfKnownMemberInvoker.SetValue(System.Object, System.Object) at MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(System.Xaml.XamlMember, System.Object, System.Object) at MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(System.Object, System.Xaml.XamlMember, System.Object) Exception Info: System.Windows.Markup.XamlParseException at System.Windows.Markup.WpfXamlLoader.Load(System.Xaml.XamlReader, System.Xaml.IXamlObjectWriterFactory, Boolean, System.Object, System.Xaml.XamlObjectWriterSettings, System.Uri) at System.Windows.Markup.WpfXamlLoader.LoadBaml(System.Xaml.XamlReader, Boolean, System.Object, System.Xaml.Permissions.XamlAccessLevel, System.Uri) at System.Windows.Markup.XamlReader.LoadBaml(System.IO.Stream, System.Windows.Markup.ParserContext, System.Object, Boolean) at System.Windows.Application.LoadComponent(System.Object, System.Uri) at eidias.wpfapp.wpf.App.InitializeComponent() at eidias.wpfapp.wpf.App.Main()

Besides that, there were no other information about the source of the problem. After deep investigation it turned out to be somehow connected with the collection of installed SDK’s on the build machine. Our application targeted the .Net Framework 4.5. However we couldn’t find out what is responsible for causing the problem.

Further more during investigation, when we tried to isolate the source of the problem, we’ve stumbled upon some strange situation.
The application built within the TFS build process was failed to start. So we took the exact same msbuild command that TFS build step uses and run it locally from regular command prompt on the build agent machine (we took the command from the log of the build process; when you run the build process with system.debug=true you get all the commands fired by the process with exact arguments). The result was that the application builded up the same way, used exaclty same tool paths, produced same build logs, but this time it was running  with no problem ! Having that we’ve decided to disassemble and compare both version of compiled app and see what are the differences.

Surprisingly the only difference was connected with the XAML resources file loaded during application startup . In each view code behind there is an InitializeComponent() method, where the resource locater object is created. In this case we had

Uri resourceLocater = new Uri("/eidias.wpfapp.wpf;component/app.xaml", UriKind.Relative);

in the working version of the application and

Uri resourceLocater = new Uri("/eidias.wpfapp.wpf;V1.0.8.8858;component/app.xaml", UriKind.Relative);

in non working version of the application. Somehow the TFS build process added the version number to the Uri of the view xaml and that caused the xaml content could not be located. I have no idea why is that and what is the cause for that.
Fortunately we’ve found a quick solution for this problem. It turned out that there need to be some changes made in the application code, fortunately not really significant changes. In the App.xaml file, where we define the resource dictionaries we had to change this

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/eidias.wpfapp.wpf;component/Resources/Brushes.xaml" />
    <ResourceDictionary Source="/eidias.wpfapp.wpf;component/Resources/Common.Style.xaml" />
    <ResourceDictionary Source="/eidias.wpfapp.wpf;component/Resources/Images.xaml" />
    <ResourceDictionary Source="/eidias.wpfapp.wpf;component/Resources/Button.Style.xaml" />
</ResourceDictionary.MergedDictionaries>

into this

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="../Resources/Brushes.xaml" />
    <ResourceDictionary Source="../Resources/Common.Style.xaml" />
    <ResourceDictionary Source="../Resources/Images.xaml" />
    <ResourceDictionary Source="../Resources/Button.Style.xaml" />
</ResourceDictionary.MergedDictionaries>

and that solved the problem.
If anyone have an idea or reasonable explanation, I'm open for comments. For now I leave it as it is and go forward. Already spent way too much time on researching this issue.

The problem might be related to this.

UPDATE: Another case that might cause the same problem is when your resource dictionary is located in separate assembly. In that case we need to use full Pack URI

So instead of referencing the dictionary like this

 <ResourceDictionary Source="/eidias.common;component/Styles/Brushes.xaml" />

we have to write it like this

<ResourceDictionary Source="pack://application:,,,/eidias.common;component/Styles/Brushes.xaml" />

UPDATE 2: Problem started to occur again when I wanted to build another WPF project. The fixes described above did not work so I started to isolate the case. I get to the point where the build process contained only a MSBuild step so only building a solution without any additional steps.

Clipboard03

The output application did not work again. By looking into an obj folder and inspecting the *.g.cs file generated for the App.xaml file I realized that the version number is still added inside the Uri. Finally it turned out the the version number was not the number stored in project files but the number defined in build custom variable called AssemblyVersion

Clipboard02

The build somehow take this variable into account no matter if I use it anywhere or not, and incorporates it while generating Uri for XAML locator. I have no idea why this behaves like this, but simply renaming the variable to anything else than AssemblyVersion seemed to be a solution for the problem.