fsprojects / FsXaml

F# Tools for working with XAML Projects
http://fsprojects.github.io/FsXaml/
MIT License
172 stars 48 forks source link

FsXaml will sometimes fail to report the real problem with a XAML file #81

Open BillHally opened 4 years ago

BillHally commented 4 years ago

Description

FsXaml will sometimes fail to report the real problem with a XAML file, instead reporting:

System.Xaml.XamlObjectWriterException: XAML Node Stream: Missing CurrentObject before EndObject.

Repro steps

  1. Edit the file demos/WpfSimpleMvvmApplication/MainView.xaml so that its UserControl.Resources section changes from:
<fsxaml:BooleanToCollapsedConverter x:Key="TrueToCollapsed" />

to:

<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/FsXaml.Wpf;component/NoSuchResourceDictionary.xaml" />
    </ResourceDictionary.MergedDictionaries>
    <fsxaml:BooleanToCollapsedConverter x:Key="TrueToCollapsed" />
</ResourceDictionary>

This inserts a reference to a ResourceDictionary that doesn't exist.

  1. Edit the project file to change the OutputType from WinExe to Exe (so that if you run the app from the command line, the exception is visible)

  2. Build and run the application

Expected behavior

The exception raised provides the error message explaining what the problem is i.e. that the resource can't be found.

Unhandled Exception: System.Windows.Markup.XamlParseException: 'The invocation of the constructor on type 'Views.MainView' that matches the specified binding constraints threw an exception.' Line number '12' and line position '19'. ---> System.Xaml.XamlException: Error parsing XAML contents from MainView.xaml.
  Error at line 0, column 0. ---> System.Xaml.XamlObjectWriterException: Set property 'System.Windows.ResourceDictionary.Source' threw an exception. ---> System.IO.IOException: Cannot locate resource 'nosuchresourcedictionary.xaml'.

Actual behavior

The exception reports "XAML Node Stream: Missing CurrentObject before EndObject".

Unhandled Exception: System.Windows.Markup.XamlParseException: 'The invocation of the constructor on type 'Views.MainView' that matches the specified binding constraints threw an exception.' Line number '12' and line position '19'. ---> System.Xaml.XamlObjectWriterException: XAML Node Stream: Missing CurrentObject before EndObject.

Explanation

This happens because the XamlObjectWriter created in InjectXaml.from will sometimes (depending on the XAML being handled) raise an exception when it goes out of scope, preventing the useful exception the code raises from propagating.

Proposed fix

Explicity closing it in a try ... catch block avoids this problem, allowing the useful exception to propagate as normal, i.e. inserting this:

try writer.Dispose() with _ -> ()

into the relevant exception handler:

use writer = new XamlObjectWriter(reader.SchemaContext, writerSettings)

try
    while reader.Read() do                
        writer.WriteNode(reader)
with 
| :? XamlException as xamlException -> // From writer

    // Explicitly close the writer. Otherwise, as it is disposed
    // when it goes out of scope it may raise an exception, which will
    // prevent the propagation of the helpful one we're just about to raise
    try writer.Dispose() with _ -> () <---------------------------- HERE

    let message =
        if reader.HasLineInfo then
            sprintf "Error parsing XAML contents from %s.\n  Error at line %d, column %d.\n  Element beginning at line %d, column %d." file xamlException.LineNumber xamlException.LinePosition reader.LineNumber reader.LinePosition
        else
            sprintf "Error parsing XAML contents from %s.\n  Error at line %d, column %d." file xamlException.LineNumber xamlException.LinePosition
    raise <| XamlException(message, xamlException)
| :? System.Xml.XmlException as xmlE  -> // From reader
    let message =
        if reader.HasLineInfo then
            sprintf "Error parsing XAML contents from %s.\n  Error at line %d, column %d.\n  Element beginning at line %d, column %d." file xmlE.LineNumber xmlE.LinePosition reader.LineNumber reader.LinePosition
        else
            sprintf "Error parsing XAML contents from %s.\n  Error at line %d, column %d." file xmlE.LineNumber xmlE.LinePosition                        
    raise <| XamlException(message, xmlE)
writer.Result
|> ignore