none
How To Get ParserContext? RRS feed

  • Question

  • Currently I am writing a custom MarkupExtension, and I am running into an issue that I want to access all xml namespaces to clr namespaces mapping dictionary under ParserContext.XmlnsDictionary property, in the ProvideValue method I do something like this:
    ParserContext context = serviceProvider.GetService(typeof(ParserContext)) as ParserContext;
    if (context == null)
    {
    	throw new InvalidOperationException("ParserContext is null");
    }


    the code always run into an exception, so how can I reference the ParserContext within a MarkupExtension? should I manually parse those mapping info myself?

    Sheva
    Wednesday, October 4, 2006 5:21 PM

Answers

  • That should work, but note that the runtime namespace for Dictionary is System.Collections.Generic (with an "s"). 

    As an example, here's some markup that creates a single-argument generic type (here it creates a Collection<string>):

    <Window x:Class="GenericsAndXamlTypeResolver.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="GenericsAndXamlTypeResolver" Height="300" Width="300"

        xmlns:co="clr-namespace:System.Collections.ObjectModel;assembly=mscorlib"

        xmlns:local="clr-namespace:GenericsAndXamlTypeResolver"

        xmlns:sys="clr-namespace:System;assembly=mscorlib"

        >

        <Grid>

          <Button Content="{local:GenericType1 co:Collection, sys:String}"/>

        </Grid>

    </Window>

    ... and here's the code for the markup extension:

     

    namespace GenericsAndXamlTypeResolver

    {

        public class GenericType1 : MarkupExtension

        {

            string _typeName;

            Type _typeArgument;

     

            public GenericType1(string typeName, Type typeArgument)

            {

                _typeName = typeName;

                _typeArgument = typeArgument;

            }

            public override object ProvideValue(IServiceProvider serviceProvider)

            {

                IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

                Type type = xamlTypeResolver.Resolve(_typeName + "`1");

                return type.MakeGenericType(_typeArgument);

     

            }

        }

    }

     

    Thursday, October 5, 2006 5:31 PM
  • It's too bad that it's been difficult to debug.  Usually, when you get a XamlParseException, you can look at the inner exceptions ("view details" in Visual Studio) to see what the original exception was.

    Now try #3 at your scenario.  Here's the markup extension to produce the object.  Note that it has a [ContentProperty] to make the second syntax work, and a type converter to make the third syntax work:

    [ContentProperty("TypeArguments")]

    public class GenericExtension : MarkupExtension

    {

        // Type arguments.  This is read/write so that it can be

        // set in Xaml attribute syntax with a type converter.

     

        private Collection<Type> _typeArguments = new Collection<Type>();

        [TypeConverter(typeof(TypeCollectionConverter))]

        public Collection<Type> TypeArguments

        {

            get { return _typeArguments; }

            set { _typeArguments = value; }

        }

         

        // The generic type name (e.g. Dictionary, for the Dictionary<K,V> case)

        private string _typeName;

        public string TypeName

        {

            get { return _typeName; }

            set { _typeName = value; }

        }

     

        // Constructors

        public GenericExtension()

        {

        }

        public GenericExtension(string typeName )

        {

            TypeName = typeName;

        }

     

        // ProvideValue, which returns the concrete object of the generic type

        public override object ProvideValue(IServiceProvider serviceProvider)

        {

            IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

            if (xamlTypeResolver == null)

                throw new Exception("The Generic markup extension requires an IXamlTypeResolver service provider");

     

            // Get e.g. "Collection`1" type

            Type genericType = xamlTypeResolver.Resolve(

                _typeName + "`" + TypeArguments.Count.ToString() );

     

            // Get an array of the type arguments

            Type[] typeArgumentArray = new Type[ TypeArguments.Count ];

            TypeArguments.CopyTo(typeArgumentArray, 0);

     

            // Create the conrete type, e.g. Collection<String>

            Type concreteType = genericType.MakeGenericType(typeArgumentArray);

     

            // Create an instance of that type

            return Activator.CreateInstance(concreteType);

        }

     

    }

    And here's the referenced type converter:

    //

    // Type converter that converts a string which is a list

    // of Xaml type names, e.g. "sys:String,sys:Int32", to a Collection<Type>.

    //

     

    public class TypeCollectionConverter : TypeConverter

    {

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)

        {

            IXamlTypeResolver xamlTypeResolver = context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

     

            if (sourceType == typeof(Type) && xamlTypeResolver != null)

                return true;

     

            return base.CanConvertFrom(context, sourceType);

        }

     

     

        public override object ConvertFrom(ITypeDescriptorContext context,

                           System.Globalization.CultureInfo culture, object value)

        {

            IXamlTypeResolver xamlTypeResolver

                     = context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

     

            string[] stringArray = (value as string).Split(',');

            Collection<Type> types = new Collection<Type>();

     

            for( int i = 0; i < stringArray.Length; i++ )

                types.Add( xamlTypeResolver.Resolve( stringArrayIdea.Trim()) );

     

            return types;

        }

     

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)

        {

            return base.CanConvertTo(context, destinationType);

        }

        public override object ConvertTo(ITypeDescriptorContext context,

                      System.Globalization.CultureInfo culture, object value, Type destinationType)

        {

            return base.ConvertTo(context, culture, value, destinationType);

        }

    }

    And here's some sample Xaml that's supported by these:

    <cc:Generic TypeName="co:Dictionary">

      <cc:Generic.TypeArguments>

        <cc:CollectionOfT TypeArgument="sys:Type">

          <x:Type TypeName="sys:String"/>

          <x:Type TypeName="sys:Int32"/>

        </cc:CollectionOfT>

      </cc:Generic.TypeArguments>

    </cc:Generic>

     

    <cc:Generic TypeName="co:Dictionary">

      <x:Type TypeName="sys:String"/>

      <x:Type TypeName="sys:Int32"/>

    </cc:Generic>

     

    <Label Content="{cc:Generic TypeName=co:Dictionary, TypeArguments='sys:String, sys:Int32'}"/>

     

    Friday, October 6, 2006 6:50 PM
  • You're right that parameter arrays aren't supported for markup extensions today, but I'll put it on the wish list for the next version.

    One way you could accomplish that syntax, though, would be to make the type arguments a string, rather than a Type[].  So the markup extension would look like:

    <Label Content="{co:Dictionary, 'sys:String, sys:Int32' }"/>

    ... Then, in ProvideValue, you can parse the string into a Type[], just like the TypeCollectionConverter above.

    Tuesday, October 10, 2006 11:45 PM

