Importance of specifying AncestorType with x:Type in RelativeSourceBinding

I am really very happy right now, for the past 4 days me and my team were trying to solve a very trivial bug in our application and finally it got resolved by just adding x:Type in our RelativeSource bindings;Surprised smileCan you believe this, a production issue getting resolved just by this simple change, I couldn’t; so I tested this fix at least ten times to believe itSecret telling smile

Just to give you a background,

I am working on developing components for a very large scale financial product having plug-in architecture and multiple versions of same plug-in can be loaded at the same time.

XAML and WPF have been conceived to work on an application that never loads more than one version of the same assembly on the same AppDomain; the code generated by WPF for loading XAML resources does not use strong names and so you can not place .Xaml files on an assembly where multiple versions of that same assembly are loaded.

In simple words if you have a wpf control/window in your application and multiple versions of your assembly are loaded then your application may use xaml file from older version or cause an IOException. Actually when XAML file is parsed, the first available assembly matching provided namespace is used, regardless of the version.

Exact scenarios and other details about this can be found in this SO tread and this connect bug -

How to force WPF to use resource URIs that use assembly strong name? Argh!

XAML generated code uses resource URI without assembly strong name

So we are loading the xaml files by appending the current assembly version as explained in above threads. This works well and always correct xaml file gets loaded even if multiple versions of assembly exist in memory.

Everything was working correctly except that if we load a template created in previous version of our application into newer version then application behaved weirdly and no data was displayed;

On inspecting closely we found that in this case assemblies from both the versions(old and current) were loaded into memory. This was a valid scenario as per the design and must be supported. Our first reaction was that some of our controls/xaml are getting loaded from wrong(old) assembly; we tried to look into our code and find the culprit control/xaml, but couldn’t find anything wrong. All the xaml files were loaded correctly, we came to this conclusion after snooping the application and checking that all the changes of current version are reflected in Visual Tree created. We checked our deserialization logic and that too was working correctly.

We kept debugging the application, analyzed whole visual tree through Snoop, checked DataContext of controls, bindings etc. etc. but nothing concrete came up. One thing we noticed was that a lot of our bindings were failing in this case.

MatrixBindingError1

On close inspection we noticed that all the failed bindings were using Relative source binding to find the ancestor control. Digging deeper into binding through Snoop pointed that wrong assembly was used by the binding to  point to the ancestor type

Snoop: Delve Binding –> RelativeSource –> AncestorType –> Assembly

We were clueless about why it was so, the xaml was getting loaded from correct assembly but the binding inside that was pointing to a type from wrong assembly.I don't know smileI mean how the hell is that even possible. Here is an e.g. of the binding –

Tag="{Binding RelativeSource={RelativeSource FindAncestor,

AncestorType= PriceEditor:PriceEditorControl}, Path=Rights}

It worked perfectly alright till now and nothing is wrong in the binding. After some thinking I noticed that only thing missing from this binding was x:Type, which is generally used for pointing to types, so I changed the binding to use the x:Type -

Tag="{Binding RelativeSource={RelativeSource FindAncestor,

AncestorType={x:Type PriceEditor:PriceEditorControl}}, Path=Rights}"

Eureka, problem solved; everything worked perfectly fine. I was still surprised to see this, as per my understanding there should be no difference between directly using the type name and using x:Type. In first case WPF will use a TypeConverter and in second case a TypeExtension will be used but both are supposed to perform identical operation. But it looks like

x:Type considers the strong name or the version of the assembly but TypeName-As-String doesn’t.

I would still be interested in knowing the internal difference between the two but that’s subject for another blog post. Right now I am happy that issue is resolved and now I will never forget to use x:Type while specifying the types in XAML Smile

No Comments