Skip to content

Instantly share code, notes, and snippets.

@SoulFireMage
Created January 15, 2018 17:35
Show Gist options
  • Save SoulFireMage/5e41ac33df26b5d7d029440c50c75d82 to your computer and use it in GitHub Desktop.
Save SoulFireMage/5e41ac33df26b5d7d029440c50c75d82 to your computer and use it in GitHub Desktop.
T4 Model Generator (Use with Datalayer.dll!)
For the model layer I use Automapper (for use in MVC Projects) so this file will need you to have successfully compiled a datalayer DLL and make it available.
It will add attributes that it finds from the Datalayer file (these were inserted via the custom Datalayer T4 file).
I had plenty of fun with reflection to make this work, but it does make adding new tables quite trivial!
<#@ template debug="true" hostspecific="true" language="C#"#>
<#@ output extension=".cs"#>
<#@ assembly name="System.Core"#>
<#@ assembly name="C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7\System.ComponentModel.DataAnnotations.dll" #>
<#@ import namespace="System"#>
<#@ import namespace="System.IO"#>
<#@ assembly name="$(SolutionDir)\DataLayer\bin\Debug\DataLayer.dll"#>
<#@ import namespace="System"#>
<#@ import namespace="System.Collections.Generic"#>
<#@ import namespace="System.Linq"#>
<#@ import namespace="System.Reflection"#>
<#@ import namespace="System.Text"#>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating"#>
<#@ import namespace="DataLayer.Data"#>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Runtime.CompilerServices" #>
<#@ import namespace="System.ComponentModel.DataAnnotations" #>
<#
/// This file generates 1 to 1 Model classes to Data classes.
/// It is intended to make it very easy to generate Model layer classes without having to find every single property and write them out.
/// It will also make it easy to work with automapper rather than having to manually transform a Contact into a customer using Linq Select New statements.
/// The correct usage however will require that we setup automapper and these classes to reflect the business domain and the members that we DO want to write back to.
/// For example, if we have a Contact at the datalayer, we may need the business layer to have Customer, Supplier etc and not necessarily have all fields present.
/// In simpler projects we may not need to begin by breaking the business layer up this way however for any CRM we do.
/// This file is a work in progress - R.G December 11th 2017
var t = typeof(DataLayer.Data.Contact);
var model = t.Assembly.GetTypes().Where(x=>x.GetInterface("ICrud") != null );
#>
<# foreach (var type in model) { #>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;
using DataLayer.Data;
using AutoMapper;
namespace Models
{
public partial class <#= type.Name #>
{
<# foreach (MethodInfo prop in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly).Where(m => !m.IsSpecialName && !IsAsyncMethod(m))) { #>
<#= BuildMembers(prop)#>
<#
}
#>
<# foreach (MethodInfo prop in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly).Where(m => !m.IsSpecialName && IsAsyncMethod(m))) { #>
<#= BuildMembers(prop)#>
<#
}
#>
<#String propName(string n)
{
if (!n.Contains("_")) return n;
StringBuilder sb = new StringBuilder();
foreach (string x in n.Split('_')) sb.Append( $"{(x[0]).ToString().ToUpper()}{x.Substring(1)}");
return sb.ToString() ;
} #>
<# foreach (var property in type.GetProperties()) { #>
<# string _propName = propName(property.Name); #>
<#= BuildAttribute(property)#>public <#= property.PropertyType.Name.Contains("Nullable") ?Nullable.GetUnderlyingType(property.PropertyType).Name + property.PropertyType.Name.Replace("Nullable`1","?") : property.PropertyType.Name #> <#= _propName #> { get; set; }
<#
}
#>
public static <#= type.Name #> Save(<#= type.Name #> item)
{
var _c = AutoMapper.Mapper.Map<Models.<#= type.Name #>, DataLayer.Data.<#= type.Name #>>(item);
return AutoMapper.Mapper.Map<DataLayer.Data.<#= type.Name #>, Models.<#= type.Name #>>( GenericSave<DataLayer.Data.<#= type.Name #>>.Update(_c));
}
}
}
public class <#= type.Name #>Profile : Profile
{
public <#= type.Name #>Profile()
{
CreateMap<Models.<#= type.Name #>, DataLayer.Data.<#= type.Name #>>();
CreateMap<Models.<#= type.Name #>, DataLayer.Data.<#= type.Name #>>().ReverseMap();
ReplaceMemberName("_", "");
}
}
<# SaveOutPut(type.Name + ".cs");}
#>using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using AutoMapper;
//Please copy me into App_Start and in Global.asax.cs add the following line into [protected void Application_Start()" App_Start.MappingConfiguration.RegisterMaps();
namespace Models
{
public static class MappingConfiguration
{
public static void RegisterMaps()
{
Mapper.Initialize(config =>{
<#
foreach (var e in model ) {
#>
config.CreateMap< <#=e.Name #>, DataLayer.Data.<#=e.Name #>>(MemberList.Source);
<#}#>
});
}
}
}
<#
SaveOutPut("MappingConfiguration.cs");
#>
<#+
public string BuildMembers(MethodInfo prop)
{
if (prop.MemberType == MemberTypes.Method && prop.IsPublic)
{
string Callee = "";
string Caller = "";
foreach (var p in prop.GetParameters())
{
Callee += $"{p.Name},";
Caller += $"{GetFriendlyName(p.ParameterType)} {p.Name},";
}
if (Callee.Length > 0)
{
Callee = $"{Callee.TrimEnd(',')}";
Caller = $"{Caller.TrimEnd(',')}";
}
string access = prop.IsStatic ? "public static" : "public";
var propname = prop.ReturnType.Name.Contains("Task") ? buildTaskName(prop.ReturnType) : GetFriendlyName(prop.ReturnType);
string rtype = prop.ReturnType.Namespace.Contains("System") ? propname : propname;
string modelType = rtype.Replace("DataLayer.Data", "Models");
return $"{access} {modelType } {prop.Name} ({Caller}) => Mapper.Map<{rtype},{modelType}>(DataLayer.Data.{prop.DeclaringType.Name}.{prop.Name}({Callee}));";
}
else return "";
}
private string buildTaskName(Type type)
{
if (!type.IsGenericType) return GetFriendlyName(type);
var gType = type.GenericTypeArguments[0].GetGenericArguments().FirstOrDefault() == null ? type.GenericTypeArguments[0] : type.GenericTypeArguments[0].GetGenericArguments().FirstOrDefault();
if (gType == null) return GetFriendlyName(type);
var innerType = type.Name.Contains("Task") == true ? fName(gType) : GetFriendlyName(type);
string completeName = GetFriendlyName(type.GetGenericArguments()[0]);
int lastLessThan = completeName.LastIndexOf('<');
int greatThan = completeName.IndexOf('>');
string r = completeName;
var t = innerType;
if (lastLessThan > 0)
{
r = completeName.Substring(lastLessThan + 1, greatThan - (1 + lastLessThan));
completeName = completeName.Replace(r, "");
StringBuilder sb = new StringBuilder(completeName);
var SplodedType = sb.Replace($"<", "<" + innerType, lastLessThan, 1);
t = SplodedType.ToString();
}
return type.Name.Replace("`1", $"<{t}>");
}
public string GetFriendlyName(Type type)
{
return (type.IsGenericType) ? type.Name.Split('`')[0] + "<" + string.Join(", ", type.GetGenericArguments().Select(x => GetFriendlyName(x)).ToArray()) + ">" : type.FullName.Contains("System") ?type.Name.Split('.').Last() :type.FullName ;//type.Name.Split('.').Last();
}
private string fName(Type type) => $"{type.Namespace}.{type.Name}";
private string genericsName(Type type) => $"{type.GetGenericArguments()[0].Namespace}.{type.GetGenericArguments()[0].Name}";
private static bool IsAsyncMethod(MethodInfo type )
{
var attrib = (AsyncStateMachineAttribute)type.GetCustomAttribute(typeof(AsyncStateMachineAttribute));
return (attrib != null);
}
public string BuildAttribute(PropertyInfo prop){
var dataTypeAtt = GetDataTypeAttribute(prop);
string MaxLenAttr="";
string RequiredAttr = "";
foreach (var facet in prop.GetCustomAttributes()) //Yank out attributes
{
if ((Type)facet.TypeId == typeof(System.ComponentModel.DataAnnotations.StringLengthAttribute)) //What type of attribute is this?
{
System.ComponentModel.DataAnnotations.StringLengthAttribute _len = (System.ComponentModel.DataAnnotations.StringLengthAttribute)facet; //Cast it to grab Maximum/MinimumLengths etc.
MaxLenAttr = "[StringLength(" + _len.MaximumLength + ")]\n ";
}
if ((Type)facet.TypeId == typeof(System.ComponentModel.DataAnnotations.RequiredAttribute))RequiredAttr ="[Required(ErrorMessage=\"" + FixName(prop.Name) + " is required\")]\n " ;
}
string DataTypeAttr = (dataTypeAtt != String.Empty) ? dataTypeAtt + "\n " : "";
return RequiredAttr + MaxLenAttr + DataTypeAttr;
}
public void SaveOutPut(string outputFileName) {
string templateDirectory = Path.GetDirectoryName(Host.TemplateFile);
string outputFilePath = Path.Combine(templateDirectory, outputFileName);
File.WriteAllText(outputFilePath, this.GenerationEnvironment.ToString());
this.GenerationEnvironment.Remove(0, this.GenerationEnvironment.Length);
}
public string GetDataTypeAttribute(PropertyInfo property)
{
var propLower = property.Name.ToLower();
if (property.PropertyType == typeof(System.DateTime)) return "[DataType(DataType.DateTime)]";
if (propLower.Contains("phone")) return "[DataType(DataType.PhoneNumber)]";
if (propLower.Contains("html")) return "[DataType(DataType.Html)]";
if (propLower.Contains("email")) return "[DataType(DataType.EmailAddress)]";
if (propLower.Contains("url")) return "[DataType(DataType.Url)]";
return String.Empty;
}
public string FixName(string propName)
{
if (propName.ToLower() != "id" && propName.ToLower().EndsWith("id")) propName = propName.Replace("Id","").Replace("ID","");
return Regex.Replace(propName,"([A-Z])"," $1",RegexOptions.Compiled).Trim();
}
#>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment