Skip to content

Commit

Permalink
Implemented serialization suppport
Browse files Browse the repository at this point in the history
  • Loading branch information
aeinbu committed Dec 25, 2019
1 parent 44d7448 commit c444db6
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 24 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2019 Arjan Einbu
Copyright (c) 2019-2020 Arjan Einbu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
1 change: 1 addition & 0 deletions Materializer.Tests/Materializer.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.0.1" />
<PackageReference Include="NewtonSoft.Json" Version="12.0.3" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
Expand Down
14 changes: 7 additions & 7 deletions Materializer.Tests/Materializer_Create.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Materializer.Tests
{
public class Materializer_Create
{
public Lazy<Materializer> _lazy = new Lazy<Materializer>(() => new Materializer(Guid.NewGuid().ToString()));
public Lazy<Materializer> _lazy = new Lazy<Materializer>(() => new Materializer("Dynamic_Assembly_for_Materializer_Create_Tests", false));

public interface IOne
{
Expand All @@ -33,7 +33,7 @@ public void SimpleInterface()
{
var materializer = _lazy.Value;

var one = materializer.Create<IOne>();
var one = materializer.New<IOne>();
one.Prop1 = 3;

Assert.Equal(3, one.Prop1);
Expand All @@ -44,9 +44,9 @@ public void SimpleInterface_PropertyIsOtherSimpleInterface()
{
var materializer = _lazy.Value;

var one = materializer.Create<IOne>();
var one = materializer.New<IOne>();

var two = materializer.Create<ITwo>();
var two = materializer.New<ITwo>();
two.Prop2 = one;

Assert.Equal(one, two.Prop2);
Expand All @@ -57,7 +57,7 @@ public void InheritingInterface()
{
var materializer = _lazy.Value;

var three = materializer.Create<IThree>();
var three = materializer.New<IThree>();
three.Prop1 = 10;
three.Prop3 = 2.2f;

Expand All @@ -69,9 +69,9 @@ public void InheritingInterface()
public void CombiningInterface()
{
var materializer = _lazy.Value;
var one = materializer.Create<IOne>();
var one = materializer.New<IOne>();

var four = materializer.Create<IFour>();
var four = materializer.New<IFour>();
four.Prop1 = 10;
four.Prop2 = one;

Expand Down
57 changes: 57 additions & 0 deletions Materializer.Tests/Materializer_Newtonsoft.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.IO;
using System.Runtime.Serialization;
using Xunit;
using Materializer;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;

namespace Materializer.Tests
{
public class Materializer_Newtonsoft
{
public Lazy<Materializer> _lazy = new Lazy<Materializer>(() => new Materializer("Dynamic_Assembly_for_Materializer_Newtonsoft_Tests", false));

public interface IOne
{
int Prop1 { get; set; }
}


[Fact]
public void SimpleInterface_NewtonsSoft_JsonSerialize()
{
var materializer = _lazy.Value;

var before = materializer.New<IOne>();
before.Prop1 = 3;
var json = JsonConvert.SerializeObject(before);

var after = (IOne)JsonConvert.DeserializeObject(json, materializer.ConcreteTypeOf<IOne>());

Assert.Equal(3, after.Prop1);
}


[Fact]
public void SimpleInterface_NewtonsSoft_JsonSerialize_AlternativeSample()
{
var materializer = _lazy.Value;
T DeserializeInterface<T>(string json) where T : class
{
return (T)JsonConvert.DeserializeObject(json, materializer.ConcreteTypeOf<T>());
}

var before = materializer.New<IOne>();
before.Prop1 = 3;
var json = JsonConvert.SerializeObject(before);

var after = DeserializeInterface<IOne>(json);

Assert.Equal(3, after.Prop1);
}

}
}
42 changes: 42 additions & 0 deletions Materializer.Tests/Materializer_Serializable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.IO;
using System.Runtime.Serialization;
using Xunit;
using Materializer;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;

namespace Materializer.Tests
{
public class Materializer_Serializable
{
public Lazy<Materializer> _lazy = new Lazy<Materializer>(() => new Materializer("Dynamic_Assembly_for_Materializer_Serializable_Tests", true));

public interface IOne
{
int Prop1 { get; set; }
}


[Fact]
public void SimpleInterface_BinaryFormatter()
{
var materializer = _lazy.Value;

var before = materializer.New<IOne>();
before.Prop1 = 3;

IFormatter formatter = new BinaryFormatter();
using var stream = new MemoryStream();
formatter.Serialize(stream, before);

stream.Seek(0, SeekOrigin.Begin);
var after = (IOne)formatter.Deserialize(stream);

Assert.Equal(3, after.Prop1);
}

}
}
72 changes: 57 additions & 15 deletions Materializer/Materializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,50 @@ namespace Materializer
public class Materializer
{
private readonly ModuleBuilder _moduleBuilder;
private readonly AssemblyBuilder _dynamicAssembly;
private readonly bool _forSerializableTypes;
private readonly Dictionary<Type, Type> _typeCache = new Dictionary<Type, Type>();

public Materializer(string assemblyName)
public Materializer(string assemblyName = "Dynamic_assembly_for_Materializer_created_types", bool forSerializable = false)
{
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
_moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);
_dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
_moduleBuilder = _dynamicAssembly.DefineDynamicModule(assemblyName);
_forSerializableTypes = forSerializable;

if (_forSerializableTypes)
{
var currentAppDomain = AppDomain.CurrentDomain;
currentAppDomain.AssemblyResolve += new ResolveEventHandler(DynamicAssemblyResolvehandler);
}
}


private Assembly DynamicAssemblyResolvehandler(object sender, ResolveEventArgs args)
{
if (args.Name == _dynamicAssembly.FullName)
{
return _dynamicAssembly;
}

return null;
}


public T New<T>() where T : class
{
var t = GetOrCreateType<T>();
var instance = Activator.CreateInstance(t);
return (T)instance;
}

public T Create<T>(bool markAsSerializable = false) where T : class

public Type ConcreteTypeOf<T>() where T : class
{
return GetOrCreateType<T>();
}


private Type GetOrCreateType<T>() where T : class
{
var interfaceOfTypeToCreate = typeof(T);
if (!interfaceOfTypeToCreate.IsInterface)
Expand All @@ -26,30 +61,38 @@ public T Create<T>(bool markAsSerializable = false) where T : class

if (!_typeCache.ContainsKey(interfaceOfTypeToCreate))
{
var createdType = CreateClass<T>(markAsSerializable, interfaceOfTypeToCreate);
var createdType = CreateType<T>(interfaceOfTypeToCreate);
_typeCache.Add(interfaceOfTypeToCreate, createdType);
}

var t = _typeCache[interfaceOfTypeToCreate];
var instance = Activator.CreateInstance(t);
return (T)instance;
return _typeCache[interfaceOfTypeToCreate];
}

private Type CreateClass<T>(bool markAsSerializable, Type interfaceOfTypeToCreate) where T : class

private Type CreateType<T>(Type interfaceOfTypeToCreate) where T : class
{
var typename = interfaceOfTypeToCreate.Name + Guid.NewGuid();
if (_typeCache.ContainsKey(interfaceOfTypeToCreate))
{
return _typeCache[interfaceOfTypeToCreate];
}

var typename = $"{interfaceOfTypeToCreate.Name}_{Guid.NewGuid()}";
var typeBuilder = _moduleBuilder.DefineType(typename, TypeAttributes.Public);

if (markAsSerializable)
if (_forSerializableTypes)
{
typeBuilder.SetCustomAttribute(typeof(SerializableAttribute).GetConstructor(new Type[]{}), null);
var serializableAttributeTypeInfo = typeof(SerializableAttribute);
var serializableAttributeConstructorInfo = serializableAttributeTypeInfo.GetConstructor(new Type[] { });
var serializableAttributeBuilder = new CustomAttributeBuilder(serializableAttributeConstructorInfo, new object[] { });
typeBuilder.SetCustomAttribute(serializableAttributeBuilder);
}

ImplementInterfaceProperties(interfaceOfTypeToCreate, typeBuilder);

return typeBuilder.CreateType();
}


private void ImplementInterfaceProperties(Type interfaceOfTypeToCreate, TypeBuilder typeBuilder)
{
typeBuilder.AddInterfaceImplementation(interfaceOfTypeToCreate);
Expand All @@ -64,21 +107,20 @@ private void ImplementInterfaceProperties(Type interfaceOfTypeToCreate, TypeBuil

var accessorAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.Virtual | MethodAttributes.HideBySig;

//TODO: how to make seperate get_interfacename.propertyname for different interfaces?
var mbGetAccessor = typeBuilder.DefineMethod($"get_{pi.Name}", accessorAttributes, pi.PropertyType, Type.EmptyTypes);
var mbGetIL = mbGetAccessor.GetILGenerator();
mbGetIL.Emit(OpCodes.Ldarg_0);
mbGetIL.Emit(OpCodes.Ldfld, backingFieldBuilder);
mbGetIL.Emit(OpCodes.Ret);

var mbSetAccessor = typeBuilder.DefineMethod($"set_{pi.Name}", accessorAttributes, null, new []{ pi.PropertyType });
var mbSetAccessor = typeBuilder.DefineMethod($"set_{pi.Name}", accessorAttributes, null, new[] { pi.PropertyType });
var mbSetIL = mbSetAccessor.GetILGenerator();
mbSetIL.Emit(OpCodes.Ldarg_0);
mbSetIL.Emit(OpCodes.Ldarg_1);
mbSetIL.Emit(OpCodes.Stfld, backingFieldBuilder);
mbSetIL.Emit(OpCodes.Ret);

var propertyBuilder = typeBuilder.DefineProperty($"{interfaceOfTypeToCreate.Name}.{pi.Name}", PropertyAttributes.HasDefault, pi.PropertyType, null);
var propertyBuilder = typeBuilder.DefineProperty(pi.Name, PropertyAttributes.HasDefault, pi.PropertyType, null);
propertyBuilder.SetGetMethod(mbGetAccessor);
propertyBuilder.SetSetMethod(mbSetAccessor);
}
Expand Down
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,54 @@
# Materializer
Define your DTOs as interfaces
Classes can only be combined by inheriting, while interfaces can be combined.

With `Materializer` you can define your DTOs as interfaces without the need to create classes for each of the combinations you'll want to use.

Given the following interfaces:
```csharp
inferface IOne
{
int First { get; set; }
string Second { get; set; }
}

inferface ITwo
{
DateTime Third { get; set; }
Double Fourth { get; set; }
}

inferface ICombined : IOne, ITwo
{
}
```
Without `Materializer` you would need to write a class for each of the ones you want to use as DTO, producing a lot of overhead code not really worth anything.
With `Materializer` you can instantiate each interface directly.

```csharp
var materializer = new Materializer();

// create an object representing the simple IOne interface
var obj1 = materializer.New<IOne>();
obj1.First = 5;
obj1.Second = "Mary Poppins";

// create an object representing the combination interface, ICombined
var obj2 = materializer.New<ICombined>();
obj2.First = 10;
obj2.Second = "Peter Pan";
obj2.Third = DateTime.Now;
obj2.Fourth = 1.0;
```

## Serializable support

### NewtonSoft.Json
The created objects are serializable with `NewtonSoft.Json` without setting any options. (See the tests for sample code.)

### .NET serialization
To be serializable with .NET serialization, a class must have the `[Serializable]` attribute. You can turn this on with the `forSerializable` option, like this:
```csharp
var materializer = new Materializer(forSerializable: true);
```
When you set the `forSerializable` option on a serializer, all classes made with that serializer will have the `[Serializable]` attribute:

0 comments on commit c444db6

Please sign in to comment.