Roslyn - 从内存中的程序集创建MetadataReference

时间:2015-02-13 15:57:25

标签: c# asp.net roslyn asp.net-core

使用ASP.NET 5应用程序(Visual Studio 2015 CTP5)和Microsoft.CodeAnalysis.CSharp。

如果我尝试创建一个MetadataReference到一个程序集,该程序集是解决方案的一部分,将其作为对CSharpCompilation.Create的引用传递,我得到一个System.ArgumentException,“空路径名称不合法”。

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

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

如果我检查程序集的Location属性,它是空的。我假设这与在ASP.NET 5中编译内存中应用程序的新方法有关,因此程序集不会存储在光盘上。

那么有没有办法将Roslyn的引用传递给没有Location属性的程序集,或者当前是不支持的?

编辑: @JaredPar - @SLaks已经突出显示了它失败的确切位置,但这里是信息的完整堆栈跟踪。我之前在System。*程序集中创建了其他几个MetadataReferences,并且没有任何问题。

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()

3 个答案:

答案 0 :(得分:4)

已经有一段时间了,但我确实在github Roslyn存储库上得到了答案,所以如果有人发现这个问题,我会发布它:

ASP.NET 5有一个API。你可以做Razor做的https://github.com/aspnet/Mvc/blob/dev/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs#L132

这回到了Asp.Net 5的Beta1左右,所以可能需要调整,但原理仍然相同 - 遵循Asp.Net本身通过服务注入器将提供的IAssemblyLoadContextAccessor使用的API。 / p>

感谢David Fowler

更新:这个答案是针对ASP.NET 5 Beta1的。 API发生了很大变化,在Core 1.0中,而不是使用IAssemblyLoadContextAccessor,您可以从静态成员访问AssemblyLoadContext:

System.Runtime.Loader.AssemblyLoadContext.Default

然后,您可以调用LoadFromStream从二进制映像加载程序集。这是我使用的代码的一个非常粗略的草图,其中一些不相关的部分被删除了:

        // 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);

这不应该按原样运行,而只是想一想主要步骤。

此外,仅从内存中的程序集生成元数据引用的问题不再存在,因为Core 1.0中不再存在这些引用,因此每个程序集都有一个Location属性。因此,获取这些引用基本上与ASP.net 4中的过程相同:

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

答案 1 :(得分:0)

你的问题不清楚。

如果您在内存中有已编译程序集的字节,请调用MetadataReference.CreateFromImage()

如果要在同一工作区中添加对Roslyn项目的引用,请致电Compilation.ToMetadataReference()

答案 2 :(得分:0)

要从内存中的程序集创建MetadataReference,请执行以下操作:

var pi = assembly.GetType().GetMethod("GetRawBytes", BindingFlags.Instance | BindingFlags.NonPublic);
byte[] assemblyBytes = (byte[]) pi.Invoke(assembly, null);
MetadataReference.CreateFromImage(assemblyBytes)
  1. 从AppDomain获取已经加载的Assembly。
  2. 通过反射从内存中的组件中获取byte []。
  3. 创建MetadataReference。