dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.1k stars 1.17k forks source link

Allow registering xmlns prefixes using the `using:[namespace]` syntax #45

Open dotMorten opened 6 years ago

dotMorten commented 6 years ago

It would be great if we could extend the syntax for registering xmlns prefixes to adopt the pattern used by UWP ( documented here ), and achieve a little more consistency between the two XAML Flavors.

Currently we have two options:

  1. Use the namespace and assembly name with the following syntax:
    <MyObject xmlns="clr-namespace:MyClassNamespace;assembly=MyAssemblyName" />
  2. The second option is to register a prefix in the assembly itself using the xmlnsprefix attribute:
    [assembly: XmlnsPrefix("http://schemas.dotMorten.com/2018", "custom")]
    [assembly: XmlnsDefinition("http://schemas.dotMorten.com/2018", "MyClassNamespace")]
    <MyObject xmlns="http://schemas.dotMorten.com/2018" />

The first one is rather verbose and tied to a single specific assembly, and the second one has to be done at the assembly level, which isn't possible if you don't own the assembly. Secondly it's yet-another-difference from UWP XAML, which could easily be remedied with the following syntax:

<MyObject xmlns="using:MyClassNamespace" />

I made a tiny prototype in System.Xaml to accomplish this. While it's far from the most efficient way, it proves that it can be done in WPF, by adding the following to XamlSchemeContext.cs in the GetXamlNamespace method:

            if (xmlns.StartsWith("using:"))
            {
                var ns = xmlns.Substring(6);
                foreach(var a in AppDomain.CurrentDomain.GetAssemblies())
                {
                    if (a.ExportedTypes.Where(t => t.GetTypeInfo().Namespace == ns).Any())
                    {
                        if(xamlNamespace == null)
                            xamlNamespace = new XamlNamespace(this);
                        xamlNamespace.AddAssemblyNamespacePair(new AssemblyNamespacePair(a, ns));
                        continue;
                    }
                }
                if (xamlNamespace != null)
                    return xamlNamespace;
            }

(also I got a branch with this change here) There's obviously a performance issue with this approach, as it requires iterating all loaded assemblies each time the namespace declaration is encountered, but considering assemblies getting loaded and unloaded is already being tracked, I think it should be possible to piggy-back on that and maintain a cache of namespaces in each assembly.

Screenshot from this implementation in use: image

I'd be happy to volunteer for this work, but would appreciate a few tips wrt ensuring performance and caching of the lookups.

60

dotMorten commented 6 years ago

Note from @rrelyea :

Great start. We’d also need to modify presentationbuildtasks.dll’s markupcompiler...which unfortunately does use system.xaml yet. Still using v3 parser. And then work with xaml editors to support— which they likely do if they design for UWP too.

mstrobel commented 5 years ago

The fact that this only works with loaded assemblies seems like a big problem to me. Following MVVM principles, it is entirely possible for certain UI-related assemblies to only be referenced in Xaml. If nothing triggers those assemblies to load before a Xaml document is parsed, the references won’t be resolved.

I think @taori is on the right track with #96. If you could provide an assembly-qualified namespace in XmlnsDefinition, then the problem of having to “own” the namespace-mapped assembly disappears. You could add custom CLR to XML namespace mappings to your own assemblies.

dotMorten commented 5 years ago

@mstrobel My implementation was a proof-of-concept hack. As Rob had mentioned, there are many more pieces where a feature like this needs to be plugged in.

mstrobel commented 5 years ago

Actually, my objection really only matters when parsing loose Xaml. I’d forgotten that the xml namespaces get remapped to assembly-qualified CLR namespaces when compiling to Baml, so for the vast majority of cases it shouldn’t matter if the assemblies are loaded or not; the Baml parser will still be able to resolve the types within.