Can not use DisplayAttribute in T4 template - c#

In my custom T4 template I have to read DisplayAttribute of a type. But I'm keep getting different errors after struggling with several solutions.
Here is part of my .tt file :
<## template debug="true" hostspecific="true" language="C#" compilerOptions="/langversion:10" #>
<## assembly name="System" #>
<## assembly name="System.Runtime" #>
<## assembly name="System.Core" #>
<## assembly name="System.ComponentModel.Annotations" #>
<## import namespace="System" #>
<## import namespace="System.Reflection" #>
<## import namespace="System.Runtime" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## import namespace="System.ComponentModel.DataAnnotations" #>
<## output extension=".cs" #>
<#
static string GetEnumValueDisplayName(Enum value)
{
Type type = value.GetType();
MemberInfo[] memInfo = type.GetMember(value.ToString());
DisplayAttribute? dispalyAttribute = null;
if (memInfo != null && memInfo.Length > 0)
{
object[] attrs = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
if (attrs != null && attrs.Length > 0)
dispalyAttribute = attrs[0] as DisplayAttribute;
}
return dispalyAttribute?.Name ?? "";
}
#>
<#
foreach(myEnum en in Enum.GetValues(typeof(MyEnum)))
{#>
public static class <#=#en.ToString()#> { public const string Value = "<#=#en.ToString()#>";public const string Text = "<#=#GetEnumValueDisplayName(en)#>";}
<#}#>
And here is my compile time error :
Compiling transformation: The type name 'DisplayAttribute' could not
be found in the namespace 'System.ComponentModel.DataAnnotations'.
This type has been forwarded to assembly
'System.ComponentModel.DataAnnotations, Version=4.0.0.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35' Consider adding a
reference to that assembly.
I am using .net 6 with Visual Studio 2022(17.0.4).

I am using .net 6
That would be a problem. The VS T4 engine only supports .NET Framework at this point, which means that it cannot load any .NET Core libraries during the transformation. We have started working on a .NET Core version of the engine but there is no ETA on when this would become available. Any updates would be communicated through this Suggestion Ticket.

Remove <## assembly name="System.ComponentModel.Annotations" #> and add using System.ComponentModel.DataAnnotations; to the regular using statements.

Related

How to read the assembly name from a non SDK style csproj file in C# using XML?

My code is:
var doc = new XPathDocument(projectFilePath);
var nav = doc.CreateNavigator();
var nsmgr = new XmlNamespaceManager(nav.NameTable);
nsmgr.AddNamespace("p", "http://schemas.microsoft.com/developer/msbuild/2003");
var assemblyName = nav.SelectSingleNode("/p:Project/p:PropertyGroup/p:AssemblyName/text()", nsmgr).Value;
However, I find it unreasonable that I must specify the XML namespace when it is an attribute of the root node (<Project>)
So my question is - how can I use the namespace of the root node without replicating it here? Extra bonus points for concise code.
P.S.
I am perfectly aware that I can read the assembly name just be "grepping" the respective line and parsing it. I am specifically interested in XML here.
EDIT 1
The csproj file is:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<PropertyGroup>
<ProjectType>Local</ProjectType>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{09082B9A-906C-4A17-A2E5-6C947DAC7C85}</ProjectGuid>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ApplicationIcon>
</ApplicationIcon>
<AssemblyKeyContainerName>
</AssemblyKeyContainerName>
<AssemblyName>MyApp</AssemblyName>
<OutputType>Exe</OutputType>
...
After navigating to an element, you can use GetNamespacesInScope() to retrieve the namespace for local scope and use it in your code.
var doc = new XPathDocument(projectFilePath);
var nav = doc.CreateNavigator();
var nsmgr = new XmlNamespaceManager(nav.NameTable);
nav.MoveToFollowing(XPathNodeType.Element);
var ns = nav.GetNamespacesInScope(XmlNamespaceScope.Local).FirstOrDefault();
nsmgr.AddNamespace("p", ns.Value);
var assemblyName = nav.SelectSingleNode(
"/p:Project/p:PropertyGroup/p:AssemblyName/text()", nsmgr).Value;

T4 parameter directive using .Net Core

I have an issue in "CallContext.LogicalGetData" method using .net Core when i try to send parameter to runtime text template t4 (net core)
Below tt file :
<## template language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## parameter name="firstName" type="System.String" #>
<## parameter name="lastName" type="System.String" #>
and Cs call method :
var pt1 = new ParamTemplate1();
pt1.Session = new Dictionary<string, object>();
pt1.Session["firstName"] = "David";
pt1.Session["lastName"] = "Giard";
pt1.Initialize();
var outputText1 = pt1.TransformText();
Console.WriteLine(outputText1);
Hello <#=firstName #> <#=lastName #>!
the problem is du to " System.Runtime.Remoting" library is not supported in .net core
Any ideas or workaround ??
Thanks.
Sorry for the late reply, but if you still want to use the t4 templates, you can replace the parameters directives with properties at the end of the t4 document:
<#+
public string FirstName { get; set; }
public string LastName { get; set; }
#>
And then call it:
var pt1 = new ParamTemplate1
{
FirstName = "David",
LastName = "Giard"
};
var outputText1 = pt1.TransformText();
Console.WriteLine(outputText1);

how to make T4 text template database driven

I am experimenting with code generation atm, I've writen a small template that does what I need it to do, but I need to hardcode the properties myself, I want to extract class and property names from a database where the tables are the classes and index fields are the properties. Idea is to write a filter class based on table indexes. The code I have now assumes that I have 1 table and 3 fields to use as filter criteria.
Currently don't have any code to generate the compiled version as VS does this internally.
Here is what I have so far:
<## template debug="false" hostspecific="false" language="C#" #>
<## assembly name="System.Core" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Text" #>
<## import namespace="System.Collections.Generic" #>
<## parameter name="namespacename" type="System.String" #>
<## parameter name="filterobjectname" type="System.String" #>
<## parameter name="property1" type="System.String" #>
<## parameter name="property2" type="System.String" #>
<## parameter name="property3" type="System.String" #>
<## output extension=".cs" #>
using AzzWork.Business;
using Ventura.Business.Entities;
<# string _namespacename; if(this.namespacename==null){_namespacename = "Ventura.Business";}else{_namespacename = this.namespacename;} #>
<# string _classname; if(this.filterobjectname==null){_classname = "BusinessObject";}else{_classname = this.filterobjectname;}#>
<# string _property1; if(this.property1==null){_property1 = "LastName";}else{_property1 = property1;}#>
<# string _property1c = _property1.Substring(0, 1).ToLower() + _property1.Substring(1);#>
<# string _property2; if(this.property2==null){_property2 = "Email";}else{_property2 = property2;}#>
<# string _property2c = _property2.Substring(0, 1).ToLower() + _property2.Substring(1);#>
<# string _property3; if(this.property3==null){_property3 = "FirstName";}else{_property3 = property3;}#>
<# string _property3c = _property3.Substring(0, 1).ToLower() + _property3.Substring(1);#>
<# string seperator1; if((_property1!=null && _property2!=null) || (_property1!=null && _property2==null && _property3!=null)){seperator1=", ";}else{seperator1="";}#>
<# string seperator2; if(_property2!=null && _property3!=null){seperator2=", ";}else{seperator2="";} #>
namespace <#= _namespacename #>
{
public static class <#= _classname #>FilterFactory{
internal static AzzFilterCollection<<#= _classname #>> GetFilterFor<#= _classname #>(<#if(_property1!=null){#>string <#=_property1c #>Part<#}#><#=seperator1#><#if(_property2!=null){#>string <#=_property2c #>Part<#}#><#=seperator2#><#if(_property3!=null){#>string <#=_property3c #>Part<#}#>)
=> new AzzFilterCollection<<#=_classname#>>()
<#if(_property1!=null){ #>
.AddFilterFor<#= _property1 #>(<#=_property1c #>Part)
<#}#>
<#if(_property2!=null){ #>
.AddFilterFor<#= _property2 #>(<#=_property2c #>Part)
<#}#>
<#if(_property3!=null){ #>
.AddFilterFor<#= _property3 #>(<#=_property3c #>Part)<#}#>;
<#if (_property1!=null){#>
internal static AzzFilterCollection<<#=_classname #>> AddFilterFor<#= _property1 #>(this AzzFilterCollection<<#= _classname#>> filterCollection, string <#= _property1c #>Part)
{
if (<#= _property1c #>Part == null)
return filterCollection;
var filter = new AzzFilter<<#=_classname#>>(<#=_classname#>.ColumnNames.<#=_property1#>, <#=_property1c#>Part);
return filterCollection.AddFilter(filter);
}
<#}#>
<#if (_property2!=null){#>internal static AzzFilterCollection<<#=_classname #>> AddFilterFor<#= _property2 #>(this AzzFilterCollection<<#= _classname#>> filterCollection, string <#= _property2c #>Part)
{
if (<#= _property2c #>Part == null)
return filterCollection;
var filter = new AzzFilter<<#=_classname#>>(<#=_classname#>.ColumnNames.<#=_property2#>, <#=_property2c#>Part);
return filterCollection.AddFilter(filter);
}
<#}#>
<#if (_property3!=null){#>
internal static AzzFilterCollection<<#=_classname #>> AddFilterFor<#= _property3 #>(this AzzFilterCollection<<#= _classname#>> filterCollection, string <#= _property3c #>Part)
{
if (<#= _property3c #>Part == null)
return filterCollection;
var filter = new AzzFilter<<#=_classname#>>(<#=_classname#>.ColumnNames.<#=_property3#>, <#=_property3c#>Part);
return filterCollection.AddFilter(filter);
}
<#}#>
}
}
I guess you're looking at something for SQL.
Does this helps you?
This, This (same),
Also this

Codegeneration with usings

I´m using CodeDOM to build code from several xsd-files. So let´s assume we have some mapping from namespaces as defined in the xsd to those within the assemblies already created:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:abw="MyNamespace" xmlns:bfm="BaseNamespace"
xmlns:gml="http://www.opengis.net/gml/3.2" elementFormDefault="qualified" targetNamespace="MyNamespace"
version="1.0.1.0">
<import namespace="BaseNamespace" schemaLocation="Base.xsd"/>
<import namespace="http://www.opengis.net/gml/3.2" schemaLocation="http://schemas.opengis.net/gml/3.2.1/gml.xsd"/>
<element name="MyClass" substitutionGroup="bfm:MyBaseClass" type="abw:MyClassType"/>
<complexType name="MyClassType">
<complexContent>
<extension base="bfm:MyBaseClassType">
<sequence>
<element maxOccurs="unbounded" minOccurs="0" name="Property1" type="gml:ReferenceType">
<annotation>
<appinfo>
<targetElement xmlns="http://www.opengis.net/gml/3.2">abw:MyClass2</targetElement>
<reversePropertyName xmlns="http://www.opengis.net/gml/3.2">abw:MyClassAlias</reversePropertyName>
</appinfo>
</annotation>
</element>
</sequence>
</extension>
</complexContent>
</complexType>
</schema>
Now - as the assemblies for the namespaces BaseNamespace and gml already exist - I want to build only the source-code for MyClass which should automatically reference the base-class bfm:MyBaseClass and gml:ReferenceType by adding a using-directive.
So I created a CodeNamespace for targetNamespace that applies the namespace MyNamespace from the xsd. Now I loop the types within that CodeNamespace and filter those that exist within my xsd as all the other types from the referenced xsd-files already have an assembly assigned to them. If a type was not defined in my xsd it should not be added to the code, however a using should be added.
var codeNamepscace = new CodeNamespace("targetNamepscae");
var tmp = code.Types.Cast<CodeTypeDeclaration>().ToArray();
foreach (var type in tmp)
{
var typeFromSchema = schema.SchemaTypes.Names.Cast<XmlQualifiedName>().FirstOrDefault(x => x.Name == type.Name);
if (typeFromSchema == null)
{
string xsdNamespace = ???; // how to get the xsd-namespace for the current type here?
Console.WriteLine(String.Format("Found referenced type {0} which is not declared in current schema", type.Name));
// omit the type from the current namespace and add a using-directive to the generated source-file if not yet done
code.Types.Remove(type);
// add a using-directive for the type if not already done
if (!((IList)code.Imports).Contains(typeFromSchema.Namespace)) code.Imports.Add(new CodeNamespaceImport(GetDotNetNSFromXsdNS(xsdNamespace)));
}
}
The medthod GetDotNetNSFromXsdNS defines a mapping from the namespaces defined in the xsd-files to those within the assembly.
My question now is this: how do I get the namespace from within the xsd for a type within my CodeNamepspace? In particular: how can I exclude gml:ReferenceType and bfm:MyBaseClass from generation and add them via using?
The following solution works for me - although I consider it more a workaround:
var tmp = code.Types.Cast<CodeTypeDeclaration>().ToArray(); // temp. copy of our types-list to avoid ModifiedException during iteration
foreach (var type in tmp)
{
var attributes = type.CustomAttributes.Cast<CodeAttributeDeclaration>().Where(x => x.Name.StartsWith("System.Xml.Serialization"));
var namespaceAttribute = attributes.SelectMany(x => x.Arguments.Cast<CodeAttributeArgument>()).FirstOrDefault(x => x.Name == "Namespace");
string xsdNamespace = ((CodePrimitiveExpression)namespaceAttribute.Value).Value as string;
if (xsdNamespace != schema.TargetNamespace)
{
Console.WriteLine("INFO: Found referenced type {0} which is not declared in current schema", type.Name);
// omit the type from the current namespace and add a using-directive to the generated source-file if not yet done
code.Types.Remove(type);
var nameWithinAssembly = this.m_typeMapper.GetDotNetNamespaceFromXsdNamespace(xsdNamespace);
// add a using-directive for the type if not already done
if (!(code.Imports.Cast<CodeNamespaceImport>().Any(x => x.Namespace == nameWithinAssembly))) code.Imports.Add(new CodeNamespaceImport(nameWithinAssembly));
}
}
Here we assume that the namespace from the xsd is mirrored in the serialization-attributes. So we get the CustomAttributes for every type within ozur CodeNameapace and check if it has an Namespace-property defined on any of its attributes from System.Xml.Serialization. Finally all we have to do is to check if this namespace used for serialization is equal to the schemas target-namespace. If this condition doesn´t pass the type should be inferred using a using-directive which is done with the last line.

How to create a method that encapsulates the T4 template text section?

Instead of this .tt:
<## template debug="false" hostspecific="true" language="C#" #>
<## import namespace="System.IO" #>
<## output extension=".txt" #>
<## assembly name="System"#>
<# message = "hello world" ; #>
blah blah blah etc. very complex example with embedded expression like
<#=message#>
I'd like to have an output function that would return the output blah blah etc.:
<## template debug="false" hostspecific="true" language="C#" #>
<## import namespace="System.IO" #>
<## output extension=".txt" #>
<## assembly name="System"#>
<#output();#>
<#+ output() { #>
blah blah blah etc. very complex example with embedded expression like
<#=message#>
<#}
#>
Of course the syntax above is not correct. How to do this ?
This is an alternative solution not using class feature blocks <#+ ... #>.
Using a lambda expression inside usual statement blocks <# ... #> allows defining a local function as follows:
<## template language="C#" #>
<## output extension=".txt" #>
<# Action output = () => { #>
loooooooong text <#= "message" #>
<# }; #>
<# output(); #>
This template produces the output below:
loooooooong text message
Actually you're very close with what you've got there.
I find it helps to remember that the template is essentially a C#/VB class under the hood, so when you use a <#+ #> block, you're really just adding a member to the class.
Once you've started using the <#+ #> notation, you have to keep using it, as you're still adding stuff to the class at the member level, not adding the the TransformText() method which regular <# #> tags do.
The correct syntax would be
<#+ public void output() { #>
blah blah blah etc. very complex example with embedded expression like <#=message#>
<#+ }
#>

Categories