Creating .proto definitions from existing types at runtime

There was a need to create .proto definition files from the definitions of a reverse engineered database first project. The approach taken was that of using System.Emit to generate the type definitions and feed those to protobuf-net and use its ability to generate the .proto files.

There are only three classes needed:

  • ContextFinder
  • ClassGenerator
  • Program

The ContextFinder is pretty straight forward. It uses reflection to get all the generic parameters of DbSet<> properties within a DbContext. Then, ClassGenerator is used to copy the properties of the Types we harvested into a new type with the addition of adding ProtoContract and ProtoMember appropriately. Then, the Program class just loads the assembly from the file specified and runs the previously two mentioned classes.

public class ClassGenerator
{
private readonly ModuleBuilder _moduleBuilder;
public ClassGenerator()
{
var an = new AssemblyName("DynamicProtoAssembly");
AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(an,AssemblyBuilderAccess.Run);
_moduleBuilder = assemblyBuilder.DefineDynamicModule("DynamicProtoModule");
}
public Type CreateType(Type typeToCopy)
{
TypeBuilder tb = _moduleBuilder.DefineType(typeToCopy.Name + "Proto",
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
ConstructorBuilder constructor = tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
var ci = typeof(ProtoContractAttribute).GetConstructor(new Type[0]);
var builder = new CustomAttributeBuilder(ci,new object[0]);
tb.SetCustomAttribute(builder);
var propertiesToCopy = typeToCopy.GetProperties();
for (int i = 0; i < propertiesToCopy.Length; i++)
{
var propertyInfo = propertiesToCopy[i];
CreateProperty(tb,propertyInfo.Name,propertyInfo.PropertyType,i);
}
return tb.CreateType();
}
private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType, int i)
{
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
ILGenerator getIl = getPropMthdBldr.GetILGenerator();
getIl.Emit(OpCodes.Ldarg_0);
getIl.Emit(OpCodes.Ldfld, fieldBuilder);
getIl.Emit(OpCodes.Ret);
MethodBuilder setPropMthdBldr =
tb.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldarg_1);
setIl.Emit(OpCodes.Stfld, fieldBuilder);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
var ci = typeof(ProtoMemberAttribute).GetConstructor(new [] { typeof(int) });
var builder = new CustomAttributeBuilder(ci, new object[] { i + 1 });
propertyBuilder.SetCustomAttribute(builder);
}
}

view raw
ClassGenerator.cs
hosted with ❤ by GitHub

public class ContextFinder
{
public IEnumerable<Type> GetAllTypesInContextDbSets(Assembly assembly)
{
return GetContextTypes(assembly)
.Select(x => GetDataSetTypes(x))
.SelectMany(x => x)
.Select(x => x.GetGenericArguments()[0]);
}
private IEnumerable<Type> GetContextTypes(Assembly assembly)
{
return assembly.GetTypes()
.Where(myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf(typeof(DbContext)));
}
private IEnumerable<Type> GetDataSetTypes(Type context)
{
return context.GetProperties()
.Select(x => x.PropertyType)
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(DbSet<>));
}
}

view raw
ContextFinder.cs
hosted with ❤ by GitHub

class Program
{
static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("The first argument should be a path to the assembly");
return;
}
Assembly assembly = Assembly.LoadFile(args[0]);
ContextFinder finder = new ContextFinder();
var types = finder.GetAllTypesInContextDbSets(assembly);
ClassGenerator generator = new ClassGenerator();
var protoTypes = types.Select(x => generator.CreateType(x));
foreach (var protoType in protoTypes)
{
Console.WriteLine(GenerateProtoFile(protoType));
}
}
private static string GenerateProtoFile(Type protoType)
{
MethodInfo methodInfo = typeof(Serializer).GetMethod(nameof(Serializer.GetProto),new [] {typeof(ProtoSyntax)});
MethodInfo genericMethod = methodInfo.MakeGenericMethod(protoType);
return (string) genericMethod.Invoke(null, new object[] { ProtoSyntax.Proto3 });
}
}

view raw
Program.cs
hosted with ❤ by GitHub

Leave a Reply