Creating DTOs at Runtime

Tags: LINQ, Reflection, IL

One of the first problems that came up when creating the Linq2Rest project was how to handle LINQ projections (select statements). On the client the resulting anonymous type would be generated because it is part of the compiled code, but the server that has to deserialize and execute the LINQ query has no knowledge of the projected type. Still it has to be able to handle the request.

One way to handle this would obviously be to use the resource type in full and only set the requested values. This becomes very visible during serialization and may cause problem when deserializing the response on the client. Another unsuitable solution is to generate types for all projections, but if your initial type is large, this becomes pointless. A nicer way is to generate a matching DTO (data transfer object) at runtime which can be serialized and returned to the client. It should be simple enough, basically it only needs an automatic property so it can store the value before serialization. It turns out that creating a runtime type is surprisingly easy, and so are automatic properties (once you know how).

The easiest solution is to create a type with public fields. This takes care of most scenarios. But let's go for the full property.

The first step is to create a ModuleBuilder in order to get access to the builder classes:

var moduleBuilder = Thread
	.DefineDynamicAssembly(AssemblyName, AssemblyBuilderAccess.Run)

This gives you an instance of a ModuleBuilder. With this you can create your runtime DTO type, using a TypeBuilder.

var typeBuilder = ModuleBuilder.DefineType(className, TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);

The exact type attributes will be up to you, but you'll probably want to create a public class. Next it is time to define a backing field, a property and getter and setter methods.

var fieldBuilder = typeBuilder.DefineField(fieldName, propertyType, FieldAttributes.Private);

var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, null);

var getAccessor = typeBuilder.DefineMethod(
	"get_" + propertyName,
	MethodAttributes.Final | MethodAttributes.Public,

var getIl = getAccessor.GetILGenerator();
getIl.Emit(OpCodes.Ldfld, fieldBuilder);

var setAccessor = typeBuilder.DefineMethod(
	"set_" + propertyName,
	MethodAttributes.Final | MethodAttributes.Public
	new[] { propertyType });

var setIl = setAccessor.GetILGenerator();
setIl.Emit(OpCodes.Stfld, fieldBuilder);


That's it. The code should be fairly self-explanatory. First the backing field is created using the DefineField method. Next the property is created with the DefineProperty method. The biggest gotcha is that you have to create getter and setter methods (after all, it's not Java). Defining the method is pretty much the reverse process of what you would do to find them using reflection. The tricky bit is creating the method body. But once you have the recipe for how to create the IL that goes into these methods, they are easy to make. Finally the getter and setter methods are attached to the property.

Once the necessary properties have been created, the Type needs to be loaded, which is done by having the TypeBuilder create the type, like so:


Once the type is created it would make sense to store it somewhere where you can retrieve it next time someone requests a matching type.

Linq2Rest will create an instance of the runtime DTO and populate the properties before it is returned to the user. This is where it is useful to have a DTO that uses properties, because it will look like the requested anonymous type from a reflection point of view. This may not be important if the DTO is immediately serialized and sent to the user. It may be useful is the object is returned to the user in some other way where the type is preserved.

Another question is how to handle types that have defined serialization aliases. This causes a mismatch between the publicly exposed property names and the ones actually used to execute the LINQ query. That will be for another post.

Latest Tweets