Q. How can you add meta-information, such as a description, to a class and return it without instantiating the class

 

This is the second out of three articles in this series.

Part 1 (Previous): Refactoring the Project folder and XAML namespace directives
Part 2 (This article): Inserting MetaInformation into a class and generically returning it.

Part 3 (Next): How do we get a list of pages within a namespace ( a subspace of the project).

 

 

In the article on Xamarin.Forms Xaml Samples, ”Part 5. From Data Bindings to MVVM”, towards the end it presents an app ”Implementing a Navigation Menu” that displays a menu of sample Content pages.  The menu lists a Title and Description for each page that is supplied as each page type is added to a collection. The menu binds to these two pieces of information. My issue is that that meta-information does not come from the ContentPage class but is external to it. This grates against my comprehension of what Object Oriented Programming is about .. Data should be contained within the object. Also each page is explicitly added to the collection of page types, without instantiating the pages. I’d like to iterate through all pages found in a folder and therefore get this information from the class code. Three related approaches are discussed here

 

Links:

https://docs.microsoft.com/en-au/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

https://github.com/xamarin/xamarin-forms-samples

https://github.com/xamarin/xamarin-forms-samples/tree/master/XamlSamples

    My Fork:


This article addresses:

[2] MetaInfo Add the Title and Description meta-information to ContentPages’ codebehind pages and return it in a generic manner without page instantiation in the PageDataViewModel class.

     

Xamarin.Forms ContentPage has a Title property that is typically set in the Xaml page in the class definition at the start of the page. On one hand it would be convenient in this quest to get that property value but I guess that value is not installed until the page is instantiated. So what is needed is some way to place some metainformation in the codebehind that can be extracted from the class type. Three solution are presented here.

 

In PageDataViewModel, for each page, beside the class type, e a Title and Description is required:

namespace XamlSamples
{
    public class PageDataViewModel
    {
        public Type Type { private set; get; }
        public string Title { private set; get; }
        public string Description { private set; get; }


 

[1] Use class property Description Attributes

One way is to add some Dummy non-static properties to the page class, each with a Description attribute. This can then be extracted from the type via the System.ComponentModel API:

namespace XamlSamples.Views
{

    public partial class HelloXamlPage : ContentPage
    {

        [System.ComponentModel.Description("Hello, XAML")]
        public string PTitle { get; set; }

        [System.ComponentModel.Description("Display a Label with many properties set")]
        public string PInfo { get; set; }

        public HelloXamlPage()
        {
            InitializeComponent();
        }
    }
}

Adding the Dummy” properties, each with  a Description attribute.

This is then extracted from the type in PageVeiwModel in a method:

        private string GetPropDescription(Type type, string prop)
        {
            PropertyInfo propInfo = type.GetProperty(prop);

            System.ComponentModel.DescriptionAttribute attrib =
                (System.ComponentModel.DescriptionAttribute)propInfo.GetCustomAttributes( 
typeof(System.ComponentModel.DescriptionAttribute), false).FirstOrDefault(); return attrib.Description; }

PageDataViewModel.GetPropDescription( ) Method

 

Which is called in the PageDataViewModel List constructor  to set the required properties for each page:

//[1] Get from dummy properties' Descriptions
 Title = GetPropDescription(type, "PTitle");
 Description = GetPropDescription(type, "PInfo");

 

[2] Use class static properties

Another approach is to add the information as static properties to the class. But how do you get the static properties when they aren’t properties of the page type (ContentPage)?

namespace XamlSamples.Views
{
    public partial class HelloXamlPage : ContentPage
    {
        public static string StatPTitle { get; set; } = "Hello, XAML";
        public static string StatPInfo { get; set; } = "Display a Label with many properties set";

Title and Description as static class properties

 

