How do I map a type from a CLR namespace in XAML from an assembly other than the one where it's declared?

后端 未结 1 336
被撕碎了的回忆
被撕碎了的回忆 2021-01-18 04:07

In XAML, I would like to use types from two different assemblies, each with their own namespace. Rather than declaring the namespaces explicitly in an xmlns:

相关标签:
1条回答
  • 2021-01-18 04:51

    If I understand correctly, your question boils down to this:

    Having xmlns:units="clr-namespace:Gu.Units;assembly=Gu.Units" works fine but it is what I'm trying to avoid.

    Is there a way to do this?

    The answer: no, there is not any way to avoid declaring some XML namespace prefix.

    1. The [XmlnsPrefix] attribute has no effect on manually-authored XAML. I.e. it does not introduce an xmlns prefix to the scope of the XAML. It is simply a marker that XAML-authoring tools can retrieve so that if and when they autogenerate XAML declarations, they have a way to choose the xmlns prefix to use. E.g. if you use the VS Designer to add a new object from a referenced library, it can look in that library to know that when adding the object to the XAML, it needs to add the appropriate xmlns: attribute to the outer container element, and use the specified prefix in the XAML where the object was added.

    2. Neither of these attributes have any effect on XAML authored in the same assembly in which the attributes are specified. The attributes are useful only in a fully-compiled assembly, and of course you don't get that until the XAML in the assembly has been compiled. So that XAML can't use the attributes.

    3. That leaves the [XmlnsDefinition] attribute, when specified in an assembly that is referenced by the assembly containing the XAML currently being edited. In this scenario, the attribute is useful but still does not allow you to forego the xmlns: attribute declaration. Instead what it does is allows you to map a URI (e.g. http://Gu.com/Units) to one or more CLR namespaces. In this way, a single xmlns: prefix attribute declaration can a) refer to more than one CLR namespace, and b) do so without having the authored XAML have to actually name any CLR namespace specifically (i.e. it encapsulates the managed-code aspect of the assembly reference, hiding that behind a URI).

      In this way, instead of writing xmlns:units="clr-namespace:Gu.Units;assembly=Gu.Units", you can write xmlns:units="http://Gu.com/Units" and that will allow the units xmlns prefix to qualify any type from any of the CLR namespaces that were attached to the http://Gu.com/Units URI via an [XmlnsDefinition] attribute.

      But you still have to declare the XML namespace prefix. It's just that the declaration takes a different form than would otherwise be needed.


    Note: when multiple assemblies use the [XmlnsDefinition] attribute to declare CLR namespaces in the same URI as each other, all of the namespaces are referred to by that URI in XAML that references all of those assemblies. You can take advantage of this to join your own library's namespaces with those of a URI that you expect will already be referenced in the XAML (e.g. http://schemas.microsoft.com/winfx/2006/xaml/presentation).

    As long as that URI is in fact used in an xmlns: attribute emitted in the XAML by the authoring tool, this "solves" the problem you are asking about. But conflating your own assembly namespaces with pre-existing ones from the framework is a hack and ill-advised. Even if there are no type name conflicts, it's still a poor practice, and of course if there are type name conflicts, it can cause serious problems.


    EDIT:

    Per your comment:

    The problem I'm trying to solve is joining Gu.Units to the http://Gu.com/Units without adding a reference to System.Xaml for Gu.Units

    From the documentation:

    Apply one or more XmlnsDefinitionAttribute attributes to assemblies in order to identify the types within the assembly for XAML usage. [emphasis mine]

    I.e. you can use [XmlnsDefinition] only to map types from a given namespace that are actually declared in the same assembly where the attribute itself is specified.

    The attribute includes an AssemblyName property, which seems to indicate you could include types from other assemblies. This is the natural reading of the documentation, and was probably the intent of the usage of the attribute. Unfortunately, the framework has no control over how other code consumes that attribute, and the XAML tools do in fact ignore it.

    The [XmlnsDefinition] attribute can be used only to map URIs to namespaces declared in the assembly in which the attribute is found. You can specify other assembly names, but the XAML designer and compiler in Visual Studio pay no attention to the AssemblyName property.

    The WPF team has acknowledged this to be a bug, but has stated that they will not fix the problem.


    It is possible to work around the issue on a type-by-type basis. The most obvious approach is to declare a new type in the assembly where the [XmlnsDefinition] is specified, inheriting the type you want from the other assembly. For example, in your Gu.Units.Wpf assembly, you could declare a type like this:

    public class LengthUnits : Gu.Units.LengthUnits { }
    

    Then you would wind up using the type Gu.Units.Wpf.LengthUnits instead of Gu.Units.LengthUnits. Obviously, this will work only if the type is an unsealed reference type. Also, depending on how the type is actually being used, you could wind up with problems where the code is trying to use an instance of Gu.Units.LengthUnits where an instance of Gu.Units.Wpf.LengthUnits is required. For simple one-way binding scenarios, this probably wouldn't come up but I can easily imagine other scenarios where it would.

    A less obvious way to work around the problem is to use the [TypeForwardedTo] attribute. For example, in your Gu.Units.Wpf assembly, you could include this:

    [assembly: TypeForwardedTo(typeof(Gu.Units.LengthUnits))]
    

    This has the advantage that the type in question will be the same type. However, this is a runtime effect and does not play very well with the XAML designer tools. Your project will build and run correctly, but the designer will still complain that "the type 'units:LengthUnits' was not found".

    I am not aware of any work-around that will address the issue more broadly, on a namespace basis.

    0 讨论(0)
提交回复
热议问题