Read classes in a namespace in a t4 template - t4

is there a way I can read in a namespace and loop through all the classes in a t4 template using reflection or something?
<#foreach (class poco in LoadNamespace("Web.Code.Entities.Poco").Classes ) { #>
public interface I<# poco.ClassName #>Repository
{
IQueryable< <# poco.ClassName #> > Get();
<# poco.ClassName #> Save(<# poco.ClassName #> entity);
bool Delete(<# poco.ClassName #> entity);
}
<#} #>
example meta data:
namespace Web.Code.Entities.Poco
{
public class Product
{
public int Id { get; set; }
public string Name{ get; set; }
public string Category{ get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name{ get; set; }
}
}
desired output:
public interface IProductRepository
{
IQueryable<Product> Get();
Product Save(Product entity);
bool Delete(Product entity);
}
public interface IEmployeeRepository
{
IQueryable<Employee> Get();
Employee Save(Employee entity);
bool Delete(Employee entity);
}

First install T4Toolbox from here
Then In your Template ensure you have the following lines at top :
<## assembly name="EnvDTE" #>
<## import namespace="EnvDTE" #>
<## include file="T4Toolbox.tt" #>
Then add these lines of codes:
<#+
private List<CodeClass> FindClasses(string nameSpace, string className)
{
List<CodeClass> result=new List<CodeClass>();
FindClasses(TransformationContext.Project.CodeModel.CodeElements,className,nameSpace,result,false);
return result;
}
private void FindClasses(CodeElements elements, string className,string searchNamespace,List<CodeClass> result,bool isNamespaceOk)
{
if (elements==null)return;
foreach (CodeElement element in elements)
{
if(element is CodeNamespace)
{
CodeNamespace ns = element as CodeNamespace;
if(ns != null)
{
if (ns.FullName == searchNamespace)
FindClasses(ns.Members, className,searchNamespace,result,true);
else
FindClasses(ns.Members, className,searchNamespace,result,false);
}
}
else if(element is CodeClass && isNamespaceOk)
{
CodeClass c = element as CodeClass;
if (c != null)
{
if(c.FullName.Contains(className))
result.Add(c);
FindClasses(c.Members, className,searchNamespace,result,true);
}
}
}
}
#>
Then you can find all classes in a namespace by calling like this (the second parameter filters all classes which their names contain the passed string) :
FindClasses("NameSpace.SubNameSpace",""))//All classes in the specifed namespace
OR
FindClasses("NameSpace.SubNameSpace","Repository")) //All classes in the specifed namespace which have "Repository" in their names
------------------------------------------ UPDATE FOR VS 2012 --------------------------
Use these functions and namespaces if your T4Toolbox is updated for VS 2012 :
//visual studio 2012+
<## assembly name="Microsoft.VisualStudio.Shell.11.0" #>
<## assembly name="Microsoft.VisualStudio.Shell.Interop" #>
<## import namespace="Microsoft.VisualStudio.Shell" #>
<## import namespace="Microsoft.VisualStudio.Shell.Interop" #>
private List<CodeClass> FindClasses(string nameSpace, string className,string baseClassName)
{
List<CodeClass> result=new List<CodeClass>();
FindClasses(GetProject().CodeModel.CodeElements,className,baseClassName,nameSpace,result,false);
return result;
}
private void FindClasses(CodeElements elements, string className,string baseClassName,string searchNamespace,List<CodeClass> result,bool isNamespaceOk)
{
if (elements==null)return;
foreach (CodeElement element in elements)
{
if(element is CodeNamespace)
{
CodeNamespace ns = element as CodeNamespace;
if(ns != null)
{
if (ns.FullName == searchNamespace)
FindClasses(ns.Members, className,baseClassName,searchNamespace,result,true);
else
FindClasses(ns.Members, className,baseClassName,searchNamespace,result,false);
}
}
else if(element is CodeClass && isNamespaceOk)
{
CodeClass c = element as CodeClass;
if (c != null)
{
if(c.FullName.Contains(className) && (baseClassName==null || (HasIt(c.Bases ,baseClassName) && c.Name != baseClassName)))
result.Add(c);
FindClasses(c.Members, className,baseClassName,searchNamespace,result,true);
}
}
}
}
private bool HasIt(CodeElements elements,string name)
{
foreach (CodeElement element in elements)
{
if (element.Name==name)
return true;
}
return false;
}
private Project GetProject()
{
// Get DTE
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
return project;
}
private string GetDefaultNamespace()
{
// Get DTE
var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
var vsSolution = (IVsSolution)TransformationContext.Current.GetService(typeof(SVsSolution));
IVsHierarchy vsHierarchy;
ErrorHandler.ThrowOnFailure(vsSolution.GetProjectOfUniqueName(project.FullName, out vsHierarchy));
uint projectItemId;
ErrorHandler.ThrowOnFailure(vsHierarchy.ParseCanonicalName(projectItem.FileNames[1], out projectItemId));
object defaultNamespace;
ErrorHandler.ThrowOnFailure(vsHierarchy.GetProperty(projectItemId, (int)VsHierarchyPropID.DefaultNamespace, out defaultNamespace));
return ((string)defaultNamespace);
}
so your search will be something like this :
FindClasses(GetDefaultNamespace(),"Repository","RepositoryBase")

