[英]Dynamic code in ASP.NET
在我們的ASP.NET應用程序中,我們有一個功能,允許用C#或VB.NET代碼編寫腳本。 這些腳本存儲在數據庫中,並按指定的時間間隔進行編譯,執行存儲在這些腳本中的代碼。
只要用戶編寫基本的.NET代碼,這就可以工作。 當然,我們的客戶現在要求他們能夠引用自己的DLL以允許執行特定代碼。 這對我們來說是可以接受的,我們正在為此創建一個解決方案。 但是,我們希望在任何時候都避免使用特定方案:
不允許應用程序將引用的DLL文件復制到ASP.NET端的BIN文件夾中,因為這會重新啟動應用程序並且不支持/允許
我一直在玩CompilerOptions類,我注意到你可以在那里設置引用的庫。 從我在MSDN網站上找到的信息:
在我們的腳本中,我們還有以下機制; 用戶可以在其代碼中定義一個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 }
}
}
這將引用System.IO程序集和指定的兩個自定義DLL。 但是,對於當前的實現,我們將自定義DLL復制到GAC,然后只添加它們的名稱作為編譯器的引用。
是否可以禁用DLL的副本並使用這些dll的完整路徑進行引用,而無需將它們復制到應用程序的GAC / bin文件夾中? 是否可以使用CompilerOptions來設置libpath並讓所有引用都指向這個?
我們之所以不想復制Dll並重新啟動應用程序,是因為我們有多實例應用程序,單個實例上有多個客戶,而且我們不能簡單地重啟應用程序。
我希望問題很清楚我正在努力實現的目標......
我正在使用的代碼似乎工作正常,沒有我指定特定的程序集。 編譯腳本並加載所有動態引用的代碼如下所示:
/// <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#.NET中的Compile類,我可以從腳本編譯我的源代碼並鏈接到外部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;
}
我認為您需要收聽AppDomain.AssemblyResolve事件,然后從那里開始加載程序集。 有關更多信息,請訪問http://msdn.microsoft.com/en-us/library/system.appdomain.assemblyresolve.aspx
和
Phil Haack在博客中發表了關於ASP.NET 4.0中一些不太知名的可擴展性鈎子的博文。 其中一個是在應用程序生命周期中很早就啟動的事件,您可以在其中注冊構建提供程序並添加程序集引用。 也許您可以使用它來動態添加引用,但仍然需要重新啟動應用程序。 這是包含更多信息的博客文章:
http://haacked.com/archive/2010/05/16/three-hidden-extensibility-gems-in-asp-net-4.aspx
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.