1

Im creating c# class during runtime in .NET 4.7.2 by using the TypeBuilder. The problem is that the DLL for the type is stored in the root folder of current application. The problem is that often the user have no write access to the local root folder.

So how do I set the location where the assemblies should be saved and loaded? And what user folder would be fitting for this?

Current code :

private static Type CreateRaportType(List<PropertieInformation> propertieList, string className)
        {
            AssemblyName assemblyName;
            AssemblyBuilder assemblyBuilder;
            ModuleBuilder module;
            TypeBuilder typeBuilder;
            FieldBuilder field;
            PropertyBuilder property;
            MethodAttributes GetSetAttr;
            MethodBuilder currGetPropMthdBldr;
            MethodBuilder currSetPropMthdBldr;
            ILGenerator currGetIL;
            ILGenerator currSetIL;

            Type caType;
            CustomAttributeBuilder caBuilder;

            List<Object> objList = new List<object>();

            assemblyName = new AssemblyName();
            assemblyName.Name = "ReportAssembly";

            assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            module = assemblyBuilder.DefineDynamicModule("ReportModule");

            typeBuilder = module.DefineType(className, TypeAttributes.Public | TypeAttributes.Class, typeof(GeneratedClassBase));

            foreach (PropertieInformation propertieInfo in propertieList)
            {
                field = typeBuilder.DefineField("_" + propertieInfo.PropertieName, propertieInfo.PropertieType, FieldAttributes.Private);

                property = typeBuilder.DefineProperty(propertieInfo.PropertieName, PropertyAttributes.None, propertieInfo.PropertieType, new Type[] { propertieInfo.PropertieType });
                GetSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig;

                currGetPropMthdBldr = typeBuilder.DefineMethod("get_value", GetSetAttr, propertieInfo.PropertieType, Type.EmptyTypes);

                currGetIL = currGetPropMthdBldr.GetILGenerator();
                currGetIL.Emit(OpCodes.Ldarg_0);
                currGetIL.Emit(OpCodes.Ldfld, field);
                currGetIL.Emit(OpCodes.Ret);

                currSetPropMthdBldr = typeBuilder.DefineMethod("set_value", GetSetAttr, null, new Type[] { propertieInfo.PropertieType });

                currSetIL = currSetPropMthdBldr.GetILGenerator();
                currSetIL.Emit(OpCodes.Ldarg_0);
                currSetIL.Emit(OpCodes.Ldarg_1);
                currSetIL.Emit(OpCodes.Stfld, field);
                currSetIL.Emit(OpCodes.Ret);

                // Last, we must map the two methods created above to our PropertyBuilder to
                // their corresponding behaviors, "get" and "set" respectively. 
                property.SetGetMethod(currGetPropMthdBldr);
                property.SetSetMethod(currSetPropMthdBldr);

                caType = typeof(Reportable);
                objList.Clear();
                objList.Add(propertieInfo.MemberToDataBind);
                objList.Add(propertieInfo.ControlToUse);
                objList.Add(propertieInfo.PropertieName);

                if (propertieInfo.ControlToUse == ControlToUse.SystemItemTable)
                {
                    objList.Add(propertieInfo.PropertieInnerCollectionType);
                    objList.Add(propertieInfo.PropertieInnerCollectionName);
                    objList.Add(propertieInfo.DisplayName);
                    objList.Add(-1);
                    objList.Add(FieldListIcon.UnUsedItem);
                    objList.Add(propertieInfo.SystemItemKey);
                    objList.Add(null);
                }
                else if (propertieInfo.ControlToUse == ControlToUse.AttributeTable)
                {
                    objList.Add(null);
                    objList.Add(null);
                    objList.Add(null);
                    objList.Add(propertieInfo.MultiAttributeId);
                    objList.Add(FieldListIcon.UnUsedItem);
                    objList.Add(null);
                    objList.Add(null);
                }
                else if (propertieInfo.ControlToUse == ControlToUse.GUIGroupTable)
                {
                    objList.Add(propertieInfo.PropertieInnerCollectionType);
                    objList.Add(propertieInfo.PropertieInnerCollectionName);
                    objList.Add(propertieInfo.DisplayName);
                    objList.Add(-1);
                    objList.Add(FieldListIcon.UnUsedItem);
                    objList.Add(null);
                    objList.Add(propertieInfo.GuiGroupKey);
                }
                else
                {
                    objList.Add(null);
                    objList.Add(null);
                    objList.Add(null);
                    objList.Add(-1);
                    objList.Add(FieldListIcon.UnUsedItem);
                    objList.Add(null);
                    objList.Add(null);
                }

                var conInfo = caType.GetConstructor(Type.EmptyTypes);
                var conArgs = new object[] { };
                var caTypeFields = caType.GetFields();

                caBuilder = new CustomAttributeBuilder(conInfo, conArgs, caTypeFields, objList.ToArray());

                property.SetCustomAttribute(caBuilder);

                caType = typeof(DisplayNameAttribute);

                if (propertieInfo.IsList)
                    caBuilder = new CustomAttributeBuilder(caType.GetConstructor(new Type[] { typeof(string) }), new string[] { propertieInfo.DisplayName });
                else
                    caBuilder = new CustomAttributeBuilder(caType.GetConstructor(new Type[] { typeof(string) }), new string[] { propertieInfo.DisplayName });

                property.SetCustomAttribute(caBuilder);
            }
            return typeBuilder.CreateType();
        }