Your best bet is to use the Visual Studio CodeModel, which is part of the DTE automation APIs.
You can get hold of the DTE by making your template HostSpecific, casting your Host property to IServiceProvider and then doing a GetService() call.
There's an example of doing this by Colin Eberhardt on CodeProject:
http://www.codeproject.com/Articles/39071/Declarative-Dependency-Property-Definition-with-T4.aspx

I am using T4 in a .net core project,
My T4 is fairly large, so just extracting the relevant info here..
[In my case I am looking for Entity Framework Models and I know these are the only classes in the namespace, you may need to filter out your own]
referencing these nuget packages:
<## assembly name="$(UserProfile)\.nuget\packages\Microsoft.VisualStudio.TextTemplating.Interfaces.14.0\14.3.25407\lib\net45\Microsoft.VisualStudio.TextTemplating.Interfaces.14.0.dll" #>
<## assembly name="$(UserProfile)\.nuget\packages\Microsoft.VisualStudio.TextTemplating.14.0\14.3.25407\lib\net45\Microsoft.VisualStudio.TextTemplating.14.0.dll" #>
these imports/includes:
<## import namespace="EnvDTE" #>
<## import namespace="Microsoft.VisualStudio.TextTemplating" #>
<## import namespace="Microsoft.VisualStudio.TextTemplating.Interfaces" #>
<## include file="$(UserProfile)\.nuget\packages\T4.VsAutomationHelper\1.0.0\tools\ttinc\VsAutomationHelper.CS.ttinclude" #>
<## include file="$(UserProfile)\.nuget\packages\T4.TemplateFileManager\2.2.1\tools\ttinc\TemplateFilemanager.CS.ttinclude" #>
I have this method:
// Get all CodeClass Items in specified namespace
public List<EnvDTE.CodeClass> GetClassesInNameSpace(IEnumerable<ProjectItem> items, string nameSpace)
{
var classItems = new List<EnvDTE.CodeClass>();
var csFileProjectItems = items.Where(d => d.Properties.Item("FullPath").Value.ToString().EndsWith(".cs"));
foreach(ProjectItem csFileProjectItem in csFileProjectItems)
{
EnvDTE.FileCodeModel model = csFileProjectItem.FileCodeModel;
foreach(var modelCodeElements in model.CodeElements)
{
if (modelCodeElements is EnvDTE.CodeNamespace)
{
var codeNameSpace = ((EnvDTE.CodeNamespace)modelCodeElements);
if (codeNameSpace.FullName == nameSpace)
{
foreach(var modelCodeElementChild in codeNameSpace.Children)
{
if (modelCodeElementChild is EnvDTE.CodeClass)
{
classItems.Add((EnvDTE.CodeClass)modelCodeElementChild);
}
}
}
}
}
}
return classItems;
}
and call it using: (replace "App.Data.Model.Entities" with your own)
var projectItems = this.dteHelper.GetAllProjectItems();
var entityClasses = GetClassesInNameSpace(projectItems, "App.Data.Model.Entities");
foreach (var entityClass in entityClasses)
{
// entityClass.Name
// Important for .NetCore
// when calling fileManager.StartNewFile() specify all the parameters, its didn't work for me otherwise eg:
// code appreciated (file manager created before following code)
var fileProperties = new FileProperties()
{
BuildAction = BuildAction.Compile
};
fileManager.StartNewFile(dataAccessFileName, generatedFilesTargetProject, generatedFilesTargetFolder, fileProperties);
fileManager.IsAutoIndentEnabled = true;
fileManager.CanOverwriteExistingFile = true;
}

