5 minute read

The following guide will walk you through the basics on how-to extend the CodeSmith Generator PLINQO Templates to fit your custom development needs. In this example we will create a new Webpage for each entity that we generate. This page will contain a LinqDataSource control and an ASP.NET GridView control that will display data from a corresponding entity that we generated. I always recommend that you use the PLINQO API when writing templates that interact with code generated by PLINQO as this ensures that the code generated has the proper names and associations and or types.

Find relevant templates to use as a starting point

The first step I always use is to determine if an existing template has a close fit to your end goal so you save time writing the template. In this example I choose to pick the Entity.Editable.cst template that is located in the CSharp or VisualBasic\Internal Folder. This template is a very basic template that shows how to query off of the underlying PLINQO Template API. After I had determined that this was the template file I wanted to use, I created a new folder next to the internal folder called CustomTemplates. This name really doesn’t matter but I choose this name to get started. Next, I copied the ASP.NET sample templates out of the the ASP.NET samples folder and renamed it to Entity.GridView respectively.

PLINQO Template Explorer

Once the files were renamed, I copied the import statements and properties that were defined in the Entity.Editable.cst template and copied them to my Entity.GridView templates. Since the paths were relative and used the same hierarchy. I was able to get away with not updating the file paths. I removed the extra properties and declarations that I knew I would not be needing. I was left with the following in the three Entity.GridView templates:

<%@AssemblyName="Dbml"Path="..\..\Common"%>
<%@ImportNamespace="LinqToSqlShared.DbmlObjectModel"%>
<%@PropertyCategory="1.Mapping"Name="Database" Type="LinqToSqlShared.DbmlObjectModel.Database"Optional="False" Description="Database instance. Must be set by parent template"%>
<%@PropertyCategory="1.Mapping"Name="Type" Type="LinqToSqlShared.DbmlObjectModel.Type"Optional="False" Description="The Type instance for this entity. Must be set by parent template"%>
<%@MapName="CSharpAlias" Src="System-CSharpAlias.csmap" Reverse="False" Description="Convert system data types to c# alias"%>

Design first in Visual Studio than create a dynamic template

Once this was done, I went inside of generated PLINQO Visual Studio solution and created a new sample ASPX page that contained the an GridView control and a LinqDataSource control. I then went and pasted this syntax for the SourceView, Code Behind and the Designer file into the respected template after I had the look and feel that I wanted. The EntityGridView.aspx.cst template looked like this when I started:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="CategoryId" DataSourceID="LinqDataSource1">
  <Columns>
    <asp:BoundField DataField="CategoryId" HeaderText="CategoryId" SortExpression="CategoryId"/>
    <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name"/>
    <asp:BoundField DataField="Description" HeaderText="Description" SortExpression="Description"/>
  </Columns>
</asp:GridView>

<asp:LinqDataSource ID="LinqDataSource1" runat="server" ContextTypeName="PetShop.Web" EnableDelete="True" EnableInsert="True" EnableUpdate="True" EntityTypeName="" TableName="Category">
</asp:LinqDataSource>

I then started converting the static text below into a dynamic template. I looked at the existing Entity.Editable.cst template to see how they queried the API and then I updated the template to use the existing template properties.

<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" DataKeyNames="CategoryId" DataSourceID="LinqDataSource1">
  <Columns>
  <% foreach(Column column in Type.Columns){ %>
    <asp:BoundField DataField="<%=column.Member%>" HeaderText="<%=column.Member%>"
    <%if(column.IsReadOnly.HasValue && column.IsReadOnly.Value){ %>ReadOnly="True"<%}%> SortExpression="<%=column.Member%>"/>
  <%}%>
  </Columns>
</asp:GridView>
<asp:LinqDataSource ID="LinqDataSource2" runat="server" ContextTypeName="<%=Database.ContextNamespace%>.<%=Database.Class%>" EnableDelete="True" EnableInsert="True" EnableUpdate="True" EntityTypeName="" TableName="<%=Type.Name%>">
</asp:LinqDataSource>

As you can see we updated the template to use the specified Type that is defined in the DBML File and not a table type that we specified using SchemaExplorer. The Type Object should be thought of as the entity that has been generated with theColumns Property as the properties in the entity. Each object has properties defined that relate closely to that of which makes up the DBML markup. Once the template is built you will have Intellisense to help you discover the properties and methods that you can use.

