#c_sharp #roslyn
Нужно сделать, чтобы пользователь мог выполнять C#-код на моем сервере (что-то вроде dotnetfiddle.net). Приложение Asp.net Core. Есть такой метод, который вполне нормально работает: public static async Task> ExecuteScriptAsync(string code, IEnumerable references, IEnumerable usings) { var options = ScriptOptions.Default.WithReferences(references).WithImports(usings); return await CSharpScript.RunAsync(code, options); } Проблема метода ExecuteScript в том, что такой код он уже не будет выполнять: class Program { public static void Main() { throw new System.Exception(); } } Как сделать чтобы код выполнялся как в консольном приложении, т.е. метод Main был точкой входа? Update Нашел такой код. Срабатывает, но если в скрипт дописать Console.WriteLine("hi"), то он ломается с ошибкой "Имя Console не существует в текущем контексте". var script = @"using System; public static class Program { public static int Main(string[] args) { var x = 7 * 8; return x; } }"; var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); var refs = new List { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")), MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location) }; // Parse the script to a SyntaxTree var syntaxTree = CSharpSyntaxTree.ParseText(script); var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication); // Compile the SyntaxTree to a CSharpCompilation var compilation = CSharpCompilation.Create("Script", new[] { syntaxTree }, refs, new CSharpCompilationOptions( OutputKind.ConsoleApplication, optimizationLevel: OptimizationLevel.Release, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default)); //CodeDomProvider codeDomProvider = new CodeDomProvider(); using (var outputStream = new MemoryStream()) using (var pdbStream = new MemoryStream()) { // Emit assembly to streams. var result = compilation.Emit(outputStream, pdbStream); if (!result.Success) { return; } // Load the emitted assembly. var assembly = Assembly.Load(outputStream.ToArray(), pdbStream.ToArray()); // Invoke the entry point. var x = assembly.EntryPoint.Invoke(null, null);
Ответы
Ответ 1
Assembly.Load, тем более из массива байт - очень плохая идея, ведь она не дает возможности впоследствии выгрузить сборку из памяти, т.е. при работе в серверном приложении память рано или поздно исчерпается и сервер придется перезапускать. Кроме того, как ограничить права для запускаемого кода, чтобы он не разрушил вам систему? Если вы ориентируетесь под .NET Core, то домены приложений недоступны. Правильнее скомпилировать код в файл и запускать его в новом процессе под пользователем с ограниченными правами и перехватывать его вывод: using System; using System.Collections.Generic; using System.IO; using System.Diagnostics; using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; namespace RoslynTest { class Program { static void RunScript() { var script = @"using System; public static class Program { public static int Main(string[] args) { var x = 7 * 8; Console.WriteLine(x.ToString()); return x; } }"; var assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); var refs = new List{ MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "mscorlib.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Core.dll")), MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")), MetadataReference.CreateFromFile(Assembly.GetEntryAssembly().Location) }; // Parse the script to a SyntaxTree var syntaxTree = CSharpSyntaxTree.ParseText(script); var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication); // Compile the SyntaxTree to a CSharpCompilation var compilation = CSharpCompilation.Create("Script", new[] { syntaxTree }, refs, new CSharpCompilationOptions( OutputKind.ConsoleApplication, optimizationLevel: OptimizationLevel.Release, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default) ); var result = compilation.Emit("script.exe"); if (!result.Success) { throw new ApplicationException("Cannot compile script"); } ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = "script.exe"; psi.UseShellExecute = false; psi.RedirectStandardOutput = true; psi.RedirectStandardInput = true; psi.UserName = "Vasya"; psi.Password = "123"; var process = new Process(); using (process) { process.StartInfo = psi; process.Start(); while (!process.StandardOutput.EndOfStream) { string res = process.StandardOutput.ReadLine(); Console.WriteLine(res); } } } } } Код заточен под .NET Framework / Windows, но думаю, не составит труда переделать под .NET Core, так как все используемые библиотеки есть в .NET Standard. Запуск процессов от имени другого пользователя должен работать в .NET Core на всех ОС, начиная с .NET Core 2.1. Примечание. В .NET Core 3.0 появилась возможность выгрузки сборок из памяти, но это все еще не решает проблему обеспечения безопасности.
Комментариев нет:
Отправить комментарий