Related

Find child table with foreign key in t4 template

I am pretty new to using T4 templates to generate POCOs and I have a working series of templates now that generate my DTO, DAL and Manager objects as well as the associated CRUD SQL files, history trigger and table.
It works great for individual tables, but I have been unable to figure out how to include the child tables in the DTO and DAL. Essentially, I want to create Read Only properties in my DTO and the Get Data methods for them in the DAL object for the related child DTO of the foreign key.
Here is my DTOGenerator.tt which is executed from another .tt taking in the Table, Namespace, Ddestination folder and a string to represent the user executing the template.
Thanks in advance for the help.
<## import namespace="System" #><##>
<## import namespace="System.IO" #><##>
<## import namespace="System.Text" #><##>
<## assembly name="System.Data" #>
<##assembly name="Microsoft.SqlServer.Smo"#><##>
<##assembly name="Microsoft.SqlServer.ConnectionInfo"#><##>
<##assembly name="Microsoft.SqlServer.Management.Sdk.Sfc"#><##>
<##import namespace="Microsoft.SqlServer.Management.Smo"#><##>
<#+public void GenerateDTO(Table target_table, string classNamespace, string destinationFolder, string executed_by)
{
// StringBuilders for Fields, Properties and Parameters.
StringBuilder stbMembers = new StringBuilder();
StringBuilder stbUserSaveEditMembers = new StringBuilder();
StringBuilder stbAccessors = new StringBuilder();
StringBuilder stbUserSaveEditAccessors = new StringBuilder();
StringBuilder stbConstructorParameters = new StringBuilder();
StringBuilder stbClassProperties = new StringBuilder();
StringBuilder stbUserSaveEditClassProperties = new StringBuilder();
// Keep count so we don't whitespace the last property/column
int columnCount = target_table.Columns.Count;
int i = 0;
string col_identity = string.Empty;
// Iterate all columns
foreach (Column col in target_table.Columns)
{
i++;
string propertyType = GetNetDataType(col.DataType.Name);
// If we can't map it, skip it
if (string.IsNullOrWhiteSpace(propertyType))
{
// Skip
continue;
}
// Make propertytype nullable for nullable columns
if (col.Nullable && propertyType != "string" && propertyType != "byte[]")
{
propertyType += "?";
}
// Build Fields & Class Properties
string col_name = SnakeCaseToCamelCase(col.Name);
// Capture Identity column name
if(col.Identity)
{
col_identity = col_name;
}
else if (col.IsForeignKey)
{
//Create Read Only property to return child DTO via the child table DAL
}
// TABLE
// Members
stbMembers.Append("private ").Append(propertyType).Append(" _").Append(col.Name).Append(";\r\n\t\t");
// Accessors
if(i>1){stbAccessors.Append("\t\t");}
stbAccessors.Append("[DataMember()]\r\n");
stbAccessors.Append("\t\tpublic ").Append(propertyType).Append(" ").Append(col_name).Append("\r\n");
stbAccessors.Append("\t\t{\r\n");
stbAccessors.Append("\t\t\tget { return").Append(" _").Append(col.Name).Append("; }\r\n");
stbAccessors.Append("\t\t\tset\r\n");
stbAccessors.Append("\t\t\t{\r\n");
stbAccessors.Append("\t\t\t\tif (").Append("_").Append(col.Name).Append(" == value){ return; }\r\n");
stbAccessors.Append("\t\t\t\t_").Append(col.Name).Append(" = value;\r\n");
stbAccessors.Append("\t\t\t\tChanged = true;\r\n");
stbAccessors.Append("\t\t\t}\r\n");
stbAccessors.Append("\t\t}\r\n\r\n");
// Class Properties
stbClassProperties.Append("_").Append(col.Name).Append(" =").Append(" p").Append(col_name).Append(";");
if(i < columnCount)
{
stbClassProperties.Append("\r\n\t\t\t");
}
// Class Parameters
stbConstructorParameters.Append(propertyType).Append(" p").Append(col_name);
if(i < columnCount)
{
stbConstructorParameters.Append(",\r\n\t\t\t\t\t\t\t\t");
}
}
#>
using System;
using System.Runtime.Serialization;
namespace <#=classNamespace #>
{
#region " History "
// <remarks>
//-------------------------------------------------------------------------------------------------------------------------------
// Purpose : Data Transfer Object for <#=target_table.Name #>
// T4 Template : DTOGenerator.tt
// Date Created : <#=DateTime.Now.ToString(#"MM\/dd\/yyyy")#>
// Created By : <#=executed_by#>
// Notice : Copyright © <#=DateTime.Now.ToString(#"yyyy")#> GooeyPC, All rights reserved.
//-------------------------------------------------------------------------------------------------------------------------------
// History:
//
//-------------------------------------------------------------------------------------------------------------------------------
// </remarks>
#endregion
[DataContract()]
public class <#=target_table.Name #>DTO
{
#region " _MEMBERS "
// TABLE FIELDS
<#=stbMembers.ToString() #>
#endregion
#region " ACCESSORS "
/// <summary>
/// Returns TRUE if data on this object record has changed
/// </summary>
[DataMember()]
public bool Changed { get; set; }
<#=stbAccessors.ToString() #>
#endregion
#region " PROPERTIES - READ ONLY "
/// <summary>
/// Returns TRUE if primary key id != 0 (i.e. valid primary key id exists.)
/// </summary>
public virtual bool RecordExists
{
get { return (<#=col_identity #> != 0); }
}
#endregion
#region " CONSTRUCTORS "
/// <summary>
/// Description of <#=target_table.Name #>
/// </summary>
public <#=target_table.Name #>DTO()
{
// Default constructor is needed
}
/// <summary>
/// Description of <#=target_table.Name #>
/// </summary>
public <#=target_table.Name #>DTO(<#=stbConstructorParameters.ToString() #>)
{
// Set Class Properties
<#=stbClassProperties.ToString() #>
}
#endregion
}
}
<#+
// Write new class to its own file
SaveOutput(destinationFolder, target_table.Name + "DTO.cs");}
#>

C# How to make a ConcurrentQueue to be cleaned by the condition

How to make a ConcurrentQueue to be cleaned by the condition for the first elements. Eg to clear older blog posts. I came up with this idea of a ConditionConcurrentQueue:
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Threading;
public class ConditionConcurrentQueue<T> : ConcurrentQueue<T>
where T: class
{
public ConditionConcurrentQueue(Func<T, bool> condition)
: this(null, condition)
{ }
public ConditionConcurrentQueue(IEnumerable<T> items, Func<T, bool> condition)
: base(items)
{
_condition = condition;
}
private Func<T, bool> _condition;
public virtual void Enqueue(T item)
{
T removed;
bool cleaningRun = true;
int failedCnt = 0;
while (!IsEmpty && cleaningRun && failedCnt < 10)
{
if (TryPeek(out removed))
{
bool result = _condition.Invoke(removed);
if (!result)
{
if (!TryDequeue(out removed))
{
failedCnt++;
Thread.Sleep(10);
}
}
else
cleaningRun = false;
}
else
{
failedCnt++;
Thread.Sleep(10);
}
}
base.Enqueue(item);
}
}
Use this ConditionConcurrentQueue could be so:
class Blog
{
public ConditionConcurrentQueue<Post> Posts { get; set; }
}
class Post
{
public DateTime Time { get; set; }
public string Text { get; set; }
}
class Program
{
static void Main(string[] args)
{
Blog blog = new Blog
{
Posts = new ConditionConcurrentQueue<Post>(
new Post[] {
new Post { Time = DateTime.Now - TimeSpan.FromMinutes(80), Text = "Title 1" },
new Post { Time = DateTime.Now - TimeSpan.FromMinutes(60), Text = "Title 2" },
new Post { Time = DateTime.Now - TimeSpan.FromMinutes(40), Text = "Title 3" },
},
p => p.Time > DateTime.Now - TimeSpan.FromHours(1))
};
blog.Posts.Enqueue(new Post { Time = DateTime.Now - TimeSpan.FromMinutes(20), Text = "Title 4" });
foreach (Post post in blog.Posts.ToList())
Console.WriteLine(post.Text);
}
}
maybe it is too primitive solution.
I would appreciate any improvements. Thanks.
also, you can try through the extension method:
public static ICollection<T> Enqueue<T>(this ConcurrentQueue<T> field, T item, Func<T, bool> predicate)
{
ICollection<T> removed = field.TryDequeue<T>(predicate);
field.Enqueue(item);
return removed;
}
public static ICollection<T> TryDequeue<T>(this ConcurrentQueue<T> field, Func<T, bool> predicate)
{
T comparedItem;
var removedList = new List<T>();
while (field.TryPeek(out comparedItem))
{
if (!predicate.Invoke(comparedItem))
{
if (field.TryDequeue(out comparedItem))
removedList.Add(comparedItem);
else
break;
}
else
break;
}
return removedList;
}
As of .NET Core 2.0 / .NET Standard 2.1 / .NET Framework 5.0, there is a Clear() method on ConcurrentQueue<T>. See: ConcurrentQueue.Clear.

Change a LINQ expression predicate from one type to another

I have two unrelated classes. One is exposed as API, and the other is used internally by 3rd party API.
Entity is exposed from our API, while EntityProvider is from the 3rd party assembly.
class Entity
{
public A { get; set; }
}
class EntityProvider
{
public A { get; set; }
}
Consumers of our API will provide predicates of the form Expression <Func<Entity, bool>> and I need to modify it to Expression <Func<EntityProvider, bool>> so that I can pass the same to internal 3rd party assembly.
Please help with this conversion.
Since Expressions in .NET are immutable, the only way to do this is to rebuild the whole expression. To do this usually involves inheriting from the ExpressionVisitor class. Depending on the complexity of the expressions you have to convert this could be quite complicated.
This is a simple example of a visitor that will work with simple expressions( like x=>x.Someproperty == somevalue ). It's just an example to get you started and it's in no way finished or tested(it won't handle method calls in the expression for example)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
//Type from which to convert
public class A
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
//Type to which we want the Expression converted
public class B
{
public int Property1 { get; set; }
public int Property2 { get; set; }
}
class Program
{
static void Main(string[] args)
{
//the expression we want to convert expresion
Expression<Func<A, bool>> expA = x => x.Property1 == 6 && x.Property2 == 3;
var visitor = new ParameterTypeVisitor<A,B>(expA);
var expB = visitor.Convert();
var b = new B() { Property1 = 6, Property2 = 3 };
//try the converted expression
var result = expB.Compile().Invoke(b);
}
}
public class ParameterTypeVisitor<TFrom,TTo> : ExpressionVisitor
{
private Dictionary<string, ParameterExpression> convertedParameters;
private Expression<Func<TFrom, bool>> expression;
public ParameterTypeVisitor(Expression<Func<TFrom,bool>> expresionToConvert )
{
//for each parameter in the original expression creates a new parameter with the same name but with changed type
convertedParameters = expresionToConvert.Parameters
.ToDictionary(
x => x.Name,
x => Expression.Parameter(typeof (TTo), x.Name)
);
expression = expresionToConvert;
}
public Expression<Func<TTo,bool>> Convert()
{
return (Expression<Func<TTo, bool>>)Visit(expression);
}
//handles Properties and Fields accessors
protected override Expression VisitMember(MemberExpression node)
{
//we want to replace only the nodes of type TFrom
//so we can handle expressions of the form x=> x.Property.SubProperty
//in the expression x=> x.Property1 == 6 && x.Property2 == 3
//this replaces ^^^^^^^^^^^ ^^^^^^^^^^^
if (node.Member.DeclaringType == typeof(TFrom))
{
//gets the memberinfo from type TTo that matches the member of type TFrom
var memeberInfo = typeof (TTo).GetMember(node.Member.Name).First();
//this will actually call the VisitParameter method in this class
var newExp = Visit(node.Expression);
return Expression.MakeMemberAccess(newExp, memeberInfo);
}
else
{
return base.VisitMember(node);
}
}
// this will be called where ever we have a reference to a parameter in the expression
// for ex. in the expression x=> x.Property1 == 6 && x.Property2 == 3
// this will be called twice ^ ^
protected override Expression VisitParameter(ParameterExpression node)
{
var newParameter = convertedParameters[node.Name];
return newParameter;
}
//this will be the first Visit method to be called
//since we're converting LamdaExpressions
protected override Expression VisitLambda<T>(Expression<T> node)
{
//visit the body of the lambda, this will Traverse the ExpressionTree
//and recursively replace parts of the expression we for which we have matching Visit methods
var newExp = Visit(node.Body);
//this will create the new expression
return Expression.Lambda(newExp,convertedParameters.Select(x=>x.Value));
}
}

How do I apply a default IComparable<T> in a Linq OrderBy clause

I have a type which has a default sort order as it implements IComparable<T> and IComparable. I'm not getting the results I expect from LINQ , basically it looks as if the IComparable<T> which the type implements is not being applied.
I thought I would get the result I want with an expression in the form:
var result = MyEnumerable<T>.OrderBy(r => r);
where T itself implements IComparable<T>. It's not happening.
I can see related questions where specific IComparable<T> classes are specified for the sort, but I can't find one which uses the default IComparable<T> implemented by T itself.
My syntax is clearly incorrect. What is the correct syntax please?
Thanks in advance.
OrderBy uses the default comparer Comparer<T>.Default which in turn will default to use the IComparable<T> implementation for T, or the non-generic IComparable if the former does not exist.
This code works:
public class Program
{
static void Main(string[] args)
{
var list = new List<Stuff>
{
new Stuff("one"),
new Stuff("two"),
new Stuff("three"),
new Stuff("four")
};
var sorted = list.OrderBy(x => x);
foreach (var stuff in sorted)
{
Console.Out.WriteLine(stuff.Name);
}
}
}
public class Stuff : IComparable<Stuff>
{
public string Name { get; set; }
public Stuff(string name)
{
Name = name;
}
public int CompareTo(Stuff other)
{
return String.CompareOrdinal(Name, other.Name);
}
}
public static class GenericSorter
{
public static IOrderedEnumerable<T> Sort<T>(IEnumerable<T> toSort, Dictionary<string, SortingOrder> sortOptions)
{
IOrderedEnumerable<T> orderedList = null;
foreach (KeyValuePair<string, SortingOrder> entry in sortOptions)
{
if (orderedList != null)
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenBy");
}
else
{
orderedList = orderedList.ApplyOrder<T>(entry.Key, "ThenByDescending");
}
}
else
{
if (entry.Value == SortingOrder.Ascending)
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderBy");
}
else
{
orderedList = toSort.ApplyOrder<T>(entry.Key, "OrderByDescending");
}
}
}
return orderedList;
}
private static IOrderedEnumerable<T> ApplyOrder<T>(this IEnumerable<T> source, string property, string methodName)
{
ParameterExpression param = Expression.Parameter(typeof(T), "x");
Expression expr = param;
foreach (string prop in property.Split('.'))
{
expr = Expression.PropertyOrField(expr, prop);
}
Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), expr.Type);
LambdaExpression lambda = Expression.Lambda(delegateType, expr, param);
MethodInfo mi = typeof(Enumerable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof(T), expr.Type);
return (IOrderedEnumerable<T>)mi.Invoke(null, new object[] { source, lambda.Compile() });
}
}

