微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 .net core 中为 Roslyn 动态选择引用

如何解决在 .net core 中为 Roslyn 动态选择引用

TL;DR

如何让运行时在 .NET Core 5 中为涉及 .NET 4.7.2 代码的在运行时编译的 C# 插件选择正确的程序集?

背景

我有一个 .NET 4.7.2 应用程序,在该应用程序上,某些模块根据一些可配置的插件表现不同。我在运行时编译 C# 插件的 .NET 4.7.2 程序集中有以下代码

import csv
from bs4 import BeautifulSoup
from selenium import webdriver
import time
import matplotlib.pyplot as plt,mpld3


driver = webdriver.Chrome()
url = "https://www.tiktok.com/"
driver.get(url)

time.sleep(2)
soup = BeautifulSoup(driver.page_source,"html.parser")
result = soup.find_all('span',{'class' : "lazyload-wrapper"})
len(result)

trending = []
viewss = []
for item in result[0]:
    for j in item:
        type(item)
        #print(item[0])
        tak = item.find_all("a","jsx-2474431524")
        a = range(len(tak))
        #print(a[4])
        for i in range(len(tak)):
            url = "https://www.tiktok.com/"+tak[i].get("href")
            time.sleep(2)
            driver.get(url)
            soup = BeautifulSoup(driver.page_source,"html.parser")
            result = soup.find_all('div',{'class' : "share-info"})
            views = result[0].h2.text
            if 'B' in views:
                lists = result[0].h1.text
                #view = result[0].h2.text
                print(lists)
                print(views)
                trending.append(lists)
                viewss.append(views)
                
                
import matplotlib.pyplot as plt

xval=[]
lent = len(trending)
for i in range(lent):
    xval.append(i)

x = trending
y = viewss

#print(trending[3],)

fig,ax = plt.subplots()
plt.plot(x,y)
plt.xlabel('trends',size = 15)
plt.ylabel('Ranking',size = 15)
#plt.title('',size = 10)

plt.xticks(rotation=-30)
plt.xticks(size = 20)
plt.yticks(size = 20)
fig.savefig('my_plot.png')
plt.show()
#mpld3.show()

我现在正在尝试将代码(缓慢地)升级到 .NET 5.0,我从 UnitTests(没有其他项目可以参考的项目之一)开始。我写了以下代码

    public OperationResult<Assembly> CompileClass(string code,string[] references,string fileName,bool generateInMemory = true,bool includeDebuginformation = true)
    {
        OperationResult<Assembly> result = new OperationResult<Assembly> { Success = true };

        try
        {
            string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
            bool keepSoureceFilesAfterCompiling = false;
#if (DEBUG)
            keepSoureceFilesAfterCompiling = true;
#endif

            if (!Directory.Exists(pluginsFolder)) 
            {
                Directory.CreateDirectory(pluginsFolder); 
            }

            using (CSharpCodeProvider compiler = new CSharpCodeProvider(new Dictionary<string,string> { { "CompilerVersion","v4.0" } }))
            {
                CompilerParameters parameters = new CompilerParameters()
                {
                    GenerateInMemory = generateInMemory,IncludeDebuginformation = includeDebuginformation,OutputAssembly = Path.Combine(pluginsFolder,fileName) + ".dll",CompilerOptions = "/debug:full",TempFiles = new TempFileCollection { KeepFiles = keepSoureceFilesAfterCompiling }
                };
                parameters.ReferencedAssemblies.AddRange(references);
                CompilerResults compiledCode = compiler.CompileAssemblyFromSource(parameters,code);
                var errors = new StringBuilder();

                foreach (CompilerError error in compiledCode.Errors)
                {
                    errors.AppendLine($"Error in line {error.Line},Column {error.Column}: {error.ErrorText}");
                }

                if (!string.IsNullOrEmpty(errors.ToString()))
                {
                    result.HandleFailure(errors.ToString());
                }

                result.ResultObject = compiledCode.CompiledAssembly;
            }
        }
        catch (Exception ex)
        {
            LogService.Current.LogError(ex);
        }

        return result;
    }

