ASP.NET中的动态代码

本文关键字:动态 代码 NET ASP | 更新日期: 2023-09-27 18:13:06

NET应用程序,我们有一个功能,允许使用c#或VB编写脚本。NET代码。这些脚本存储在数据库中,并按指定的时间间隔编译,执行存储在这些脚本中的代码。

只要用户正在编写基本的。net代码,这就可以工作。当然,我们的客户现在要求他们能够引用自己的dll来执行特定的代码。这对我们来说是可以接受的,我们正在为此创造一个解决方案。但是,有一种特定的场景我们希望始终避免:

应用程序不允许将引用的DLL文件复制到ASP的BIN文件夹中。

我一直在摆弄CompilerOptions类,我注意到你可以在那里设置你的引用库。从我在MSDN网站上找到的信息:

  • 您可以使用以下命令设置库路径: CompilerOptions ="/libpath: & lt; path>"
  • 你可以像这样添加一个引用:CompilerOptions.ReferencedAssemblies。添加("程序集名称")
  • 添加如下引用:CompilerOptions.ReferencedAssemblies。添加("程序集的完整路径")

在我们的脚本中,我们也有以下机制:用户可以在代码中定义References区域,其中包含执行脚本所需的各种自定义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 }
   }
}

这将引用系统。IO汇编和指定的两个自定义dll。然而,在当前的实现中,我们将自定义dll复制到GAC中,然后将它们的名称添加到编译器中作为引用。

是否有可能禁用DLL的副本并使用完整路径来引用这些DLL,而不将它们复制到应用程序的GAC/bin文件夹?是否有可能使用CompilerOptions来设置libpath并让所有引用指向此?

我们不想复制dll并重新启动应用程序的原因是因为我们有多实例应用程序,单个实例上有多个客户,我们不能简单地重新启动应用程序。

ASP.NET中的动态代码

我目前使用的代码似乎工作得很好,没有我指定特定的程序集。编译脚本并加载所有动态引用的代码如下:

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

这将加载我在区域块中定义的所有动态引用到编译器中。使用c#中的Compile类。. 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 :'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;
    }

我觉得你需要听听AppDomain。AssemblyResolve事件,然后从那里自己加载程序集。更多信息请访问- http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx

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

Phil Haack写了一篇关于ASP中一些不太为人所知的可扩展性钩子的博文。NET 4.0。其中之一是一个事件,在应用生命周期的早期触发,在那里你可以注册构建提供者和添加程序集引用。也许您可以使用它来动态添加引用,但仍然需要重新启动应用程序。这里是更多信息的博客文章:

http://haacked.com/archive/2010/05/16/three - -扩展-宝石藏在asp -网- 4. - aspx