Creating Templates That Read From Custom Attributes

Oct 19, 2009 at 10:42 PM

I'm currently using a template to generate my Views in an ASP.net MVC project. So far things are working great until I attmpted to read out Attribute values of specific Properties.

I'm generating several views based on a Presentation Object. One Presentation Object for One View.

The Many of the Properties of these object  have a custom attribute that defines how the view will look:

 [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method)]
    public class Presentation : System.Attribute
    {
        private string cssClass;

        public string CssClass
        {
            get { return cssClass; }
            set { cssClass = value; }
        }
        private string displayName;

        public string DisplayName
        {
            get { return displayName; }
            set { displayName = value; }
        }

        public Presentation(string cssClass, string displayName)
        {
            this.cssClass = cssClass;
            this.displayName = displayName;
        }
    }

Here is the code I am using to retrieve the CssClass Property: (Keep in mind I have tried several methods)

Presentation attribute = Attribute.GetCustomAttribute(pi, typeof(Presentation)) as Presentation;
{
     #><#= attribute.CssClass.ToString() #><#
}  

When I run the template I am told the CssClass does not exist.

Any Ideas?

Thanks

 

 

Coordinator
Oct 21, 2009 at 5:04 AM
Edited Oct 21, 2009 at 5:04 AM

I didn't think T4 Toolbox worked in the context of ASP.NET MVC host. Anyway... Make sure you have the latest version of the assembly loaded in your T4 template. I can't suggest anything else, unless you provide specific details - complete template source code, error messages, etc.

Oct 21, 2009 at 3:52 PM
Edited Oct 21, 2009 at 4:09 PM
Here is the full template. You can see I am referencing an assembly that contains the custom attribute Presentation at the top of the page.
When fields are generated from propertyInfo items within the properties collection I also want to grab the custom attribute value for each property as well.
I can provide more info if needed. Unless you have a better approach?

<#@ template language="C#" HostSpecific="True" #>
<#@ output extension="aspx" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Reflection" #>
<#@ assembly name="C:\Projects\ProjectName\DataPresentation\bin\Debug\Namespace.DataPresentation.dll" #>
<#@ import namespace="Namespace.DataPresentation" #>
<#
MvcTextTemplateHost mvcHost = (MvcTextTemplateHost)(Host);
string mvcViewDataTypeGenericString = (!String.IsNullOrEmpty(mvcHost.ViewDataTypeName)) ? "<" + mvcHost.ViewDataTypeName + ">" : String.Empty;
int CPHCounter = 1;
#>
<#
// The following chained if-statement outputs the user-control needed for a partial view, or opens the asp:Content tag or html tags used in the case of a master page or regular view page
if(mvcHost.IsPartialView) {
#>
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<#= mvcViewDataTypeGenericString #>" %>

<#
} else if(mvcHost.IsContentPage) {
#>
<%@ Page Title="" Language="C#" MasterPageFile="<#= mvcHost.MasterPageFile #>" Inherits="System.Web.Mvc.ViewPage<#= mvcViewDataTypeGenericString #>" %>
<%@ Import Namespace= "Helpers" %>
<#
    foreach(string cphid in mvcHost.ContentPlaceHolderIDs) {
        if(cphid.Equals("TitleContent", StringComparison.OrdinalIgnoreCase)) {
#>
<asp:Content ID="Content<#= CPHCounter #>" ContentPlaceHolderID="<#= cphid #>" runat="server">
    <#= mvcHost.ViewName #>
</asp:Content>

<#
            CPHCounter++;
        }
    }
#>
<asp:Content ID="Content<#= CPHCounter #>" ContentPlaceHolderID="<#= mvcHost.PrimaryContentPlaceHolderID #>" runat="server">

    <h2><#= mvcHost.ViewName #></h2>

<#
} else {
#>
<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage<#= mvcViewDataTypeGenericString #>" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title><#= mvcHost.ViewName #></title>
</head>
<body>
<#
}
#>
<#
if(!String.IsNullOrEmpty(mvcViewDataTypeGenericString)) {    
    List<PropertyInfo> properties = new List<PropertyInfo>();
    FilterProperties(mvcHost.ViewDataType, properties);  
#>
    <%= Html.ValidationSummary("Edit was unsuccessful. Please correct the errors and try again.") %>

    <% using (Html.BeginForm()) {%>

        <fieldset>
            <legend>Fields</legend>
<#

    foreach(PropertyInfo pi in properties) 
    {    
#>
            <%= Html.<#= GetInputType(pi) #>("<#= pi.Name #>", "<#= pi.Name #>", "CSSValue", Model.<#= pi.Name #>.ToString()) %>   
       
  <#    
            object[] attributes = pi.GetCustomAttributes(typeof(Presentation), false); 
            if (attributes.Length > 0)
            {
                Presentation attribute = attributes[0] as Presentation;
                 #><#= attribute.CssClass.ToString() #><#
            }

          
}
#>             
        </fieldset>
        
        <div>
            <input type="submit" value="Save" />
            </div>  

    <div>
        <%=Html.ActionLink("Back to List", "Index") %>
    </div>
    
<% } %>
<#
}
#>
<#
// The following code closes the asp:Content tag used in the case of a master page and the body and html tags in the case of a regular view page
#>
<#
if(mvcHost.IsContentPage) {
#>
</asp:Content>
<#
    foreach(string cphid in mvcHost.ContentPlaceHolderIDs) {
        if(!cphid.Equals("TitleContent", StringComparison.OrdinalIgnoreCase) && !cphid.Equals(mvcHost.PrimaryContentPlaceHolderID, StringComparison.OrdinalIgnoreCase)) {
            CPHCounter++;
#>

<asp:Content ID="Content<#= CPHCounter #>" ContentPlaceHolderID="<#= cphid #>" runat="server">
</asp:Content>
<#
        }
    }
#>
<#
} else if(!mvcHost.IsPartialView && !mvcHost.IsContentPage) {
#>
</body>
</html>
<#
}
#>

<#+
    
public string GetInputType(PropertyInfo pi) {    
    switch (pi.PropertyType.ToString()) {
    
        case "System.Int32":    
        case "System.Int64":    
            return "StyledTextBox";
    
        case "System.Boolean":    
            return "StyledCheckBox";
        
        //case "System.List<String>":
            //return "StyledDropDownList";
    
        case "System.String":
        default:    
            return "StyledTextBox";    
    }    
}

public void FilterProperties(Type type, List<PropertyInfo> properties) {     if(type != null) {    
        PropertyInfo[] publicProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); 
  
        foreach (PropertyInfo pi in publicProperties)    
        {    
            if (IsBindableType(pi.PropertyType) && pi.CanRead && pi.CanWrite)    
            {    
                properties.Add(pi);    
            }    
        }    
    }    
} 

public bool IsBindableType(Type type)
{
    bool isBindable = false;

    if (type.IsPrimitive || type.Equals(typeof(string)) || type.Equals(typeof(DateTime)) || type.Equals(typeof(decimal)) || type.Equals(typeof(Guid)) || type.Equals(typeof(DateTimeOffset)) || type.Equals(typeof(TimeSpan)))
    {
        isBindable = true;
    }

    return isBindable;
}

#>

Oct 21, 2009 at 4:17 PM

Ha ha, It works!

Looks like I just had an issue with the latest version of the refrenced DLL not being available in the local MV project. (Build entire solution next time ;).

Anyway now I can generate strongly-typed views and have the layout derived from that objects attributes. (CSS, ExtraHTML, Field Names) Excellent.

This way if a view needs changes I can just regenerate through the template and the layout stays as expected.