Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:<xml-namespace>="<clr-namespace>" attribute, I would like to use the [XmlnsDefinition] assembly attribute to map a URI to the namespaces for those types.

One of the assemblies is not relevant to WPF per se, so I would like to avoid it having any reference to WPF-related assemblies, and in particular the System.Xaml.dll assembly which would be required if that assembly used the [XmlnsDefinition] attribute.

I have a Visual Studio Solution that is organized like this:

Gu.Units.sln
    Gu.Units.csproj // no ref to System.Xaml here
    Gu.Units.Wpf.csproj // references Gu.Units and System.Xaml

In Gu.Units.Wpf.csproj I have this mapping:

[assembly: XmlnsDefinition("http://Gu.com/Units", clrNamespace: "Gu.Units", AssemblyName = "Gu.Units")]
[assembly: XmlnsDefinition("http://Gu.com/Units", clrNamespace: "Gu.Units.Wpf", AssemblyName = "Gu.Units.Wpf")]
[assembly: XmlnsPrefix("http://Gu.com/Units", "units")]

I have tried to use it in XAML like this:

<UserControl x:Class="Gu.Units.Wpf.Demo.Sample"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:units="http://Gu.com/Units">
    <Label Content="{x:Static units:LengthUnit.Millimetres}" />
</UserControl>

But for some reason, the namespace Gu.Units seems to be ignored. That is, it is not included in the XML namespace identified by the URI http://Gu.com/Units. Instead, I get:

The name "LengthUnit" does not exist in the namespace "http://Gu.com/Units".

An explicit namespace declaration in the XAML — i.e. having xmlns:units="clr-namespace:Gu.Units;assembly=Gu.Units" — works fine but I would also like to avoid that.

Is there a way that I can have my Gu.Units.Wpf.dll assembly provide the necessary [XmlnsDefinition] attribute to map the namespace from the Gu.Units.dll assembly, so that the latter does not itself need a reference to System.Xaml.dll, nor have any XAML-specific code in it at all?

like image 233
Johan Larsson Avatar asked Nov 23 '15 00:11

Johan Larsson


People also ask

How do I use namespace in XAML?

A XAML namespace is really an extension of the concept of an XML namespace. The techniques of specifying a XAML namespace rely on the XML namespace syntax, the convention of using URIs as namespace identifiers, using prefixes to provide a means to reference multiple namespaces from the same markup source, and so on.

What is XAML namespace declaration?

A XAML namespace is a specialized XML namespace, just as XAML is a specialized form of XML and uses the basic XML form for its markup. In markup, you declare a XAML namespace and its mapping through an xmlns attribute applied to an element.

What is CLR in XAML?

This topic describes the common language runtime (CLR) attributes that are defined by . NET XAML Services. It also describes other CLR attributes that are defined in . NET that have a XAML-related scenario for application to assemblies or types.

What is MC ignorable D?

The mc:Ignorable namespace provides xaml definitions that are "ignored" by the xaml processor. This allows you to specify information used by the designer at design time which is ignored at runtime.


1 Answers

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.

like image 126
Peter Duniho Avatar answered Oct 13 '22 18:10

Peter Duniho