4

I am trying to create a small web app that will take POST request with code and variables, compile it on the ASP.NET Core server and print result. I did some research and seems like using Roslyn is the only way. After reading guides I came up with the code provided below and found out that after calling GenerateAssembly() method, responsible for creating the assembly and invoking it, .NET Core Host process locks mylib.dll file, until the server is restarted. I tried deleting the file with File.Delete(), but it raised access denied exception. Is there a way to unlock/delete that DLL? One of my ideas was creating DLLs with GUID as name but I think that's not very good solution.

    public static object GenerateAssembly(string code, string[] param)
    {
        var tree = SyntaxFactory.ParseSyntaxTree(code);
        string fileName = "mylib.dll"; //Guid.NewGuid().ToString() + ".dll";

        // Detect the file location for the library that defines the 
        // object type
        var systemRefLocation = typeof(object).GetTypeInfo().Assembly.Location;

        // Create a reference to the library
        var systemReference = MetadataReference.CreateFromFile(systemRefLocation);

        var compilation = CSharpCompilation.Create(fileName)
            .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
            .AddReferences(systemReference)
            .AddSyntaxTrees(tree);

        path = Path.Combine(Directory.GetCurrentDirectory(), fileName);
        var compilationResult = compilation.Emit(path); // On 2nd call exception is raised on this line
        Assembly asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(path);

        object result = asm.GetType("RoslynCore.Helper").GetMethod("Calculate").
                Invoke(null, new object[] { param });
        return result;
    }
Pankwood
  • 1,799
  • 5
  • 24
  • 43
IAmVisco
  • 373
  • 5
  • 14
  • 1
    Sorry but idea with guid file name is terrible :) You loading assembly to appdomain so file gets locked. So you need to load to separate appdomain and then destroy it or you can try to load assembly to current appdomain in different way like in [this answer](https://stackoverflow.com/a/34077090/754438) – Renatas M. Jul 26 '18 at 14:43
  • Expected as much about guid lol. Much thanks for the link, that really solved it! – IAmVisco Jul 26 '18 at 14:47
  • You should exclusively load the generated assemblies into another appdomain, thats the only way you can release the memory again. Maybe roslyn can emit the assembly directly to a memorystream then you do not even have to write it to disk before loading. – thehennyy Jul 26 '18 at 14:48

2 Answers2

4

Solved it thanks to Reniuz, just changed Assembly asm = AssemblyLoadContext.Default.LoadFromAssemblyPath(path); to Assembly asm = Assembly.Load(System.IO.File.ReadAllBytes(path));

IAmVisco
  • 373
  • 5
  • 14
0

Necromancing.
In the Full .NET Framework, you used to use AppDomains for this.
However, .NET Core doesn't use AppDomains anymore, but in .NET Core 3, you now have LoadContexts.

But... if you just load the assemlby into the default-context, with

System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(ms);

you get:

System.InvalidOperationException: "Cannot unload non-collectible AssemblyLoadContext."

So you have to load the assembly in a different context (akin to AppDomain)

public class CollectibleAssemblyLoadContext : AssemblyLoadContext
{
    public CollectibleAssemblyLoadContext() : base(isCollectible: true)
    { }

    protected override Assembly Load(AssemblyName assemblyName)
    {
        return null;
    }
}

byte[] result = null; // Assembly Emit-result from roslyn
System.Runtime.Loader.AssemblyLoadContext context = new CollectibleAssemblyLoadContext();
System.IO.Stream ms = new System.IO.MemoryStream(result);
System.Reflection.Assembly assembly = context.LoadFromStream(ms);


System.Type programType = assembly.GetType("RsEval");
MyAbstractClass eval = (MyAbstractClass) System.Activator.CreateInstance(programType);
eval.LoadContext = context;
eval.Stream = ms;
// do something here with the dynamically created class "eval"

and then you can say

eval.LoadContext.Unload();
eval.Stream.Dispose();

Bonus if you put that into the IDisposable interface of the abstract class, then you can just use using, if you want to.

Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442