Created
January 15, 2018 17:35
-
-
Save SoulFireMage/5e41ac33df26b5d7d029440c50c75d82 to your computer and use it in GitHub Desktop.
T4 Model Generator (Use with Datalayer.dll!)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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