dotnet / dotnet/wpf

XAMLWriter.Save throwing exception on generics

Open
#9,569 8 comments 1 reaction 0 assignees View on GitHub
Investigate
Dominant language
C#
Stars
7.7k
Forks
1.3k
Avg merge
1d 11h
Merged PRs (30d)
61

Description

### Description

I have made a WPF dropdown component that is generic, so I can make a Combobox a little less tedious to work with:

```csharp
public class MyDropDown : UserControl
{
private readonly ComboBox _comboBox = new();

public MyDropDown()
{
_comboBox.SelectionChanged += ComboBox_SelectionChanged;
Content = _comboBox;
}

public IEnumerable Items
{
get => (IEnumerable)_comboBox.ItemsSource;
set => _comboBox.ItemsSource = value;
}

public Func DisplayFunc
{
init => _comboBox.ItemTemplate = CreateDataTemplate(value);
}

public Action SelectionChangedAction { get; init; }

private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_comboBox.SelectedItem is T selectedItem)
{
SelectionChangedAction(selectedItem);
}
}

private DataTemplate CreateDataTemplate(Func displayFunc)
{
var dataTemplate = new DataTemplate(typeof(T));
var factory = new FrameworkElementFactory(typeof(TextBlock));
factory.SetBinding(TextBlock.TextProperty, new System.Windows.Data.Binding
{
Converter = new FuncValueConverter(displayFunc),
Mode = System.Windows.Data.BindingMode.OneWay
});
dataTemplate.VisualTree = factory;
return dataTemplate;
}

// Converter for converting the Func to a binding-friendly format
private class FuncValueConverter : System.Windows.Data.IValueConverter
{
private readonly Func _func;

public FuncValueConverter(Func func)
{
_func = func;
}

public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
{
return value is TInput input ? _func(input) : default;
}

public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
{
return value is TOutput output ? output : default;
}
}
}
```

### Reproduction Steps

I have a basic test using Xunit with XUnit WpfFact nuget, where I would check for elements in the string:

```csharp
[WpfFact]
public void Test2()
{
var uiElement = new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new MyDropDown()
{
Items = new[] { "One", "Two", "Three" },
DisplayFunc = x => x,
SelectionChangedAction = x => { }
}
}
};

var result = XamlWriter.Save(uiElement);
_outputHelper.WriteLine(result);

Assert.Contains("One", result);
}
```

### Expected behavior

XAMLWriter.Save(...) to return something like:

```xml

// Items

```

### Actual behavior

Throws exception:

```plaintext
System.InvalidOperationException: Cannot serialize a generic type 'Frank.Wpf.Tests.MyDropDown`1[System.String]'.

System.InvalidOperationException
Cannot serialize a generic type 'Frank.Wpf.Tests.MyDropDown`1[System.String]'.
at System.Windows.Markup.Primitives.MarkupWriter.VerifyTypeIsSerializable(Type type)
at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope)
at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item, Scope scope)
at System.Windows.Markup.Primitives.MarkupWriter.WriteItem(MarkupObject item)
at System.Windows.Markup.Primitives.MarkupWriter.SaveAsXml(XmlWriter writer, MarkupObject item)
at System.Windows.Markup.XamlWriter.Save(Object obj)
at Frank.Wpf.Tests.XamlSerializerTests.Test2() in D:\frankrepos\Frank.Wpf\Frank.Wpf.Tests\XamlSerializerTests.cs:line 54
at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

```

### Regression?

_No response_

### Known Workarounds

Writing one's own "serializer", or rewriting to not use a generic

### Impact

Edge case maybe, but I often include a secret key-combo in my WPF apps that will display a "raw dump" of the XAML and whatever else is loaded in the current window

### Configuration

Windows 11
.net 8

### Other information

Documentation don't state that this is an unreasonable expectation, (accepting generic UiElements): https://learn.microsoft.com/en-us/dotnet/desktop/wpf/advanced/serialization-limitations-of-xamlwriter-save?view=netframeworkdesktop-4.8&viewFallbackFrom=netdesktop-8.0

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.