        private string GetStaticProperty(Type type, string prop)
        {
            PropertyInfo propertyInfo = type
                .GetProperty(prop, BindingFlags.Public | BindingFlags.Static);
            if (propertyInfo == null)
                return string.Empty;
            // Use the PropertyInfo to retrieve the value from the type by not passing in an instance
            return (string)propertyInfo.GetValue(null);
        }

GetStaticProperty( ) method

 

Which is called in the PageDataViewModel List constructor  to set the required properties for each page:

//[2] Use static properties
Title = GetStaticProperty(type, "StatPTitle");
Description = GetStaticProperty(type, "StatPInfo");

 

[3] Use class Description Custom Attribute

A third approach is add the Title and Description as a tilde separated list of two strings. Description is a designated Custom Attribute of a ContentPage so how do you get access to without instantiating the page?

namespace XamlSamples.Views
{
    [System.ComponentModel.Description("Hello Xaml~Display a Label with many properties set")]
    public partial class HelloXamlPage : ContentPage
    {

class Description Custom Attribute: Title and Description as tilde separated list.

 

private string  [ ]  GetClassDescription(Type type)
{ string infos = null; foreach (var cAttrib in type.CustomAttributes)
    { foreach (var constructorArg in cAttrib.ConstructorArguments)
        { var val = constructorArg.Value; if (val is string)
            { string strn = (string)val; //One ConstructorArg is the relative path to the class file 
if (!strn.Contains("\\")) { //The class description is tilde separated class Title and Description
if (strn.Contains("~")) { infos = strn.Split(new char [ ] {'~' }); break; }
} } } } return infos; }

GetClassDescription( ) Method

 

Which is called in the PageDataViewModel List constructor  to set the required properties for each page:

//[3] Get it from the Class Description Custom attribute
string infos = GetClassDescription(type);
if (infos != null)
{
    Title = infos[0];
    Description = infos[1];    }

Wrap Up

The PageDataViewModel  constructor parameters are then removed:

public PageDataViewModel(Type type) //, string title, string description)
{

 

And the calls to it are then simplified thus:

static PageDataViewModel()
{
    All = new List<PageDataViewModel>
    {
        // Part 1. Getting Started with XAML
        new PageDataViewModel(typeof(HelloXamlPage)),
    
        new PageDataViewModel(typeof(XamlPlusCodePage)), 

etc

 

Each CodePage in this version of the repository has all three options. There is a mode at the top of PageDataViewmodel that chooses which mode (1, 2 or 3) to use”"

namespace XamlSamples.Views
{
    [System.ComponentModel.Description("Hello Xaml~Display a Label with many properties set")]
    public partial class HelloXamlPage : ContentPage
    {
        public static string StatPTitle { get; set; } = "Hello, XAML";
      
public static string StatPInfo { get; set; } = "Display a Label with many properties set"; [System.ComponentModel.Description("Hello, XAML")] public string PTitle { get; set; } [System.ComponentModel.Description("Display a Label with many properties set")] public string PInfo { get; set; } public HelloXamlPage() { InitializeComponent(); } } }

 

namespace XamlSamples
{
    public class PageDataViewModel
    {
        static int mode = 3;

        public Type Type { private set; get; }
        public string Title { private set; get; }
        public string Description { private set; get; }

        public PageDataViewModel(Type type) //, string title, string description)
        {
            Type = type;

            //Three ways to get it implicitly from the class
            if (mode == 1)
            {
                //[1] Get from dummy properties' Descriptions
            }
            else if (mode == 2)
            {
                //[2] Use static properties
            }
            else
            {
                //[3] Get it from the Class Description Custom attribute
            }
        }

        private string GetPropDescription(Type type, string prop) {}

        private string GetClassDescription(Type type) {}

        private string GetStaticProperty(Type type, string prop) {}

        static PageDataViewModel() {}

        public static IList<PageDataViewModel> All { private set; get; }
    }
}

 


Discussion

Three solutions to the question are provided. Each are correct solutions but are substantially different. But which is the best? “Beauty is in the eye of the beholder!”  So its an esoteric decision to make. The last solution is possibly the most didactic as it assigns the properties to the class itself but is a little less neat as the two are combined. The first approach is possibly more straight forward, two class properties, but is possibly a kludge as it uses dummy properties. Finally the static properties approach may to be a straight forward approach within the class code but the extraction of this information is a bit esoteric. I had to do a bit of “monkey on a typewriter” coding to get to this, with a bit of Intellisense and debug watches thrown in. What’s your choice?

 

Which of the three solutions is your favourite? Please vote on the alternatives at : https://tinyurl.com/y9xyug3g

 


Next: How do we get a list of pages within a namespace ( a subspace of the project).