Infosys Microsoft Alliance and Solutions blog

« A Refactorability Approach to Software Reusability | Main | WPF TextBox Memory Issue »

WPF XmlDataProvider, Working with External XML Files

Recently on the WPF Forum I came across an interesting problem about use of XmlDataProvider in XAML and its ability to load external XML files. It took a bit of a struggle, but eventually I did manage to get it working. Here I will try and explain it in some detail so that others can also benefit.

We all know that one can easily bind to inline XML in XAML or can load that from external file. If working with inline XML, you usually use XData to specify the XML file content.

        <XmlDataProvider x:Key="Persons" XPath="persons/person">

            <x:XData>

                <persons xmlns="">

                    <person name="Person 1" age="20"/>

                    <person name="Person 2" age="30"/>

                    <person name="Person 3" age="40"/>

                </persons>

            </x:XData>

        </XmlDataProvider>

If working with external file, the Source attribute is set to point to the specific XML File.

        <XmlDataProvider x:Key="xmlFileRes" Source="XMLFile1.xml" XPath="persons/person" />

Note that when you add a new XML file to the project, by default, it is marked as Resource. This means that it will be compiled as a resource inside of the application assembly and personally I don't see much value in that. XML files are typically used so that you can modify them without having to recompile the code, so set the properties of the XML file as
Build Action - Content
Copy to Output Directory - Copy if newer

You can also set Copy to Output Directory to Copy always if you prefer it. That said, if you there is no need for XML file to be modified, you can leave it as a Resource. Also if you do want the XML file to be externally modifyable, take care of appropriate updating to Application logic to ensure that it can pick up the latest XML.

With the above XML you can easily bind this to say a ListBox as

        <ListBox ItemsSource="{Binding Source={StaticResource xmlFileRes}}" ItemContainerStyle="{StaticResource listStyle}" Height="70" Name="list" />

In my case, I have used a style for the ListBox which is defined as below.  

        <Style x:Key="listStyle" TargetType="{x:Type ListBoxItem}">

            <Setter Property="Template">

                <Setter.Value>

                    <ControlTemplate>

                        <TextBlock Text="{Binding XPath=@name}" />

                    </ControlTemplate>

                </Setter.Value>

            </Setter>

        </Style>

Compile and run and all works fine. If you don't see any content in the ListBox, make sure that the settings on the XML file are properly done and physically verify that the XML file is indeed present in the same path as the Application executable.

This entire logic can be moved inside of a UserControl and all still works fine. However the issue occurs if this is moved inside of a new project altogether. In my case, I created a new WPF User Control library and created a User Control inside of it. In the VS and Expression designer the user control loads fine, but when added to the Window of my parent project and executed, the XML files to load. The output window shows that some error occured while loading the XML file -

System.Windows.Data Error: 43 : XmlDataProvider cannot load asynchronous document from Source because of load or parse error in XML stream.; Source='<null>' IOException:'System.IO.IOException: Cannot locate resource 'ucxmlfile1.xml'.
   at MS.Internal.AppModel.ResourcePart.GetStreamCore(FileMode mode, FileAccess access)
   at System.IO.Packaging.PackagePart.GetStream(FileMode mode, FileAccess access)
   at System.IO.Packaging.PackWebResponse.CachedResponse.GetResponseStream()
   at System.IO.Packaging.PackWebResponse.GetResponseStream()
   at System.Windows.Data.XmlDataProvider.CreateDocFromExternalSource(WebRequest request)'

If you look at this strack trace, the call seems to have orginated from XmlDataProvider.CreateDocFromExternalSource(WebRequest). I don't know why the data provider is thinking this to be an XML coming from Web, but this could be the reason for the failure to load. Looks like some bug in the way the XML file is loaded.

To fix this,  here is what I did in the user control's code behind. I modified the constructor logic to as below

        public UserControl1()

        {

            InitializeComponent();

 

            XmlDataProvider xdp = this.TryFindResource("xmlFileRes") as XmlDataProvider;

            if (xdp != null)

            {

                XmlDocument doc = new XmlDocument();

                doc.Load("UCXMLFile1.xml");

                xdp.Document = doc;

                xdp.XPath = "persons/person";

            }

        }

With this change, the application now works fine. You might wonder if we are modifying the Source of the XmlDataProvider in the code behind, do we really need to specific it in the XAML also. It isn't necessary and you can very well modify the XAML as below. However I didn't do it to continue to get designer support. If you remove the Source and XPath details from XAML, the control will not display anything in the designer view and hence designing the layout may become an issue.  

        <XmlDataProvider x:Key="xmlFileRes" />

That said, things aren't still completely fixed. If only both VS and Expression designer's would behave in the same way !!! If you open the Window.XAML in Expression it looks fine and is even able to load the UserControl and show the XML contents inside it as expected. However for some reason now the VS designer goes for a toss. You may see a "Could not create an Instance of type 'UserControl'" error and the VS designer fails to load. To fix this, you can further modify the UserControl's constructor as below.

        public UserControl1()

        {

            InitializeComponent();

 

            if (DesignerProperties.GetIsInDesignMode(this))

                return;

 

            XmlDataProvider xdp = this.TryFindResource("xmlFileRes") as XmlDataProvider;

            if (xdp != null)

            {

                XmlDocument doc = new XmlDocument();

                doc.Load("UCXMLFile1.xml");

                xdp.Document = doc;

                xdp.XPath = "persons/person";

            }

        }

Now both VS and Expression designer work fine, but since the XML load logic isn't executed in design time, the XML contents that were earlier visible in Expression are not visible anymore. You will only see the bounds of the control, but no data inside of it. So based on which designer you prefer to work with, you can take a call of how to write the code.

BTW, I have used Visual Studio 2008 RTM and Expression Blend 2.5 March 2008 Preview. 

 

 

Comments

Thank you for this one!

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Please key in the two words you see in the box to validate your identity as an authentic user and reduce spam.

Subscribe to this blog's feed

Follow us on

Blogger Profiles

Infosys on Twitter