All replies

  • The ParserContext isn't available to markup extensions or type converters.  The service provider does provide an IXamlTypeResolver service, though, which doesn't give all of the mappings, but does map a "prefix:Name" to a System.Type instance.  For example, given this code:

    namespace Test

    {

        public class Foo

        {

            ...

        }
    }

    ... and this markup:

    <Window

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:test="clr-namespace:Test"

        >

     

      <Button Content="{SomeExtension TestType}"/>

    </Window>

    ... the SomeExtension implementation could call IXamlTypeResolver.Resolve("test:Foo") to get back a typeof(Foo) object.

     

    Thursday, October 5, 2006 12:03 AM
  • Actually I am writing a MarkupExtension to enable the xaml to accept generic type, I've use IXamlTypeResolver to resolve the generic type name, what i do is something like this:
    IXamlTypeResolver resolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;
    if (resolver == null)
    {
    	throw new InvalidOperationException("Context is not available now");
     }
     
    String typeName = genericTypeName + "`" + typeArguments.Count.ToString();
    genericType = resolver.Resolve(typeName);
    if (genericType == null)
    {
    	throw new InvalidOperationException("genericType is null");
    }


        If I pass in "co:Dictionary" as the genericTypeName, and two String types as the TypeArguments, "co" prefix actually points to "System.Collection.Generic" clr namespace, and use the IXamlTypeResolver to parse the typeName, the genericType field is always null. I guess IXamlTypeResolver doesn't know how to parse generic type name, that's why I plan to do it myself, but one thing I need to know is which clr namespace prefix "co" points to, that's why I need to access the ParserContext to retrieve the xmlns table, if ParserContext is not available for MarkupExntension and TypeConverter, then in which context we can get the reference to it?

    Sheva
    Thursday, October 5, 2006 4:29 AM
  • That should work, but note that the runtime namespace for Dictionary is System.Collections.Generic (with an "s"). 

    As an example, here's some markup that creates a single-argument generic type (here it creates a Collection<string>):

    <Window x:Class="GenericsAndXamlTypeResolver.Window1"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="GenericsAndXamlTypeResolver" Height="300" Width="300"

        xmlns:co="clr-namespace:System.Collections.ObjectModel;assembly=mscorlib"

        xmlns:local="clr-namespace:GenericsAndXamlTypeResolver"

        xmlns:sys="clr-namespace:System;assembly=mscorlib"

        >

        <Grid>

          <Button Content="{local:GenericType1 co:Collection, sys:String}"/>

        </Grid>

    </Window>

    ... and here's the code for the markup extension:

     

    namespace GenericsAndXamlTypeResolver

    {

        public class GenericType1 : MarkupExtension

        {

            string _typeName;

            Type _typeArgument;

     

            public GenericType1(string typeName, Type typeArgument)

            {

                _typeName = typeName;

                _typeArgument = typeArgument;

            }

            public override object ProvideValue(IServiceProvider serviceProvider)

            {

                IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

                Type type = xamlTypeResolver.Resolve(_typeName + "`1");

                return type.MakeGenericType(_typeArgument);

     

            }

        }

    }

     

    Thursday, October 5, 2006 5:31 PM
  • Thanks Mike, you code really enlightens me.

    Sheva
    Thursday, October 5, 2006 6:19 PM
  • Cool idea, by the way.  In fact, I played with it a bit more to create some markup extensions for the basic collection types, posted here.

    Also, there was a missing line in the sample code of my last post, corrected here in yellow:

    public class GenericType1 : MarkupExtension

    {

        string _typeName;

        Type _typeArgument;

     

        public GenericType1(string typeName, Type typeArgument)

        {

            _typeName = typeName;

            _typeArgument = typeArgument;

        }

     

        public override object ProvideValue(IServiceProvider serviceProvider)

        {

            IXamlTypeResolver xamlTypeResolver

                     = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

            Type type = xamlTypeResolver.Resolve(_typeName + "`1"); // Gets generic Collection<T>

            type =  type.MakeGenericType(_typeArgument);            // Gets concrete Collection<String>

     

            return Activator.CreateInstance(type);

        }

    }

     

     

    Friday, October 6, 2006 4:17 PM
  • Hi Mike, I want to add some properties to my markup extension, what I really want to get is some xaml syntax like followings:
       
    syntax one:
        <cc:Generic TypeName="co:Dictionary">
            <cc:Generic.TypeArguments>
              <x:Type TypeName="sys:String"/>
              <x:Type TypeName="sys:Int32"/>
            </cc:Generic.TypeArguments>
          </cc:Generic>
     
    syntax two:
          <cc:Generic TypeName="co:Dictionary">
              <x:Type TypeName="sys:String"/>
              <x:Type TypeName="sys:Int32"/>
          </cc:Generic>

     syntax three:
          <Label Content="{cc:Generic TypeName=cc:Collection, TypeArguments=[sys:String, sys:Int32]}"/>



        I wanna have those three syntax all work well, I've tried pretty hard to come up with some code to implement it, but whenever I test the code, I always got XamlParseException, what's more, this exception ain't give me any detailed info about what I am doing wrong, you know, testing custom markup extension is reallly really painful.

    Sheva
    Friday, October 6, 2006 5:27 PM
  • It's too bad that it's been difficult to debug.  Usually, when you get a XamlParseException, you can look at the inner exceptions ("view details" in Visual Studio) to see what the original exception was.

    Now try #3 at your scenario.  Here's the markup extension to produce the object.  Note that it has a [ContentProperty] to make the second syntax work, and a type converter to make the third syntax work:

    [ContentProperty("TypeArguments")]

    public class GenericExtension : MarkupExtension

    {

        // Type arguments.  This is read/write so that it can be

        // set in Xaml attribute syntax with a type converter.

     

        private Collection<Type> _typeArguments = new Collection<Type>();

        [TypeConverter(typeof(TypeCollectionConverter))]

        public Collection<Type> TypeArguments

        {

            get { return _typeArguments; }

            set { _typeArguments = value; }

        }

         

        // The generic type name (e.g. Dictionary, for the Dictionary<K,V> case)

        private string _typeName;

        public string TypeName

        {

            get { return _typeName; }

            set { _typeName = value; }

        }

     

        // Constructors

        public GenericExtension()

        {

        }

        public GenericExtension(string typeName )

        {

            TypeName = typeName;

        }

     

        // ProvideValue, which returns the concrete object of the generic type

        public override object ProvideValue(IServiceProvider serviceProvider)

        {

            IXamlTypeResolver xamlTypeResolver = serviceProvider.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

            if (xamlTypeResolver == null)

                throw new Exception("The Generic markup extension requires an IXamlTypeResolver service provider");

     

            // Get e.g. "Collection`1" type

            Type genericType = xamlTypeResolver.Resolve(

                _typeName + "`" + TypeArguments.Count.ToString() );

     

            // Get an array of the type arguments

            Type[] typeArgumentArray = new Type[ TypeArguments.Count ];

            TypeArguments.CopyTo(typeArgumentArray, 0);

     

            // Create the conrete type, e.g. Collection<String>

            Type concreteType = genericType.MakeGenericType(typeArgumentArray);

     

            // Create an instance of that type

            return Activator.CreateInstance(concreteType);

        }

     

    }

    And here's the referenced type converter:

    //

    // Type converter that converts a string which is a list

    // of Xaml type names, e.g. "sys:String,sys:Int32", to a Collection<Type>.

    //

     

    public class TypeCollectionConverter : TypeConverter

    {

        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)

        {

            IXamlTypeResolver xamlTypeResolver = context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

     

            if (sourceType == typeof(Type) && xamlTypeResolver != null)

                return true;

     

            return base.CanConvertFrom(context, sourceType);

        }

     

     

        public override object ConvertFrom(ITypeDescriptorContext context,

                           System.Globalization.CultureInfo culture, object value)

        {

            IXamlTypeResolver xamlTypeResolver

                     = context.GetService(typeof(IXamlTypeResolver)) as IXamlTypeResolver;

     

            string[] stringArray = (value as string).Split(',');

            Collection<Type> types = new Collection<Type>();

     

            for( int i = 0; i < stringArray.Length; i++ )

                types.Add( xamlTypeResolver.Resolve( stringArrayIdea.Trim()) );

     

            return types;

        }

     

        public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)

        {

            return base.CanConvertTo(context, destinationType);

        }

        public override object ConvertTo(ITypeDescriptorContext context,

                      System.Globalization.CultureInfo culture, object value, Type destinationType)

        {

            return base.ConvertTo(context, culture, value, destinationType);

        }

    }

    And here's some sample Xaml that's supported by these:

    <cc:Generic TypeName="co:Dictionary">

      <cc:Generic.TypeArguments>

        <cc:CollectionOfT TypeArgument="sys:Type">

          <x:Type TypeName="sys:String"/>

          <x:Type TypeName="sys:Int32"/>

        </cc:CollectionOfT>

      </cc:Generic.TypeArguments>

    </cc:Generic>

     

    <cc:Generic TypeName="co:Dictionary">

      <x:Type TypeName="sys:String"/>

      <x:Type TypeName="sys:Int32"/>

    </cc:Generic>

     

    <Label Content="{cc:Generic TypeName=co:Dictionary, TypeArguments='sys:String, sys:Int32'}"/>

     

    Friday, October 6, 2006 6:50 PM
  • Mike, you rocks, I will try your code later on, and probably I will have more questions regarding custom markup extension.

    Sheva
    Saturday, October 7, 2006 5:49 AM
  • Now, I have another question, it seems xaml parser doesn't recognize ParamArrayAttribute, you know, when I define my custom markup extension's constructor this way:
    public GenericTypeExtension(String typeName, params Type[] typeArguments)
    {
        //...
    }

    And I use it in xaml this way:
    <Label Content="{co:Dictionary, sys:String, sys:Int32}"/>

    When I do this, I get an exception saying that GenericTypeExtension doesn't have a constructor which accepts three arguments, which means that xaml parser don't treat params the way we anticipate, is this for true? is there any workaround to this problem? or actually the current implementation of WPF prevents me from doing this, if so, I wish this feature can be added in the next version of WPF.

    Sheva
    Sunday, October 8, 2006 4:28 PM
  • You're right that parameter arrays aren't supported for markup extensions today, but I'll put it on the wish list for the next version.

    One way you could accomplish that syntax, though, would be to make the type arguments a string, rather than a Type[].  So the markup extension would look like:

    <Label Content="{co:Dictionary, 'sys:String, sys:Int32' }"/>

    ... Then, in ProvideValue, you can parse the string into a Type[], just like the TypeCollectionConverter above.

    Tuesday, October 10, 2006 11:45 PM
  • Nice tip Mike, hopefully in the version 2.0 of WPF, there will be more generic enhancement for xaml.

    Sheva
    Wednesday, October 11, 2006 4:15 PM
  • Very good sample Mike, but ... how can I use the MarkupExtension from code?

    For example I have a Dictionary property in a class and I want to be serialized this way.

    Thanks,

    Laurentiu

    Friday, November 10, 2006 9:07 AM