Question

I'm trying to access my custom attributes like SortableAttribute attached to my model properties in a T4 Controller template I'm writing. I've copied the class block, assemblies and imports from List.tt as boilerplate.

First I tried to cast the attributes as follows:

<#+
string SortableBy(PropertyInfo property)
{
    foreach (object attribute in property.GetCustomAttributes(true))
    {
        var sortable = attribute as SortableAttribute;
        if (sortable != null) return sortable.By == "" ? property.Name : sortable.By;
    }
    return property.Name;
}
#>

This however yielded no positive results since my project's namespace is unknown to T4. To resolve this, I have added my project's dll and imported the required namespace.

<#@ assembly name ="c:\path\to\MyProject\bin\MyProject.dll" #>
<#@ import namespace= "MyProject.Filters" #>

It seemed to be successful at first (eg. no errors on the namespace import), but it still did not find my attributes. If I replaced SortableAttribute with MyProject.Filters.SortableAttribute, the error message was that SortableAttribute was not found in MyProject.Filters.

To overcome this, I've changed my code as follows:

<#+
string SortableBy(PropertyInfo property)
{
    foreach (object attribute in property.GetCustomAttributes(true))
    {
        if (attribute != null && attribute.GetType().Name == "SortableAttribute")
        {
            var sortableBy = (string) attribute.GetType().GetProperty("By").GetValue(attribute, null);
            return sortableBy == "" ? property.Name : sortableBy;
        }
    }
    return property.Name;
}
#>

I thought I've hit the jackpot, but I soon had to realise that property.GetCustomAttributes(true) returns all attributes but not mine...

An example model:

public class MyModel
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    [Display(Name = "Full Name")]
    [Sortable]
    public int Name { get; set; }
}

Implementation of SortableAttribute:

using System;

namespace MyProject.Filters
{
    [AttributeUsage(AttributeTargets.Property)]
    public class SortableAttribute : Attribute
    {
        public SortableAttribute(string by = "")
        {
            this.By = by;
        }

        public string By { get; private set; }
    }
}

Can you please point me to the right direction?

Any help is greatly appreciated!

Edit:

It turns out that property.GetCustomAttributes(true) does return my attributes, but the following expression in SortableBy always evaluates to null:

(string) attribute.GetType().GetProperty("By").GetValue(attribute, null);

Any idea why this can be?

Was it helpful?

Solution

Remember: Build fixes a lot of problems, but REBUILD can fix it all!

To recap and to help others writing T4 templates, here are some working code that can be used as boilerplate:

Reading custom attribute in a .tt file (based on Scaffold() in any default view template):

<#+
string SortableBy(PropertyInfo property)
{
    foreach (object attribute in property.GetCustomAttributes(true))
    {
        var sortable = attribute as SortableAttribute;
        if (sortable != null) return sortable.By == "" ? property.Name : sortable.By;
    }
    return property.Name;
}
#>

Model:

public class MyModel
{
    [Key]
    [DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    [Required]
    [Display(Name = "Full Name")]
    [Sortable]
    public int Name { get; set; }
}

Implementation of SortableAttribute:

using System;

namespace MyProject.Filters
{
    [AttributeUsage(AttributeTargets.Property)]
    public class SortableAttribute : Attribute
    {
        public SortableAttribute(string by = "")
        {
            this.By = by;
        }

        public string By { get; private set; }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top