Banshee
  • 15,376
  • 38
  • 128
  • 219
  • 1
    TypeBuilder doesn't use arbitrary paths. *The code* specifies the file path where the assembly is stored. Instead of using a relative path provide a full path to an appropriate folder. – Panagiotis Kanavos May 21 '19 at 13:14
  • Applications should never write in `Program Files` anyway, the appropriate location for application data is `ProgramData` whose location can be retrieved either through environment variables or through [Environment.GetFolderPath](https://learn.microsoft.com/en-us/dotnet/api/system.environment.getfolderpath?view=netframework-4.8). Check [this question](https://stackoverflow.com/questions/27918865/how-to-get-the-application-specific-data-folder-programdata) for an example – Panagiotis Kanavos May 21 '19 at 13:15

1 Answers1

0

For saving dynamic assembly to custom folder your can use overloaded DefineDynamicAssembly method of AppDomain

AppDomain currDom = AppDomain.CurrentDomain;
AssemblyName aName = new AssemblyName();
aName.Name = "YouAssemblyName";
string  moduleName = aName.Name + ".dll";
AssemblyBuilder ab = currDom.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Save, @"FOLDER\TO\YOUR\ASSEMBLY");
ModuleBuilder mb = ab.DefineDynamicModule(aName.Name, moduleName);
TypeBuilder tb = mb.DefineType("Example");
tb.CreateType();
// type definition here
ab.Save(moduleName);

For automatic assembly loading from custom place your can use TypeResolve event of AppDomain

AppDomain.CurrentDomain.TypeResolve += CurrentDomain_TypeResolve;

Assembly CurrentDomain_TypeResolve(object sender, ResolveEventArgs args) 
{
        return Assembly.LoadFrom(/*path to your saved assembly*/); 
}

Good folder for this purpose is: driveletter:\Users\\AppData\Local\YourFolder. Use GetFolderPath method to get its path:

Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); 

p.s. I slightly tweaked example from this doc: https://learn.microsoft.com/en-us/dotnet/api/system.appdomain.typeresolve?view=netframework-4.8

UPDATED:

Initial question was about how to save/load dynamic assembly to/from custom location. But if it is necessary to define dynamic module and types in memory in one AppDomain and you don't need to save dynamic assembly, then it is enough to call AssemblyBuilder.DefineDynamicAssembly with AssemblyBuilderAccess.Run and then define transient dynamic module using AssemblyBuilder.DefineDynamicModule(string) method. Finally you should call AssemblyBuilder.GetType(string, bool) to get your type.

The code will be like this

AppDomain currDom = AppDomain.CurrentDomain;
AssemblyName aName = new AssemblyName();
aName.Name = "YourAssemblyName";
AssemblyBuilder assemblyBuilder = currDom.DefineDynamicAssembly(aName, AssemblyBuilderAccess.Run); // We need Run access, don't need Save access
ModuleBuilder mb = assemblyBuilder.DefineDynamicModule(aName.Name); // Here we define transient dynamic module in assembly

TypeBuilder tb = mb.DefineType("YourTypeName");
tb.CreateType();       

// Define your type definition here
// ...

Type t = assemblyBuilder.GetType("YourTypeName", true); // Load created type using our AssemblyBuilder instance
Console.WriteLine("Loaded type \"{0}\".", t);
Object o = Activator.CreateInstance(t); // Create object of loaded type
Console.WriteLine("Created object \"{0}\".", o.ToString());
SergeyIL
  • 575
  • 5
  • 11
  • Mixing this up with my code throws persistable and transient exceptions. In this case I want to avoid saving the assembly to disc but that does not seem possible. But it sounds like I should use transient assembly anyway. – Banshee May 21 '19 at 14:11
  • Do you try to create dynamic assembly in remote AppDomain? Reflection.Emit doesn't allow a dynamic assembly to be emitted directly to a remote application domain. See this link for more infomation: https://msdn.microsoft.com/en-us/data/tt9483fk(v=vs.98) – SergeyIL May 21 '19 at 14:27
  • I want to creat a object from a dynamicly generated class and fill it with data. All this within the same WinForm application. When the application dont use the generated class it can be left to the GC to clean up. Next time the GUI opens a new dynamic class will be generated. Im hoping to do this in memory only and not to disc at all if possible? – Banshee May 21 '19 at 14:46
  • Yes, it's possible. Your initial question was about how to save/load dynamic assembly to/from custom location. Now I updated my answer with example of creation of object of dynamic type in memory using transient module. – SergeyIL May 22 '19 at 07:32