在旧代码中,在

public OperationResult<Assembly> CompileClassWithRoslyn(string code,List<string> referenceAssemblies,string assemblyName)
{
        OperationResult<Assembly> result = new OperationResult<Assembly>();

        try
        {
            //Set file name,location and referenced assemblies
            string pluginsFolder = Path.Combine(InsproConfiguration.GetSettings().PluginsFolder);
            SyntaxTree SyntaxTree = CSharpSyntaxTree.ParseText(code);
            var trustedAssembliesPaths = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")).Split(Path.PathSeparator);

            if (!referenceAssemblies.Any(a => a.Contains("mscorlib"))) 
            {
                 referenceAssemblies.Add("mscorlib.dll"); 
            }

            var references = trustedAssembliesPaths.Where(p => referenceAssemblies.Contains(Path.GetFileName(p)))
                                                        .Select(p => MetadataReference.CreateFromFile(p))
                                                        .ToList();

            CSharpCompilation compilation = CSharpCompilation.Create(
                assemblyName,SyntaxTrees: new[] { SyntaxTree },references: references,options: new CSharpCompilationoptions(OutputKind.DynamicallyLinkedLibrary));

            using (var ms = new MemoryStream())
            {
                EmitResult emitResult = compilation.Emit(ms);

                if (!emitResult.Success)
                {
                    IEnumerable<Diagnostic> failures = emitResult.Diagnostics.Where(diagnostic =>
                        diagnostic.IsWarningAsError ||
                        diagnostic.Severity == DiagnosticSeverity.Error);
                    result.HandleFailure(failures.Select(f => f.GetMessage()));
                }
                else
                {
                    ms.Seek(0,SeekOrigin.Begin);
                    Assembly assembly = Assembly.Load(ms.ToArray());                     
                }
            }
        }
        catch (Exception ex)
        {
            return result.HandleFailure(ex);
        }

        return result;
    }

程序集由运行时自动名称选择。在新代码中,mscorlib 无法正确解析,因为出现错误

错误 CS0518:未定义或导入预定义类型“System.Object”

解决方法

使用 Roslyn 针对 .net5 进行编译时,挑战与针对旧的 .net 框架进行编译有很大不同,因为您必须引用引用程序集而不是实现程序集。许多提示会让您引用System.Private.CoreLib.dll,即一个实现程序集,从而将您引向糟糕的方向。例如MetadataReference.CreateFromFile(typeof(object).Assembly.Location)

下面的代码引用了 .net 5 的所有(VB 除外)引用程序集

foreach (string dll in Api.GetFiles(@"C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\5.0.0\ref\net5.0","*.dll"))
{
    if (!dll.Contains("VisualBasic"))
        references.Add(MetadataReference.CreateFromFile(dll));
}

如果您使用 Windows 窗体兼容包 (net5.0-windows),请添加以下程序集:

foreach (string dll in Api.GetFiles(@"C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\5.0.0\ref\net5.0\","*.dll"))
{
    if (!dll.Contains("VisualBasic") && !dll.Contains("PresentationFramework") && !dll.Contains("ReachFramework"))
        references.Add(MetadataReference.CreateFromFile(dll));
}

通过这些参考

  1. 编译没有错误
  2. 生成的程序集可以在其他项目中使用而不会抱怨缺少引用(例如 System.Private.CoreLib.dll)

框架的所有程序集?窥探生成的代码时,您将看到仅引用了所需的程序集。

如果编译必须在上述目录不存在的机器上运行,可能的解决方案是:

  • 将所有这些参考程序集嵌入为嵌入式资源
  • 使用 Assembly.GetExecutingAssembly().GetManifestResourceStream 将这些嵌入资源读取为 stream
  • 用这些流填充 byte[]
  • 使用 references.Add(MetadataReference.CreateFromImage(BytesFromResource(dll))); 添加引用

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。