Roslyn - Create MetadataReference from in-memory assembly
Asked Answered
V

4

14

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()
Vegetal answered 13/2, 2015 at 15:57 Comment(2)
Can you add the full stack trace of the error? That will help out in understanding why this failed.Fafnir
@JaredPar: source.roslyn.io/#Microsoft.CodeAnalysis/MetadataReference/…Crater
V
6

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);
Vegetal answered 16/10, 2015 at 16:45 Comment(0)
C
0

Your question is unclear.

If you have the bytes of a compiled assembly in memory, call MetadataReference.CreateFromImage().

If you want to add a reference to a Roslyn project in the same Workspace, call Compilation.ToMetadataReference().

Crater answered 13/2, 2015 at 16:57 Comment(6)
I don't have the bytes. I just have a reference to the assembly (i.e. a reference to a System.Reflection.Assembly object). But the assembly has no Location property (it. is empty).Vegetal
What kind of assembly is it? What does it come from?Crater
It's in an ASP.NET 5 application (the new project system in VS2015). I just get it via typeof(<mytype>).Assembly. My thinking is that because the new build system doesn't write to disc this is what is breaking it. I've tried setting the 'Produce outputs on build' flag on the project settings but that didn't help either.Vegetal
Ah! Compilation.ToMetadataReference() looks interesting. But I didn't compile this assembly, it was compiled by the K runtime. Do you know if there is any way to get at a compilation from an Assembly reference?Vegetal
Hmm; I'm not sure if this is possible at all. Roslyn needs to read the assembly's raw bytes, and I don't see any way to get that from an in-memory assembly.Crater
I think that may be the case unfortunately. Still it may work if I build to a local folder and pre-build the dependencies. So no more CTRL-F5 real time changes for me... hopefully support will be added in a future release.Vegetal
C
0

To create MetadataReference from in-memory assembly:

var pi = assembly.GetType().GetMethod("GetRawBytes", BindingFlags.Instance | BindingFlags.NonPublic);
byte[] assemblyBytes = (byte[]) pi.Invoke(assembly, null);
MetadataReference.CreateFromImage(assemblyBytes)
  1. Get the already loaded Assembly from the AppDomain.
  2. Get the byte[] from that in-memory assemby using reflection.
  3. Create MetadataReference.
Capacious answered 25/1, 2019 at 19:51 Comment(1)
GetRawBytes no longer exists in newer .net versions github.com/dotnet/runtime/issues/28132#issue-558436811Unitive
R
0

On modern .Net, you can use code from this GitHub comment:

if (assembly.TryGetRawMetadata(out byte* blob, out int length))
{
    var moduleMetadata = ModuleMetadata.CreateFromMetadata((nint)blob, length);
    var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
    return assemblyMetadata.GetReference();
}
Rademacher answered 21/6, 2024 at 12:36 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.