Динамический код в ASP.NET - PullRequest
4 голосов
/ 03 ноября 2011

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

Это работает, пока пользователь пишет базовый код .NET.Конечно, наши клиенты теперь требуют, чтобы они могли ссылаться на свои собственные библиотеки DLL, чтобы разрешить выполнение определенного кода.Это приемлемо для нас, и мы создаем решение для этого.Однако есть определенный сценарий, который мы хотим всегда избегать:

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

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

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

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

#region References
/*
 * C:\Program Files\MailBee\MailBee.Net.dll
 * C:\Program Files\CustomApp\Custom.dll
 * System.IO.dll
/*
#endregion

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

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

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

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

Надеюсь, вопрос ясен о том, чего я пытаюсь достичь ...

Ответы [ 3 ]

1 голос
/ 03 ноября 2011

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

    /// <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) && !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 && !filenames.Contains(line)) filenames.Add(line);
        }

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

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

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

    /// <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 :\n\t");
            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;
    }
0 голосов
/ 03 ноября 2011

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

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

0 голосов
/ 03 ноября 2011

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

и

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...