Resolve DataKeyNames using IndexedEnumerable

As you can see that we still need to update the DataKeyNames which takes a list of primary keys in a comma delimited String. For this we will use the IndexedEnumerable feature that was added in CodeSmith Generator 5.1 to make it easier to build up the delimited list.

<%
string keyNames = String.Empty;
foreach(var column in Type.PrimaryKeyColumns.AsIndexedEnumerable()) {
  keyNames += String.Format("{0}{1}",column.Value.Member,!column.IsLast?",":String.Empty);
}%>

<asp:GridView ID="GridView2" runat="server" AutoGenerateColumns="False" DataKeyNames="<%=keyNames%>" DataSourceID="LinqDataSource1">

Now the template has been converted completely to use the PLINQO API.

How-to use control logic to exclude Foreign Key columns in the GridView

I’ll quickly show you how one could add conditional statements into the templates to ignore foreign keys. Inside of the main loop where you are adding a BoundField control to the Columns Collection of the GridViewControl we will update the syntax to the following.

<%
var association=Type.GetForeignKeyAssociation(column);
if (association==null) { %>
  <asp:BoundField DataField="<%=column.Member%>" HeaderText="<%=column.Member%>" <%if(column.IsReadOnly.HasValue && column.IsReadOnly.Value) {%>ReadOnly="True"<%}%> SortExpression="<%=column.Member%>"/> <%} else {%> <!-- We are ignoring <%= column.Member %> as it is a foreign key. -->
<%} }%>

If an association doesn’t exist for the current column than the method call to GetForeignKeyAssociation will return null.

Creating the Master Template

Finally, we will update the Entities.cst template or you could also create your own master template which will drive our three newly added custom templates. The first step is to register the sub templates inside of your master templates.

<%@Register Name="EntityGridViewSourceViewTemplate" Template="CustomTemplates\Entity.GridView.aspx.cst"%>
<%@Register Name="EntityGridViewCodeBehindTemplate"Template="CustomTemplates\Entity.GridView.aspx.cs.cst"%>
<%@Register Name="EntityGridViewDesignerTemplate"Template="CustomTemplates\Entity.GridView.aspx.designer.cs.cst"%>

Then I looked through the Entities.cst template and found existing method calls like CreateEntityBaseClass(database) inside of the Generate Method. I then added my own method calledCreateEntityGridViews(database) and then copied and edited an existing method (CreateInterfaces). This method will initialize and render out the sub templates.

public void CreateEntityGridViews(Database database) {
  EntityGridViewSourceViewTemplate vt = this.Create<EntityGridViewSourceViewTemplate>();
  this.CopyPropertiesTo(vt);
  vt.Database = database;

  EntityGridViewCodeBehindTemplate bt = this.Create<EntityGridViewCodeBehindTemplate>();
  this.CopyPropertiesTo(bt);
  bt.Database = database;

  EntityGridViewDesignerTemplate dt = this.Create<EntityGridViewDesignerTemplate>();
  this.CopyPropertiesTo(dt);
  dt.Database = database;

  foreach(Table tableMap in database.Tables) {
    Stopwatch watch = Stopwatch.StartNew();
    Debug.WriteLine(String.Format("Creating Entity GridView for '{0}' ...", tableMap.Type.Name));

    string fileName = Path.Combine(BaseDirectory, tableMap.Type.Name + ".aspx");
    vt.Type = tableMap.Type;
    vt.RenderToFile(fileName, true);
    Response.WriteLine(fileName);

    string parentFileName = Path.Combine(BaseDirectory, tableMap.Type.Name + ".aspx.cs");
    bt.Type = tableMap.Type;
    bt.RenderToFile(parentFileName, fileName, true);
    Response.WriteLine(parentFileName);

    parentFileName = Path.Combine(BaseDirectory, tableMap.Type.Name + ".aspx.designer.cs");
    dt.Type = tableMap.Type;
    dt.RenderToFile(parentFileName, fileName, true);
    Response.WriteLine(parentFileName);

    Debug.WriteLine(String.Format("Created '{0}' in {1} ms.", tableMap.Type.Name, watch.Elapsed.TotalMilliseconds.ToString()));
  }
}

Download Samples

Click on the following link to download the three sample templates.

Join the mailing list

Get notified of new posts and related content to your inbox. I will never sell you anything and I will NEVER sell your email address.