Search code examples
c#asp.netroslynasp.net-core

Roslyn - Create MetadataReference from in-memory assembly


Working on an ASP.NET 5 application (Visual Studio 2015 CTP5) and Microsoft.CodeAnalysis.CSharp.

If I try to create a MetadataReference to an assembly that is part of the solution to pass it as a reference to CSharpCompilation.Create, I get a System.ArgumentException, "Empty path name is not legal".

// Throws exception
MetadataReference.CreateFromAssembly(typeof(this).Assembly);

// Doesn't throw exception
MetadataReference.CreateFromAssembly(typeof(Object).Assembly);

If I inspect the Location property of the assembly it is empty. I'm assuming this is related to the new way of compiling applications in-memory in ASP.NET 5, so that the assembly is not stored on the disc.

So is there a way to pass a reference to Roslyn for an Assembly with no Location property or is this currently unsupported?

EDIT: @JaredPar - @SLaks has highlighted exactly where it fails but here is the full stack trace for info. I'm creating several other MetadataReferences from System.* assemblies before this and there is no problem with any of them.

System.ArgumentException
Empty path name is not legal.
C:\Development\Incubator\net.framework\src\Webfuel.Services.Host\ScriptHelper\ScriptHelper.cs
Line 86:  
Line 87:              // Compile the code
Line 88:              var compilation = CSharpCompilation.Create(
Line 89:                  assemblyName,
Line 90:                  options: new CSharpCompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary),
 at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share) 
at System.IO.File.OpenRead(String path) 
at Microsoft.CodeAnalysis.InternalUtilities.FileStreamLightUp.OpenFileStream(String path) 
at Microsoft.CodeAnalysis.MetadataReference.CreateFromAssembly(Assembly assembly, MetadataReferenceProperties properties, DocumentationProvider documentation) 
at Microsoft.CodeAnalysis.MetadataReference.CreateFromAssembly(Assembly assembly) 
at Webfuel.Services.Host.ScriptHelper.CompileScriptImpl(String source) in C:\Development\Incubator\net.framework\src\Webfuel.Services.Host\ScriptHelper\ScriptHelper.cs:line 88 
at Webfuel.Services.Host.ScriptHelper.<>c__DisplayClass0.<CompileTemplate>b__3(String source) in C:\Development\Incubator\net.framework\src\Webfuel.Services.Host\ScriptHelper\ScriptHelper.cs:line 71 
at System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue>.GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) 
at Webfuel.Services.Host.ScriptHelper.CompileTemplate(String template) in C:\Development\Incubator\net.framework\src\Webfuel.Services.Host\ScriptHelper\ScriptHelper.cs:line 69 
at Webfuel.Services.Host.SandboxContext.<ExecuteTemplateAsync>d__1.MoveNext() in C:\Development\Incubator\net.framework\src\Webfuel.Services.Host\SandboxContext.cs:line 176 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult() 
at Webfuel.Services.Host.SandboxHost.<ExecuteTemplateAsync>d__1.MoveNext() in C:\Development\Incubator\net.framework\src\Webfuel.Services.Host\SandboxHost.cs:line 39 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult() 
at Webfuel.Services.Sandbox.SandboxService.<ExecuteTemplateAsync>d__1.MoveNext() in C:\Development\Incubator\net.framework\src\Webfuel.Services.Sandbox\SandboxService.cs:line 47 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult() 
at Webfuel.Services.Server.ServerService.<ProcessContentRequestAsync>d__1.MoveNext() in C:\Development\Incubator\net.framework\src\Webfuel.Services.Server\ServerService.cs:line 179 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult() 
at Webfuel.Services.Server.ServerService.<ProcessRequestAsync>d__1.MoveNext() in C:\Development\Incubator\net.framework\src\Webfuel.Services.Server\ServerService.cs:line 73 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter<TResult>.GetResult() 
at Webfuel.App.ServerMiddleware.<Invoke>d__1.MoveNext() in C:\Development\Incubator\net.framework\src\Webfuel.App\Startup.cs:line 89 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.RequestContainer.ContainerMiddleware.<Invoke>d__1.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Loader.IIS.KlrHttpApplication.<ProcessRequestAsyncImpl>d__1.MoveNext() 
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) 
at System.Runtime.CompilerServices.TaskAwaiter.GetResult() 
at Microsoft.AspNet.Loader.IIS.HttpApplicationBase.<InvokeProcessRequestAsyncImpl>d__1.MoveNext()

Solution

  • It's been a while but I did get an answer this on github Roslyn repository so I'll post it in case someone finds this question:

    ASP.NET 5 has an API for this. You can do what Razor does https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs#L132

    This was back in around Beta1 of Asp.Net 5 so may need tweaking but the principle is still the same - follow the API that Asp.Net itself uses via the IAssemblyLoadContextAccessor which the service injector will provide.

    Thanks to David Fowler

    UPDATE: This answer was for ASP.NET 5 Beta1. The API has changed a lot, and in Core 1.0 instead of using IAssemblyLoadContextAccessor, you can access AssemblyLoadContext from the static member:

    System.Runtime.Loader.AssemblyLoadContext.Default
    

    And you can then call LoadFromStream to load an assembly from a binary image. Here is a very rough sketch of the code I use with some irrelevant bits hacked out:

            // Give the assembly a unique name
            var assemblyName = "Gen" + Guid.NewGuid().ToString().Replace("-", "") + ".dll";
    
            // Build the syntax tree
            var syntaxTree = CSharpSyntaxTree.ParseText(source);
    
            // Compile the code
            var compilation = CSharpCompilation.Create(
                assemblyName,
                options: new CSharpCompilationOptions(outputKind: OutputKind.DynamicallyLinkedLibrary),
                syntaxTrees: new List<SyntaxTree> { syntaxTree },
                references: GetMetadataReferences());
    
            // Emit the image of this assembly 
            byte[] image = null;
            using (var ms = new MemoryStream())
            {
                var emitResult = compilation.Emit(ms);
                if (!emitResult.Success)
                {
                    throw new InvalidOperationException();
                }
                image = ms.ToArray();
            }
    
            Assembly assembly = null;
    
            // NETCORE
            using (var stream = new MemoryStream(image))
                assembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(stream);
    

    This is not supposed to run as is, but just give an idea of the main steps.

    Also the issue with generating metadata references from an in-memory only assembly no longer exists as these no longer exist in Core 1.0, so every Assembly has a Location property. So getting these references is basically the same process as in ASP.net 4:

    MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName(assemblyName)).Location);