Динамический код в ASP.NET

#c# #asp.net #.net #compilation

#c# #asp.net #.net #Сборник

Вопрос:

В нашем ASP.NET Приложение, у нас есть функция, которая позволяет использовать для написания сценариев на C # или VB.NET код. Эти скрипты хранятся в базе данных и компилируются через определенные промежутки времени, выполняя код, который хранится в этих скриптах.

Это работает до тех пор, пока пользователь пишет basic .ЧИСТЫЙ код. Конечно, наши клиенты теперь требуют, чтобы они могли ссылаться на свои собственные библиотеки DLL, чтобы разрешить выполнение определенного кода. Это приемлемо для нас, и мы создаем решение для этого. Однако существует определенный сценарий, которого мы всегда хотели бы избежать:

Приложению не разрешено копировать файлы DLL, на которые даны ссылки, в папку BIN ASP.NET стороне, так как это перезапускает приложение, и это не поддерживается / не разрешено

Я поиграл с классом CompilerOptions и заметил, что вы можете установить там свои библиотеки, на которые ссылаются. Из информации, которую я смог найти на сайте MSDN:

  • Вы можете задать путь к библиотеке, используя следующее: CompilerOptions = «»/libpath:<путь>»»
  • Вы можете добавить ссылку следующим образом: CompilerOptions .Ссылочные сборки.Добавить(«имя сборки»)
  • Вы можете добавить ссылку следующим образом: CompilerOptions .Ссылочные сборки.Add(«полный путь к сборке»)

В наших сценариях мы также используем следующий механизм; пользователи могут определять область ссылок в своем коде, которая содержит пути к различным пользовательским библиотекам DLL, необходимым для выполнения скрипта. Пример сценария может выглядеть следующим образом:

 #region References
/*
 * C:Program FilesMailBeeMailBee.Net.dll
 * C:Program FilesCustomAppCustom.dll
 * System.IO.dll
/*
#endregion

namespace custom.script.space {
   class CustomScript : Script {
     [EntryPoint]
     public voic run()
     { // do stuff }
   }
}
  

Это будет ссылаться на систему.Сборка ввода-вывода и две указанные пользовательские библиотеки DLL. Однако в текущей реализации мы бы скопировали пользовательские библиотеки DLL в GAC, а затем просто добавили их имя в качестве ссылки на компилятор.

Можно ли отключить копирование библиотеки DLL и использовать полные пути к этим библиотекам DLL для ссылок, не копируя их в папку GAC / bin приложения? И можно ли было бы использовать CompilerOptions для установки libpath и позволить всем ссылкам указывать на это?

Причина, по которой мы не хотим копировать библиотеки DLL и перезапускать приложения, заключается в том, что у нас есть приложения с несколькими экземплярами, несколько клиентов в одном экземпляре, и мы не можем просто перезапустить приложение.

Я надеюсь, что вопрос ясен в отношении того, чего я пытаюсь достичь…

Ответ №1:

Код, который я использую в настоящее время, кажется, работает нормально, и у меня нет указания конкретных сборок. Код, который компилирует скрипт и загружает все динамические ссылки, выглядит следующим образом:

     /// <summary>
    /// Gets the dynamic references.
    /// </summary>
    /// <param name="source">The source.</param>
    /// <param name="assemblyDllPath">The assembly DLL path.</param>
    /// <returns></returns>
    private string[] GetDynamicReferences(string source, string assemblyDllPath)
    {
        var filenames = new List<string>();

        const string startRegion = "#region References";
        const string endRegion = "#endregion";
        const string commentStart = "/*";
        const string commentEnd = "*/";
        const string commentLine = "//";
        const string libpath = "/libpath";
        var sourceReader = new StringReader(source);
        string currentLine;
        bool inReferenceRegion = false;
        bool inReferenceCommentRegion = false;

        // Loop over the lines in the script and check each line individually.
        while ((currentLine = sourceReader.ReadLine()) != null)
        {
            // Strip the current line of all trailing spaces.
            currentLine = currentLine.Trim();

            // Check if we're entering the region 'References'.
            if (currentLine.StartsWith(startRegion))
            {
                inReferenceRegion = true;   // We're entering the region, set the flag.
                continue;                   // Skip to the next line.
            }

            // Check if we're exiting the region 'References'. If so, stop the for loop.
            if (currentLine.StartsWith(endRegion)) break;

            // If we're processing a line that's not in the 'References' region, then skip the line
            // as we're only interested in the lines from that region.
            if (!inReferenceRegion) continue;

            // Check if we're entering the comments section, because the entire region is actually
            // a big comment block, starting with /*
            if (currentLine.StartsWith(commentStart))
            {
                inReferenceCommentRegion = true;    // We're entering the comment block.
                continue;                           // Skip to the next line.
            }

            // Check if we're leaving the comments section, because then we're almost done parsing
            // the entire comment block.
            if (currentLine.EndsWith(commentEnd))
            {
                inReferenceCommentRegion = false;   // Leaving the comment block.
                continue;                           // Skip to the next line.
            }

            // If the line we're processing starts with a comment '//', then skip the line because it's
            // not to be processed anymore by us, just as if it was placed in comment in real code.
            // If the line contains a double slash, strip one of the slashes from it and parse the data.
            if (currentLine.Contains(commentLine))
            {
                if (currentLine.StartsWith(commentLine)) continue;
                currentLine = currentLine.Substring(0, currentLine.IndexOf(commentLine) - 1);
            }

            // If we're dealing with a line that's not inside the reference comment section, skip it
            // because we're only interested in the lines inside the comment region section of the script.
            if (!inReferenceCommentRegion) continue;

            // Trim the current line of all trailing spaces, the line should represent either the fullpath
            // to a DLL, the librarypath option, or the relative path of a DLL.
            string line = currentLine.Trim();

            // If the line starts with the library option, then we need to extract this information, and store it
            // inside the local varialbe that holds the libpath.
            if (line.Equals(libpath))
            {
                string dataHomeFolder = Api2.Factory.CreateApi().Parameters.Read(343).Value;
                string companyName = Api2.Factory.CreateApi().Parameters.Read(113).Value;
                _libraryPath = Path.Combine(dataHomeFolder, companyName, "libraries");
            }

            // If the line is not an absolute path to the referenced DLL, then we need to assume that the DLL resides
            // in the library path. We'll build up the full path using the library path, if the path has been set.
            if (!Path.IsPathRooted(line) amp;amp; !string.IsNullOrEmpty(_libraryPath))
                line = Path.Combine(_libraryPath, line);                

            // If the file exists, then we'll add it as reference to the collection to be used by the compiler.
            // We will not copy the file however in the bin folder of the application.
            var fio = new FileInfo(line);
            if (fio.Exists amp;amp; !filenames.Contains(line)) filenames.Add(line);
        }

        // Return the entire collection of libraries.
        return filenames.ToArray();
    }
  