modify a TT template to add required html element

I am trying to create a t4 template to help speed up my Create Form template.
Is it possible to add extra html depending if a model's property is required?
e.g.
[Required]
[Display(Name = "Contact Email Address:")]
public string ContactEmailAddress { get; set; }
Now in my tt file do something like
foreach (ModelProperty property in GetModelProperties(mvcHost.ViewDataType)) {
if (!property.IsPrimaryKey && !property.IsReadOnly) {
#>
<div>
#Html.LabelFor(model => model.<#= property.Name #>)
#Html.EditorFor(model => model.<#= property.Name #>)
#Html.ValidationMessageFor(model => model.<#= property.Name #>)
if(this.Required==true){<span class="required-field"></span>}
</div>
<#
}
Or is this not possible?
1,Open this file:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcViewWithContextScaffolder\ModelPropertyFunctions.include.t4
2, Add some property to ModelProperty
class ModelProperty {
public string Name { get; set; }
public string AssociationName { get; set; }
public string ValueExpression { get; set; }
public string ModelValueExpression { get; set; }
public string ItemValueExpression { get; set; }
public EnvDTE.CodeTypeRef Type { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsForeignKey { get; set; }//
public bool IsReadOnly { get; set; }//
public bool IsRequired{ get; set;}//Here is your customer Property
public bool Scaffold { get; set; }
}
3, Add an method out under this class
bool IsRequired(EnvDTE.CodeProperty propertyType)
{
foreach (EnvDTE.CodeAttribute attribute in propertyType.Attributes)
{
if (String.Equals(attribute.FullName, "System.ComponentModel.DataAnnotations.RequiredAttribute", StringComparison.Ordinal))
{
return true;
}
}
return false;
}
4,Go to the bottom of this file to modify the method GetEligibleProperties:
List<ModelProperty> GetEligibleProperties(EnvDTE.CodeType typeInfo) {
List<ModelProperty> results = new List<ModelProperty>();
if (typeInfo != null) {
foreach (var prop in typeInfo.GetPublicMembers().OfType<EnvDTE.CodeProperty>()) {
if (prop.HasPublicGetter() && !prop.IsIndexerProperty() && IsBindableType(prop.Type)) {
string valueExpression = GetValueExpressionSuffix(prop);
results.Add(new ModelProperty {
Name = prop.Name,
AssociationName = GetAssociationName(prop),
ValueExpression = valueExpression,
ModelValueExpression = "Model." + valueExpression,
ItemValueExpression = "item." + valueExpression,
Type = prop.Type,
IsPrimaryKey = IsPrimaryKey(prop),
IsForeignKey = IsForeignKey(prop),
IsRequired=IsRequired(prop),//Here is your customer property.
IsReadOnly = !prop.HasPublicSetter(),
Scaffold = Scaffold(prop)
});
}
}
}
return results;
}
5, go to the file
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\Extensions\Microsoft\Web\Mvc\Scaffolding\Templates\MvcViewWithContextScaffolder\Edit.cs.t4
Add the following check before your ValidationMessageFor
#Html.ValidationMessageFor(model => model.<#= property.Name #>):
Codes like this:
<#
if (property.IsRequired) {
#>
*<!--your html code-->
<#
}
#>
This would be possible but would take more work than what you have here. You could add a Required property to ModelProperty and then set it by looking for the required attribute on the property when getting the model properties. You can take a look at the code that determines whether or not a property is a primary key in the .tt templates for an example.
If someone still searching for a solution...
I'm using the MetadataType attibute to define the property attributes like Required or DisplayName in a seperate class.
Sample:
[MetadataType(typeof(personMetaData))]
public partial class Person
{
}
public class personMetaData
{
[DisplayName("Surname")]
[Required]
public object Name { get; set; }
}
If you want to access these attributes in the t4-template you have to extend the ModelProperty class and creator in the template-file.
Put the following code at the bottom of your template (e.g. List.tt). You have to replace the existing code.
<#+
// Describes the information about a property on the model
public class ModelProperty
{
public string Name { get; set; }
public string ValueExpression { get; set; }
public Type UnderlyingType { get; set; }
public bool IsPrimaryKey { get; set; }
public bool IsReadOnly { get; set; }
public string DisplayName { get; set; }
}
// Change this list to include any non-primitive types you think should be eligible for display/edit
private static Type[] bindableNonPrimitiveTypes = new[]
{
typeof (string),
typeof (decimal),
typeof (Guid),
typeof (DateTime),
typeof (DateTimeOffset),
typeof (TimeSpan),
};
// Call this to get the list of properties in the model. Change this to modify or add your
// own default formatting for display values.
public List<ModelProperty> GetModelProperties(Type type)
{
List<ModelProperty> results = GetEligibleProperties(type);
foreach (ModelProperty prop in results)
{
if (prop.UnderlyingType == typeof (double) || prop.UnderlyingType == typeof (decimal))
{
prop.ValueExpression = "String.Format(\"{0:F}\", " + prop.ValueExpression + ")";
}
else if (prop.UnderlyingType == typeof (DateTime))
{
prop.ValueExpression = "String.Format(\"{0:g}\", " + prop.ValueExpression + ")";
}
}
return results;
}
// Call this to determine if the property represents a primary key. Change the
// code to change the definition of primary key.
private bool IsPrimaryKey(PropertyInfo property)
{
if (string.Equals(property.Name, "id", StringComparison.OrdinalIgnoreCase))
{
// EF Code First convention
return true;
}
if (string.Equals(property.Name, property.DeclaringType.Name + "id", StringComparison.OrdinalIgnoreCase))
{
// EF Code First convention
return true;
}
foreach (object attribute in property.GetCustomAttributes(true))
{
if (attribute is KeyAttribute)
{
// WCF RIA Services and EF Code First explicit
return true;
}
var edmScalar = attribute as EdmScalarPropertyAttribute;
if (edmScalar != null && edmScalar.EntityKeyProperty)
{
// EF traditional
return true;
}
/* var column = attribute as ColumnAttribute;
if (column != null && column.IsPrimaryKey)
{
// LINQ to SQL
return true;
}*/
}
return false;
}
// This will return the primary key property name, if and only if there is exactly
// one primary key. Returns null if there is no PK, or the PK is composite.
private string GetPrimaryKeyName(Type type)
{
IEnumerable<string> pkNames = GetPrimaryKeyNames(type);
return pkNames.Count() == 1 ? pkNames.First() : null;
}
// This will return all the primary key names. Will return an empty list if there are none.
private IEnumerable<string> GetPrimaryKeyNames(Type type)
{
return GetEligibleProperties(type).Where(mp => mp.IsPrimaryKey).Select(mp => mp.Name);
}
// Helper
private List<ModelProperty> GetEligibleProperties(Type type)
{
List<ModelProperty> results = new List<ModelProperty>();
foreach (PropertyInfo prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Type underlyingType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
if (prop.GetGetMethod() != null && prop.GetIndexParameters().Length == 0 &&
IsBindableType(underlyingType))
{
var displayName = prop.Name;
// Search in Metadata
var metadata = type.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray().FirstOrDefault();
if (metadata != null)
{
var metaPropery = metadata.MetadataClassType.GetProperty(prop.Name);
if (metaPropery != null)
{
displayName = ((DisplayNameAttribute)metaPropery.GetCustomAttributes(typeof (DisplayNameAttribute), true).First()).DisplayName;
}
}
results.Add(new ModelProperty
{
Name = prop.Name,
ValueExpression = "Model." + prop.Name,
UnderlyingType = underlyingType,
IsPrimaryKey = IsPrimaryKey(prop),
IsReadOnly = prop.GetSetMethod() == null,
DisplayName = displayName
});
}
}
return results;
}
// Helper
private bool IsBindableType(Type type)
{
return type.IsPrimitive || bindableNonPrimitiveTypes.Contains(type);
}
#>
Now you can access these attributes like this:
<#
List<ModelProperty> properties = GetModelProperties(mvcHost.ViewDataType);
foreach (ModelProperty property in properties) {
if (!property.IsPrimaryKey) {
#>
<th>
<#= property.DisplayName #><#= property.AllowEmptyStrings ? "*" : "" #>
</th>
<#
}
}
#>
T4 templates have changed with MVC5. To accomplish this in MVC5, I've written a tutorial here: https://johniekarr.wordpress.com/2015/05/16/mvc-5-t4-templates-and-view-model-property-attributes/

Resources