Dynamic code in ASP.NET

In our ASP.NET application, we have a function that allows you to use scripts in C # or VB.NET code. These scripts are stored in a database and compiled at specific intervals, executing the code that is stored in these scripts.

This works as long as the user writes the basic .NET code. Of course, our customers now request that they can reference their own DLLs to allow the execution of certain code. This is acceptable for us, and we are creating a solution for this. However, there is a specific scenario that we want to avoid at any time:

The application is not allowed to copy the associated DLL files to the BIN folder on the ASP.NET side, as this restarts the application and it is not supported / allowed

I played with the CompilerOptions class, and I noticed that you can install your libraries that you reference. From the information I could find on MSDN:

  • You can set the library path using the following: CompilerOptions = "/ libpath: <path>" "
  • You can add the link like this: CompilerOptions.ReferencedAssemblies.Add ("assembly name")
  • You can add the link like this: CompilerOptions.ReferencedAssemblies.Add ("full assembly path")

In our scenarios, we also have the following mechanism; users can define the link area in their code, which contains the paths to the various user dlls needed to execute the script. An example script might 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 will link to the System.IO assembly and the two custom dlls specified. However, with the current implementation, we copied custom DLLs to the GAC, and then simply added their name as a reference to the compiler.

Is it possible to disable a copy of a DLL and use the full paths to these DLLs that can be referenced without copying them to the application’s GAC / bin folder? And is it possible to use CompilerOptions to install libpath, and let all links point to this?

The reason we don’t want to copy the Dll and restart the applications is because we have applications with multiple instances, several clients in one instance, and we cannot just restart the application.

I hope the question will be clear regarding what I'm trying to achieve ...

+4
source share
3 answers

The code I'm using now seems to work fine if I don't have specific assemblies. The code that compiles the script and loads all the dynamic links 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 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 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 links that I defined inside the scope block into the compiler. Using the Compile class from C # .NET, I can compile the source code from a script and a link to external DLLS.

This code compiles:

  /// <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; } 
+1
source

I think you need to listen to the AppDomain.AssemblyResolve event, and then go from there, loading the assembly yourself. Additional information is available at http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx

and

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

0
source

Phil Haack wrote a blog about some of the not-so-well-known extensibility hooks in ASP.NET 4.0. One of them is an event that was launched at the very beginning of the application life cycle, where you can register assembly suppliers and add assembly references. You may be able to use this to dynamically add links, but you still need to restart the application. Here's a blog post with more info:

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

0
source

Source: https://habr.com/ru/post/1379450/


All Articles