Это загружает все динамические ссылки, которые я определил внутри блока region, в компилятор. Используя класс компиляции из C#.NET , я могу скомпилировать свой исходный код из скрипта и связать его с внешними библиотеками DLL.

Этот код выполняет компиляцию:

     /// <summary>
    /// <para>This function performs the compile operation and return the compiled assembly.</para>
    /// </summary>
    /// <param name="source">The source code of the script to compile.</param>
    /// <param name="libs">A collection of additional libraries to compile the script.</param>
    /// <returns>The compiled assembly.</returns>
    internal Assembly Compile(string source, List<string> libs)
    {
        var libraries = new List<string>(libs);
        CodeDomProvider codeProvider = new CSharpCodeProvider(new Dictionary<string, string> { { "CompilerVersion", "v4.0" } });
        var compilerParams = new CompilerParameters
                                 {
                                     CompilerOptions = "/target:library /optimize",
                                     GenerateExecutable = false,
                                     GenerateInMemory = true,
                                     IncludeDebugInformation = true,
                                     TreatWarningsAsErrors = false
                                 };
        string assemblyDllPath = Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);

        // Load all the required assemblies depending on the api implementation.
        LoadAssemblies(compilerParams, source, assemblyDllPath, libraries);

        var path = Path.Combine(Path.GetTempPath(), "TF-"   Guid.NewGuid().ToString().ToUpper());

        // replace resx-files from provided libraries with compatible dll's
        var resxs = libraries.FindAll(lb => lb.EndsWith(".resx", StringComparison.OrdinalIgnoreCase));
        var tmpFiles = new List<string>();
        if (resxs.Count > 0)
        {
            if (!Directory.Exists(path)) Directory.CreateDirectory(path);

            foreach (var resx in resxs)
            {
                // Get the resources filename
                var resourceFilename = Path.GetFileNameWithoutExtension(resx);
                var filename = Path.Combine(path, resourceFilename   ".resources");
                File.Delete(filename);
                tmpFiles.Add(filename);

                // Create a ResXResourceReader for the file items.resx.
                Stream stream = File.Open(resx, FileMode.Open, FileAccess.Read, FileShare.Read);
                var rsxr = new ResXResourceReader(stream);

                // Create a ResXResourceReader for the file items.resources.
                IResourceWriter writer = new ResourceWriter(filename);

                // Iterate through the resources and add resources to the resource writer.
                IDictionary dictionary = new Dictionary<string, string>();
                foreach (DictionaryEntry d in rsxr)
                {
                    var k = d.Key.ToString();
                    var v = d.Value.ToString();

                    dictionary.Add(k, v);
                    writer.AddResource(k, v);
                }

                // Close the reader.
                rsxr.Close();
                stream.Close();
                writer.Close();

                compilerParams.EmbeddedResources.Add(filename);

                string[] errors;
                var provider = new CSharpCodeProvider(); // c#-code compiler
                var cu = StronglyTypedResourceBuilder.Create(dictionary, resourceFilename ?? string.Empty, "", provider, false, out errors);

                var options = new CodeGeneratorOptions
                                  {
                                      BracingStyle = "C",
                                      BlankLinesBetweenMembers = false,
                                      IndentString = "t"
                                  };

                var tw = new StringWriter();
                provider.GenerateCodeFromCompileUnit(cu, tw, options);
                var libCode = tw.ToString();
                tw.Close();
                if (!libraries.Contains(libCode))
                    libraries.Add(libCode);
            }
            libraries.RemoveAll(lb => lb.EndsWith(".resx", StringComparison.OrdinalIgnoreCase));
        }

        // actually compile the code
        CompilerResults results = codeProvider.CompileAssemblyFromSource(compilerParams, new List<string>(libraries) { source }.ToArray());

        // remove the temporary files
        foreach (var file in tmpFiles)
            File.Delete(file);

        // remove the resource directory
        if(Directory.Exists(path)) Directory.Delete(path);

        if (results.Errors.HasErrors)
        {
            var sb = new StringBuilder("Compilation error :nt");
            foreach (CompilerError error in results.Errors)
                sb.AppendLine("t"   error.ErrorText);
            throw new Exception(sb.ToString());
        }

        //get a hold of the actual assembly that was generated
        Assembly generatedAssembly = results.CompiledAssembly;

        // move to some app startup place (this only needs to be set once)
        if (!API.Factory.IsAPIImplementationTypeSet)
        { API.Factory.SetAPIImplementation(Assembly.LoadFile(assemblyDllPath   "\TenForce.Execution.API.Implementation.dll").GetType("TenForce.Execution.API.Implementation.API")); }

        // Set the implementation type for the API2 as well. This should only be set once.
        if (!Api2.Factory.ImplementationSet)
        { Api2.Factory.SetImplementation(Assembly.LoadFile(assemblyDllPath   "\TenForce.Execution.Api2.Implementation.dll").GetType("TenForce.Execution.Api2.Implementation.Api")); }

        return generatedAssembly;
    }
  

Ответ №2:

Я думаю, вам нужно прослушать AppDomain.Событие AssemblyResolve, а затем перейдите оттуда, загружая сборку самостоятельно. Дополнительная информация по адресу — http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx

и

http://support.microsoft.com/kb/837908

Комментарии:

1. Полезная информация, но не то, что я ищу, или, по крайней мере, не то, что мне было нужно. По-видимому, достаточно указать полный путь в моем движке компиляции, и он будет загружен. И я думаю, что это потому, что я загружаюсь в новый ApplicationDomain и компилирую DLL в памяти, поэтому мне это не требуется.

Ответ №3:

Фил Хаак написал в блоге о некоторых не очень известных хитростях расширения в ASP.NET 4.0. Одним из них является событие, запускаемое на самом раннем этапе жизненного цикла приложения, где вы можете зарегистрировать поставщиков сборки и добавить ссылки на сборки. Возможно, вы можете использовать это для динамического добавления ссылок, но все равно приложение необходимо перезапустить. Вот сообщение в блоге с дополнительной информацией:

http://haacked.com/archive/2010/05/16/three-hidden-extensibility-gems-in-asp-net-4.aspx