简体   繁体   English

ASP.NET中的动态代码

[英]Dynamic code in ASP.NET

In our ASP.NET Application, we have a feature that allows the use to write scripts in C# or VB.NET code. 在我们的ASP.NET应用程序中,我们有一个功能,允许用C#或VB.NET代码编写脚本。 These scripts are stored in the database and compiled at specified intervals, executing the code that is stored in these scripts. 这些脚本存储在数据库中,并按指定的时间间隔进行编译,执行存储在这些脚本中的代码。

This works as long as the user is writing basic .NET code. 只要用户编写基本的.NET代码,这就可以工作。 Ofcourse our customers now request that they're able to reference their own DLLs to allow the execution of specific code. 当然,我们的客户现在要求他们能够引用自己的DLL以允许执行特定代码。 This is acceptable for us and we're creating a solution for this. 这对我们来说是可以接受的,我们正在为此创建一个解决方案。 However there is a specific scenario that we wish to avoid at all times: 但是,我们希望在任何时候都避免使用特定方案:

The application is not allowed to copy the referenced DLL files into the BIN folder of the ASP.NET side, as this restarts the application and this is not supported/allowed 不允许应用程序将引用的DLL文件复制到ASP.NET端的BIN文件夹中,因为这会重新启动应用程序并且不支持/允许

I've been playing around with the CompilerOptions class and I've noticed that you're able to set your referenced libraries there. 我一直在玩CompilerOptions类,我注意到你可以在那里设置引用的库。 From the information I could find on the MSDN site: 从我在MSDN网站上找到的信息:

  • You can set a library path using the following: CompilerOptions = ""/libpath:<path>"" 您可以使用以下命令设置库路径 :CompilerOptions =“”/ libpath:<path>“”
  • You can add a reference like so: CompilerOptions.ReferencedAssemblies.Add("assembly name") 您可以像这样添加引用:CompilerOptions.ReferencedAssemblies.Add(“程序集名称”)
  • You can add a reference like so: CompilerOptions.ReferencedAssemblies.Add("full path to assembly") 你可以像这样添加一个引用:CompilerOptions.ReferencedAssemblies.Add(“程序集的完整路径”)

In our scripts we also have the following mechanism in place; 在我们的脚本中,我们还有以下机制; users can define a References region in their code that contains paths to various custom DLLs needed to execute the script. 用户可以在其代码中定义一个References区域,该区域包含执行脚本所需的各种自定义DLL的路径。 An Example script could look like this: 示例脚本可能如下所示:

#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 }
   }
}

This would reference the System.IO assembly and the two custom DLLs specified. 这将引用System.IO程序集和指定的两个自定义DLL。 However with the current implementation, we would copy the custom DLLs to the GAC and then just add their name as reference to the compiler. 但是,对于当前的实现,我们将自定义DLL复制到GAC,然后只添加它们的名称作为编译器的引用。

Would it be possible to disable the copy of the DLL and use full paths to these dlls to be referenced, without having them copied to the GAC/bin folder of the application? 是否可以禁用DLL的副本并使用这些dll的完整路径进行引用,而无需将它们复制到应用程序的GAC / bin文件夹中? And would it be possible to use the CompilerOptions to set the libpath and let all references point to this? 是否可以使用CompilerOptions来设置libpath并让所有引用都指向这个?

The reason why we don't want to copy the Dlls and restart the applications, is because we have multi-instance applications, several customers on a single instance, and we can't simply restart an application. 我们之所以不想复制Dll并重新启动应用程序,是因为我们有多实例应用程序,单个实例上有多个客户,而且我们不能简单地重启应用程序。

I hope the question is clear on what I'm trying to achieve... 我希望问题很清楚我正在努力实现的目标......

The code I'm currently using seems to be working fine without me having the specify specific assemblies. 我正在使用的代码似乎工作正常,没有我指定特定的程序集。 The code that compiles the script and loads all dynamic references looks like this: 编译脚本并加载所有动态引用的代码如下所示:

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

This loads all the dynamic references that I have defined inside the region block into the compiler. 这会将我在区域块中定义的所有动态引用加载到编译器中。 Using the Compile class from C#.NET, I'm able to compile my source code from the script and link to external DLLS. 使用C#.NET中的Compile类,我可以从脚本编译我的源代码并链接到外部DLLS。

This code performs the compilation: 此代码执行编译:

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

I think you need to listen to the AppDomain.AssemblyResolve event and then go from there loading the assembly yourselves. 我认为您需要收听AppDomain.AssemblyResolve事件,然后从那里开始加载程序集。 More information is at - http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx 有关更多信息,请访问http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx

and

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

Phil Haack blogged about some not very well known extensibility hooks in ASP.NET 4.0. Phil Haack在博客中发表了关于ASP.NET 4.0中一些不太知名的可扩展性钩子的博文。 One of them is an event, fired very early in the app lifecycle, where you can register build providers and add assembly references. 其中一个是在应用程序生命周期中很早就启动的事件,您可以在其中注册构建提供程序并添加程序集引用。 Maybe you can use that to dynamically add references, but still the application needs to be restarted. 也许您可以使用它来动态添加引用,但仍然需要重新启动应用程序。 Here's the blog post with more information: 这是包含更多信息的博客文章:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM