ZKWeb网址框架的动态编写翻译的落到实处原理,vs中web网址和web应用程序的区分

ZKWeb网址框架是2个独立开荒的网页框架,落成了动态插件和活动编写翻译功用。
ZKWeb把1个文书夹当成是2个插件,无需使用csproj或xproj等情势的类型文件管理,并且支持修改插件代码后活动重新编写翻译加载。

ZKWeb网址框架是三个自己作主开辟的网页框架,落成了动态插件和机关编写翻译作用。
ZKWeb把3个文书夹当成是二个插件,无需使用csproj或xproj等格局的项目文件管理,并且帮衬修改插件代码后自动重新编写翻译加载。

Vs200伍和VS2010中都有树立web应用程序和Web网址,总搞的豪门不知所戳。
web应用程序大概是微软为了让程序猿很好的从winform过渡到web开荒而保留了。Web网址就全盘要利用到web开拓的。其实两者之间没有啥样大的分别,本人从表象总计了一下他们的异同点。
相同:
壹、都以布署性Asp网页的。
二、都足以增加ASP.Net文件夹(都囊括App_Browsers、App_Data、App_GlobalResources、App_LocalResources、App_Themes)。
不同:
壹、web应用程序Default.aspx展现有几个原有文件及Default.aspx.cs和Default.aspx.designer.cs;Web网址Default.aspx展现有3个原来文件Default.aspx.cs。
二、web应用程序有双重生成和公告两项;Web网址唯有一个揭橥网址。
叁、web应用程序和一般的winform未有怎么分歧都有引用的是命名空间等;Web网址在引用前边世多个bin文件夹那里存放dll和pdb文件。
四、web应用程序能够用作类库被引述;Web网址则不得以视作类库被引述。
五、web应用程序能够加多ASP.Net文件夹中不包涵bin、App_Code;Web网址能够增多ASP.Net文件夹包罗bin、App_Code。
陆、web应用程序还可加多组件和类;Web网址则未有。
7、源文件就算都以Default.aspx.cs可是web应用程序有命名空间,多了一项System.Collections空间引用。

框架地址

上边将表明ZKWeb怎么着落到实处那么些效用,您也得以参照上边包车型地铁代码和流程在团结的等级次序中落到实处。
ZKWeb的开源协议是MIT,有亟待的代码能够一贯搬,不必要忧郁协议难点。

上边将表明ZKWeb怎样得以完结这一个作用,您也足以参考上面包车型地铁代码和流程在团结的种类中实现。
ZKWeb的开源协议是MIT,有亟待的代码能够一贯搬,不供给顾虑协议难题。

 

贯彻动态编写翻译重视的重中之重本领

编译: Roslyn Compiler
Roslyn是微软提供的开源的c#
陆.0编写翻译工具,能够经过Roslyn来支撑自宿主要编辑译功效。
要使用Roslyn能够安装nuget包Microsoft.CodeAnalysis.CSharp
微软还提供了更简短的Microsoft.CodeAnalysis.CSharp.Scripting包,那一个包只需简单几行就能兑现c#的动态脚本。

加载dll:
System.Runtime.Loader
在.Net
Framework中动态加载二个dll程序集能够动用Assembly.LoadFile,然而在.Net
Core中这么些函数被移除了。
微软为.Net
Core提供了一套斩新的顺序集管理机制,须要利用AssemblyLoadContext来加载程序集。
不满的是自个儿还未有找到微软官方关于那上边的证实。

生成pdb:
Microsoft.DiaSymReader.Native,
Microsoft.DiaSymReader.PortablePdb
为了帮助调节和测试编写翻译出来的程序集,还要求生成pdb调节和测试文件。
在.Net
Core中,Roslyn并不分包生成pdb的机能,还索要安装Microsoft.DiaSymReader.NativeMicrosoft.DiaSymReader.PortablePdb技能援助生成pdb文件。
安装了那么些包今后Roslyn会自动识别并运用。

金玉锦绣动态编写翻译注重的机要技能

编译: Roslyn Compiler
Roslyn是微软提供的开源的c#
陆.0编写翻译工具,能够因而Roslyn来支撑自宿主编写翻译功效。
要使用Roslyn能够安装nuget包Microsoft.CodeAnalysis.CSharp
微软还提供了更简短的Microsoft.CodeAnalysis.CSharp.Scripting包,这一个包只需轻便几行就能促成c#的动态脚本。

加载dll:
System.Runtime.Loader
在.Net
Framework中动态加载三个dll程序集能够选拔Assembly.LoadFile,然而在.Net
Core中这些函数被移除了。
微软为.Net
Core提供了壹套崭新的顺序集管理机制,必要运用AssemblyLoadContext来加载程序集。
遗憾的是自己还不曾找到微软官方关于那地点的印证。

生成pdb:
Microsoft.DiaSymReader.Native,
Microsoft.DiaSymReader.PortablePdb
为了协理调节和测试编译出来的程序集,还须求生成pdb调节和测试文件。
在.Net
Core中,Roslyn并不带有生成pdb的效果,还须求安装Microsoft.DiaSymReader.NativeMicrosoft.DiaSymReader.PortablePdb技术支撑生成pdb文件。
设置了这些包现在Roslyn会自动识别并动用。

原vs.net二零零五中一贯不web应用程序项目。唯有新建网址的效率。SP第11中学追加了web应用程序的功用。此意义推出,满意了重重VS.NET200三付出网址的情人们。
vs200伍的“网址”项目中。其实也有局地亮点。原来的vs2003和VS200伍SP第11中学的WEB应用程序.是将全部网址应用程序编写翻译成1个DLL。而网址项目中是对种种aspx生成的代码文件,单独编写翻译。特殊目录App_Code中代码文件才编译成单独三个顺序集。那种陈设。能够单独生成二个页和该页程序集。上传的时候,能够只更新此页。
但那一个“网址”项目,编写翻译速度慢,类型检查不到底。多个例外的ASPX能够转变一样的七个名称的类。发表的时候,也不快,会去除全数原始发表目录中的全体文件,且复制全数新的文书。并且中间还有停顿,须求用户积极按覆盖文件的按键手艺揭橥。
而在SP第11中学的WEB应用程序中,编写翻译和发布速度中,明显变快,发布的时候一开首就可以安装是或不是覆盖。原来的网址要晋级过来,须要生成二个安排类代码页。有了此文件,编写翻译的时候,编写翻译器就无须再分析ASPX页面了。明显加快了编写翻译速度。且只生成二个先后集。实行的快慢页快了。
WebApplication编制程序模型的独到之处:
●编写翻译速度快,使用非增量编写翻译情势,编写翻译成单独的dll方便管理,。
●生成的次第集
WebSite:生成随机的顺序集名,须求通过插件WebDeployment才足以转移单一程序集
WebApplication:能够内定网址项目更动单1程序集,因为是单身的程序集,所以和别的连串雷同能够钦定应用程序集的名字、版本、输出地方等音信
●能够将网址拆分成八个体系以方便管理
●能够从类型花月源代码管理中排除贰个文本
ZKWeb网址框架的动态编写翻译的落到实处原理,vs中web网址和web应用程序的区分。●扶助VSTS的Team Build方便每一天营造
●越来越强劲的代码检查作用,并且检查攻略受源代码调整
●能够对编写翻译前后开始展览和谐分明的管理
●对App_GlobalResources 的Resource强类扶助(英特网说的,还尚无询问过)
●直接进级使用VS200三构建的巨型系统
WebSite编制程序模型的帮助和益处:
●动态编写翻译该页面,立时可以见见效果,不用编写翻译整个站点(首要优势)
●同上,可以使错误的有的和采纳的局地不相困扰(能够要求唯有编写翻译通过本事签入)
●可以每种页目生成三个程序集(不会利用那种办法)
●能够把七个索引当做3个Web应用来管理,直接复制文件就可以发布,不需重要项目目文件(无所谓,只适合小站点)
●能够把页面也编写翻译到程序聚集(应该用不到,而且WebApplication也得以透过WebDeployment插件来贯彻)
两种编制程序模型的互相调换:
VS200五 SP一内置了转移程序,能够足够便宜的从WebSite调换来WebApplication
只供给复制文件,右键实践“调换为Web应用程序”就可以。
未查到有专门的反向转变工具,但相比较后发觉只要调换也卓殊轻便。
去除全部*.designer.cs
将*.aspx、*.ascx、*.master页面文件中的 Codebehind=”FileList.aspx.cs”
批量替换来 CodeFile=”FileList.aspx.cs”

新的文书档案地址

落实动态编写翻译插件系统的流水生产线

在ZKWeb框架中,插件是3个文件夹,网址的配置文件中的插件列表便是文件夹的列表。
在网址运行时,会招来各样文件夹下的*.cs文本相比较文件列表和修改时间是或不是与上次编译的差别,借使分化则另行编写翻译该公文夹下的代码。
网址运维后,会监视*.cs*.dll文件是还是不是有生成,假使有生成则再一次启航网址以重新编写翻译。
ZKWeb的插件文件夹结构如下

  • 插件文件夹
    • bin:程序集文件夹
      • net: .Net Framework编写翻译的次第集
        • 插件名称.dll: 编写翻译出来的先后集
        • 插件名称.pdb: 调试文件
        • CompileInfo.txt: 积攒了文件列表和修改时间
      • netstandard: .Net Core编写翻译的主次集
        • 同net文件夹下的剧情
    • src 源代码文件夹
    • static 静态文件的文件夹
    • 其余文件夹……

贯彻动态编译插件系统的流水生产线

在ZKWeb框架中,插件是1个文本夹,网站的安排文件中的插件列表正是文件夹的列表。
在网站运维时,会招来每个文件夹下的*.cs文本相比较文件列表和修改时间是还是不是与上次编写翻译的例外,假诺分裂则另行编写翻译该文件夹下的代码。
网站运维后,会监视*.cs*.dll文件是或不是有转换,要是有调换则再度起动网址以重新编写翻译。
ZKWeb的插件文件夹结构如下

  • 插件文件夹
    • bin:程序集文件夹
      • net: .Net Framework编写翻译的先后集
        • 插件名称.dll: 编写翻译出来的次序集
        • 插件名称.pdb: 调节和测试文件
        • CompileInfo.txt: 积累了文件列表和修改时间
      • netstandard: .Net Core编写翻译的次第集
        • 同net文件夹下的内容
    • src 源代码文件夹
    • static 静态文件的文件夹
    • 别的文件夹……

通过Roslyn编写翻译代码文件到程序集dll

在网站运行时,插件处理器在获得插件文件夹列表后会使用Directory.EnumerateFiles递归查找该公文夹下的装有*.cs文件。
在获得那么些代码文件路线后,大家就足以传给Roslyn让它编写翻译出dll程序集。
ZKWeb调用Roslyn编译的完全代码能够查看那里,上边表明编写翻译的流水生产线:

率先调用CSharpSyntaxTree.ParseText来分析代码列表到语法树列表,我们能够从源代码列表得出List<SyntaxTree>
parseOptions是分析选项,ZKWeb会在.Net
Core编写翻译时定义NETCORE标识,那样插件代码中得以应用#if NETCORE来定义.Net
Core专用的管理。
path是文件路线,必须传入文件路线才具调解生成出来的程序集,不然正是生成了pdb也无法捕捉断点。

// Parse source files into syntax trees
// Also define NETCORE for .Net Core
var parseOptions = CSharpParseOptions.Default;
#if NETCORE
parseOptions = parseOptions.WithPreprocessorSymbols("NETCORE");
#endif
var syntaxTrees = sourceFiles
    .Select(path => CSharpSyntaxTree.ParseText(
        File.ReadAllText(path), parseOptions, path, Encoding.UTF8))
.ToList();

接下去供给分析代码中的using来搜索代码依赖了哪些程序集,并逐条载入那么些程序集。
举个例子碰着using System.Threading;会尝试载入SystemSystem.Threading程序集。

// Find all using directive and load the namespace as assembly
// It's for resolve assembly dependencies of plugin
LoadAssembliesFromUsings(syntaxTrees);

LoadAssembliesFromUsings的代码如下,尽管相比长可是逻辑并不复杂。
关于IAssemblyLoader就要背后解说,那里只须求知道它能够按名称载入程序集。

/// <summary>
/// Find all using directive
/// And try to load the namespace as assembly
/// </summary>
/// <param name="syntaxTrees">Syntax trees</param>
protected void LoadAssembliesFromUsings(IList<SyntaxTree> syntaxTrees) {
    // Find all using directive
    var assemblyLoader = Application.Ioc.Resolve<IAssemblyLoader>();
    foreach (var tree in syntaxTrees) {
        foreach (var usingSyntax in ((CompilationUnitSyntax)tree.GetRoot()).Usings) {
            var name = usingSyntax.Name;
            var names = new List<string>();
            while (name != null) {
                // The type is "IdentifierNameSyntax" if it's single identifier
                // eg: System
                // The type is "QualifiedNameSyntax" if it's contains more than one identifier
                // eg: System.Threading
                if (name is QualifiedNameSyntax) {
                    var qualifiedName = (QualifiedNameSyntax)name;
                    var identifierName = (IdentifierNameSyntax)qualifiedName.Right;
                    names.Add(identifierName.Identifier.Text);
                    name = qualifiedName.Left;
                } else if (name is IdentifierNameSyntax) {
                    var identifierName = (IdentifierNameSyntax)name;
                    names.Add(identifierName.Identifier.Text);
                    name = null;
                }
            }
            if (names.Contains("src")) {
                // Ignore if it looks like a namespace from plugin 
                continue;
            }
            names.Reverse();
            for (int c = 1; c <= names.Count; ++c) {
                // Try to load the namespace as assembly
                // eg: will try "System" and "System.Threading" from "System.Threading"
                var usingName = string.Join(".", names.Take(c));
                if (LoadedNamespaces.Contains(usingName)) {
                    continue;
                }
                try {
                    assemblyLoader.Load(usingName);
                } catch {
                }
                LoadedNamespaces.Add(usingName);
            }
        }
    }
}

透过地方这一步后,代码正视的持有程序集应该都载入到当下进程中了,
作者们须求搜索那些程序集并且传给Roslyn,在编写翻译代码时引用那些程序集文件。
上面包车型客车代码生成了一个List<PortableExecutableReference>对象。

// Add loaded assemblies to compile references
var assemblyLoader = Application.Ioc.Resolve<IAssemblyLoader>();
var references = assemblyLoader.GetLoadedAssemblies()
    .Select(assembly => assembly.Location)
    .Select(path => MetadataReference.CreateFromFile(path))
    .ToList();

创设编写翻译选项
此间必要调用微软非公开的函数WithTopLevelBinderFlags来安装IgnoreCorLibraryDuplicatedTypes。
其1标识让Roslyn能够忽略System.Runtime.Extensions和System.Private.CoreLib中重复的门类。
倘诺急需让Roslyn寻常办事在windows和linux上,必须安装那一个标记,具体能够看
Roslyn Scripting暗许会使用这一个标记,操蛋的微软

// Create compilation options and set IgnoreCorLibraryDuplicatedTypes flag
// To avoid error like The type 'Path' exists in both
// 'System.Runtime.Extensions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
// and
// 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
var compilationOptions = new CSharpCompilationOptions(
    OutputKind.DynamicallyLinkedLibrary,
    optimizationLevel: optimizationLevel);
var withTopLevelBinderFlagsMethod = compilationOptions.GetType()
    .FastGetMethod("WithTopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic);
var binderFlagsType = withTopLevelBinderFlagsMethod.GetParameters()[0].ParameterType;
compilationOptions = (CSharpCompilationOptions)withTopLevelBinderFlagsMethod.FastInvoke(
    compilationOptions,
    binderFlagsType.GetField("IgnoreCorLibraryDuplicatedTypes").GetValue(binderFlagsType));

最终调用Roslyn编译,传入语法树列表和引用程序集列表能够获取目的程序集。
使用Emit函数编写翻译后会重回三个EmitResult目标,里面保存了编写翻译中冒出的失实和警示音信。
留神编写翻译出错开上下班时间Emit不会抛出差异,要求手动物检疫查EmitResult中的Success属性。

// Compile to assembly, throw exception if error occurred
var compilation = CSharpCompilation.Create(assemblyName)
    .WithOptions(compilationOptions)
    .AddReferences(references)
    .AddSyntaxTrees(syntaxTrees);
var emitResult = compilation.Emit(assemblyPath, pdbPath);
if (!emitResult.Success) {
    throw new CompilationException(string.Join("\r\n",
        emitResult.Diagnostics.Where(d => d.WarningLevel == 0)));
}

到此已经到位了代码文件(cs)到程序集(dll)的编写翻译,上边来看哪样载入这几个顺序集。

经过Roslyn编写翻译代码文件到程序集dll

在网址运营时,插件管理器在获取插件文件夹列表后会动用Directory.EnumerateFiles递归查找该文件夹下的持有*.cs文件。
在赢得这么些代码文件路径后,大家就可以传给Roslyn让它编译出dll程序集。
ZKWeb调用Roslyn编写翻译的全部代码能够翻看这里,上边表明编写翻译的流程:

第一调用CSharpSyntaxTree.ParseText来分析代码列表到语法树列表,我们得以从源代码列表得出List<SyntaxTree>
parseOptions是分析选项,ZKWeb会在.Net
Core编写翻译时定义NETCORE标识,那样插件代码中能够动用#if NETCORE来定义.Net
Core专用的拍卖。
path是文本路线,必须传入文件路线才具调度生成出来的程序集,不然固然生成了pdb也无法捕捉断点。

// Parse source files into syntax trees
// Also define NETCORE for .Net Core
var parseOptions = CSharpParseOptions.Default;
#if NETCORE
parseOptions = parseOptions.WithPreprocessorSymbols("NETCORE");
#endif
var syntaxTrees = sourceFiles
    .Select(path => CSharpSyntaxTree.ParseText(
        File.ReadAllText(path), parseOptions, path, Encoding.UTF8))
.ToList();

接下去须要分析代码中的using来寻找代码正视了怎么样程序集,并壹一载入那个程序集。
比方说遇到using System.Threading;会尝试载入SystemSystem.Threading程序集。

// Find all using directive and load the namespace as assembly
// It's for resolve assembly dependencies of plugin
LoadAssembliesFromUsings(syntaxTrees);

LoadAssembliesFromUsings的代码如下,就算相比长然而逻辑并不复杂。
关于IAssemblyLoader将要前面演说,那里只必要明白它能够按名称载入程序集。

/// <summary>
/// Find all using directive
/// And try to load the namespace as assembly
/// </summary>
/// <param name="syntaxTrees">Syntax trees</param>
protected void LoadAssembliesFromUsings(IList<SyntaxTree> syntaxTrees) {
    // Find all using directive
    var assemblyLoader = Application.Ioc.Resolve<IAssemblyLoader>();
    foreach (var tree in syntaxTrees) {
        foreach (var usingSyntax in ((CompilationUnitSyntax)tree.GetRoot()).Usings) {
            var name = usingSyntax.Name;
            var names = new List<string>();
            while (name != null) {
                // The type is "IdentifierNameSyntax" if it's single identifier
                // eg: System
                // The type is "QualifiedNameSyntax" if it's contains more than one identifier
                // eg: System.Threading
                if (name is QualifiedNameSyntax) {
                    var qualifiedName = (QualifiedNameSyntax)name;
                    var identifierName = (IdentifierNameSyntax)qualifiedName.Right;
                    names.Add(identifierName.Identifier.Text);
                    name = qualifiedName.Left;
                } else if (name is IdentifierNameSyntax) {
                    var identifierName = (IdentifierNameSyntax)name;
                    names.Add(identifierName.Identifier.Text);
                    name = null;
                }
            }
            if (names.Contains("src")) {
                // Ignore if it looks like a namespace from plugin 
                continue;
            }
            names.Reverse();
            for (int c = 1; c <= names.Count; ++c) {
                // Try to load the namespace as assembly
                // eg: will try "System" and "System.Threading" from "System.Threading"
                var usingName = string.Join(".", names.Take(c));
                if (LoadedNamespaces.Contains(usingName)) {
                    continue;
                }
                try {
                    assemblyLoader.Load(usingName);
                } catch {
                }
                LoadedNamespaces.Add(usingName);
            }
        }
    }
}

因而地点这一步后,代码正视的享有程序集应该都载入到目前进度中了,
咱俩要求寻觅那么些程序集并且传给Roslyn,在编写翻译代码时引用这几个程序集文件。
上面包车型大巴代码生成了一个List<PortableExecutableReference>对象。

// Add loaded assemblies to compile references
var assemblyLoader = Application.Ioc.Resolve<IAssemblyLoader>();
var references = assemblyLoader.GetLoadedAssemblies()
    .Select(assembly => assembly.Location)
    .Select(path => MetadataReference.CreateFromFile(path))
    .ToList();

塑造编写翻译选项
那边必要调用微软非公开的函数WithTopLevelBinderFlags来设置IgnoreCorLibraryDuplicatedTypes。
以此标记让Roslyn能够忽略System.Runtime.Extensions和System.Private.CoreLib中再一次的花色。
假设供给让Roslyn符合规律办事在windows和linux上,必须设置这么些标识,具体能够看
Roslyn Scripting暗中同意会动用这几个标识,操蛋的微软

// Create compilation options and set IgnoreCorLibraryDuplicatedTypes flag
// To avoid error like The type 'Path' exists in both
// 'System.Runtime.Extensions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'
// and
// 'System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
var compilationOptions = new CSharpCompilationOptions(
    OutputKind.DynamicallyLinkedLibrary,
    optimizationLevel: optimizationLevel);
var withTopLevelBinderFlagsMethod = compilationOptions.GetType()
    .FastGetMethod("WithTopLevelBinderFlags", BindingFlags.Instance | BindingFlags.NonPublic);
var binderFlagsType = withTopLevelBinderFlagsMethod.GetParameters()[0].ParameterType;
compilationOptions = (CSharpCompilationOptions)withTopLevelBinderFlagsMethod.FastInvoke(
    compilationOptions,
    binderFlagsType.GetField("IgnoreCorLibraryDuplicatedTypes").GetValue(binderFlagsType));

最后调用Roslyn编写翻译,传入语法树列表和引用程序集列表能够获得目标程序集。
使用Emit函数编写翻译后会再次来到三个EmitResult对象,里面保存了编译中冒出的荒唐和警示音信。
留意编写翻译出错开上下班时间Emit不会抛出不相同,供给手动检查EmitResult中的Success属性。

// Compile to assembly, throw exception if error occurred
var compilation = CSharpCompilation.Create(assemblyName)
    .WithOptions(compilationOptions)
    .AddReferences(references)
    .AddSyntaxTrees(syntaxTrees);
var emitResult = compilation.Emit(assemblyPath, pdbPath);
if (!emitResult.Success) {
    throw new CompilationException(string.Join("\r\n",
        emitResult.Diagnostics.Where(d => d.WarningLevel == 0)));
}

到此已经达成了代码文件(cs)到程序集(dll)的编译,下边来看怎样载入这些程序集。

请参见上述的文书档案以获得最新的音信。

载入程序集

在.Net
Framework中,载入程序集文件分外简单,只必要调用Assembly.LoadFile
在.Net
Core中,载入程序集文件需求定义AssemblyLoadContext,并且存有有关的顺序集都亟需通过同二个Context来载入。
亟需专注的是AssemblyLoadContext不能够用在.Net
Framework中,ZKWeb为了祛除那一个距离定义了IAssemblyLoader接口。
完全的代码能够查看
IAssemblyLoader
CoreAssemblyLoader
NetAssemblyLoader

.Net
Framework的载入只是调用了Assembly中原来的函数,那里就不再表达了。
.Net Core使用的载入器定义了AssemblyLoadContext,代码如下:
代码中的plugin.ReferenceAssemblyPath指的是插件自带的第二方dll文件,用于载入插件正视可是主项目中尚无引用的dll文件。

/// <summary>
/// The context for loading assembly
/// </summary>
private class LoadContext : AssemblyLoadContext {
    protected override Assembly Load(AssemblyName assemblyName) {
        try {
            // Try load directly
            return Assembly.Load(assemblyName);
        } catch {
            // If failed, try to load it from plugin's reference directory
            var pluginManager = Application.Ioc.Resolve<PluginManager>();
            foreach (var plugin in pluginManager.Plugins) {
                var path = plugin.ReferenceAssemblyPath(assemblyName.Name);
                if (path != null) {
                    return LoadFromAssemblyPath(path);
                }
            }
            throw;
        }
    }
}

定义了LoadContext尔后供给把这一个类设为单例,载入时都经过这几个Context来载入。
因为.Net
Core如今不可能得到到全部已载入的程序集,只可以获得程序本人正视的程序集列表,
那里还增加了一个ISet<Assembly> LoadedAssemblies用于记录历史载入的持有程序集。

/// <summary>
/// Load assembly by name
/// </summary>
public Assembly Load(string name) {
    // Replace name if replacement exists
    name = ReplacementAssemblies.GetOrDefault(name, name);
    var assembly = Context.LoadFromAssemblyName(new AssemblyName(name));
    LoadedAssemblies.Add(assembly);
    return assembly;
}

/// <summary>
/// Load assembly by name object
/// </summary>
public Assembly Load(AssemblyName assemblyName) {
    var assembly = Context.LoadFromAssemblyName(assemblyName);
    LoadedAssemblies.Add(assembly);
    return assembly;
}

/// <summary>
/// Load assembly from it's binary contents
/// </summary>
public Assembly Load(byte[] rawAssembly) {
    using (var stream = new MemoryStream(rawAssembly)) {
        var assembly = Context.LoadFromStream(stream);
        LoadedAssemblies.Add(assembly);
        return assembly;
    }
}

/// <summary>
/// Load assembly from file path
/// </summary>
public Assembly LoadFile(string path) {
    var assembly = Context.LoadFromAssemblyPath(path);
    LoadedAssemblies.Add(assembly);
    return assembly;
}

到此地早已得以载入编写翻译的主次集(dll)文件了,上边来看哪样贯彻修改代码后自动重新编写翻译。

载入程序集

在.Net
Framework中,载入程序集文件十分轻易,只要求调用Assembly.LoadFile
在.Net
Core中,载入程序集文件供给定义AssemblyLoadContext,并且有着相关的先后集都亟待经过同二个Context来载入。
亟需留意的是AssemblyLoadContext不能够用在.Net
Framework中,ZKWeb为了化解那个差异定义了IAssemblyLoader接口。
完全的代码能够查阅
IAssemblyLoader
CoreAssemblyLoader
NetAssemblyLoader

.Net
Framework的载入只是调用了Assembly中原来的函数,那里就不再表明了。
.Net Core使用的载入器定义了AssemblyLoadContext,代码如下:
代码中的plugin.ReferenceAssemblyPath指的是插件自带的第2方dll文件,用于载入插件重视不过主项目中尚无引用的dll文件。

/// <summary>
/// The context for loading assembly
/// </summary>
private class LoadContext : AssemblyLoadContext {
    protected override Assembly Load(AssemblyName assemblyName) {
        try {
            // Try load directly
            return Assembly.Load(assemblyName);
        } catch {
            // If failed, try to load it from plugin's reference directory
            var pluginManager = Application.Ioc.Resolve<PluginManager>();
            foreach (var plugin in pluginManager.Plugins) {
                var path = plugin.ReferenceAssemblyPath(assemblyName.Name);
                if (path != null) {
                    return LoadFromAssemblyPath(path);
                }
            }
            throw;
        }
    }
}

定义了LoadContext从此未来供给把那么些类设为单例,载入时都由此这些Context来载入。
因为.Net
Core最近无法得到到具备已载入的程序集,只可以获取程序本人依赖的顺序集列表,
那里还增多了叁个ISet<Assembly> LoadedAssemblies用以记录历史载入的有所程序集。

/// <summary>
/// Load assembly by name
/// </summary>
public Assembly Load(string name) {
    // Replace name if replacement exists
    name = ReplacementAssemblies.GetOrDefault(name, name);
    var assembly = Context.LoadFromAssemblyName(new AssemblyName(name));
    LoadedAssemblies.Add(assembly);
    return assembly;
}

/// <summary>
/// Load assembly by name object
/// </summary>
public Assembly Load(AssemblyName assemblyName) {
    var assembly = Context.LoadFromAssemblyName(assemblyName);
    LoadedAssemblies.Add(assembly);
    return assembly;
}

/// <summary>
/// Load assembly from it's binary contents
/// </summary>
public Assembly Load(byte[] rawAssembly) {
    using (var stream = new MemoryStream(rawAssembly)) {
        var assembly = Context.LoadFromStream(stream);
        LoadedAssemblies.Add(assembly);
        return assembly;
    }
}

/// <summary>
/// Load assembly from file path
/// </summary>
public Assembly LoadFile(string path) {
    var assembly = Context.LoadFromAssemblyPath(path);
    LoadedAssemblies.Add(assembly);
    return assembly;
}

到此处1度足以载入编写翻译的次序集(dll)文件了,上边来看怎样贯彻修改代码后活动重新编写翻译。


检查评定代码文件变化并自动重新编写翻译

ZKWeb使用了FileSystemWatcher来检查评定代码文件的变动,完整代码能够查看那里。
重在的代码如下

// Function use to stop website
Action stopWebsite = () => {
    var stoppers = Application.Ioc.ResolveMany<IWebsiteStopper>();
    stoppers.ForEach(s => s.StopWebsite());
};
// Function use to handle file changed
Action<string> onFileChanged = (path) => {
    var ext = Path.GetExtension(path).ToLower();
    if (ext == ".cs" || ext == ".json" || ext == ".dll") {
        stopWebsite();
    }
};
// Function use to start file system watcher
Action<FileSystemWatcher> startWatcher = (watcher) => {
    watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
    watcher.Changed += (sender, e) => onFileChanged(e.FullPath);
    watcher.Created += (sender, e) => onFileChanged(e.FullPath);
    watcher.Deleted += (sender, e) => onFileChanged(e.FullPath);
    watcher.Renamed += (sender, e) => { onFileChanged(e.FullPath); onFileChanged(e.OldFullPath); };
    watcher.EnableRaisingEvents = true;
};
// Monitor plugin directory
var pathManager = Application.Ioc.Resolve<PathManager>();
pathManager.GetPluginDirectories().Where(p => Directory.Exists(p)).ForEach(p => {
    var pluginFilesWatcher = new FileSystemWatcher();
    pluginFilesWatcher.Path = p;
    pluginFilesWatcher.IncludeSubdirectories = true;
    startWatcher(pluginFilesWatcher);
});

那段代码监视了插件文件夹下的cs, json, dll文件,
假若发生变化就调用IWebsiteStopper来终止网址,网址下次展开时将会另行编写翻译和载入插件。
IWebsiteStopper是3个华而不实的接口,在Asp.Net中甘休网址调用了HttpRuntime.UnloadAppDomain,而在Asp.Net
Core中结束网址调用了IApplicationLifetime.StopApplication

Asp.Net结束网址会卸载当前的AppDomain,下次刷新网页时会自动重新起动。
而Asp.Net
Core甘休网址会结束当前的历程,使用IIS托管时IIS会在机关重启进度,但利用自宿主时则必要注重外部工具来重启。

检查实验代码文件变化并机关重新编写翻译

ZKWeb使用了FileSystemWatcher来检查测试代码文件的扭转,完整代码能够查看那里。
首要的代码如下

// Function use to stop website
Action stopWebsite = () => {
    var stoppers = Application.Ioc.ResolveMany<IWebsiteStopper>();
    stoppers.ForEach(s => s.StopWebsite());
};
// Function use to handle file changed
Action<string> onFileChanged = (path) => {
    var ext = Path.GetExtension(path).ToLower();
    if (ext == ".cs" || ext == ".json" || ext == ".dll") {
        stopWebsite();
    }
};
// Function use to start file system watcher
Action<FileSystemWatcher> startWatcher = (watcher) => {
    watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
    watcher.Changed += (sender, e) => onFileChanged(e.FullPath);
    watcher.Created += (sender, e) => onFileChanged(e.FullPath);
    watcher.Deleted += (sender, e) => onFileChanged(e.FullPath);
    watcher.Renamed += (sender, e) => { onFileChanged(e.FullPath); onFileChanged(e.OldFullPath); };
    watcher.EnableRaisingEvents = true;
};
// Monitor plugin directory
var pathManager = Application.Ioc.Resolve<PathManager>();
pathManager.GetPluginDirectories().Where(p => Directory.Exists(p)).ForEach(p => {
    var pluginFilesWatcher = new FileSystemWatcher();
    pluginFilesWatcher.Path = p;
    pluginFilesWatcher.IncludeSubdirectories = true;
    startWatcher(pluginFilesWatcher);
});

那段代码监视了插件文件夹下的cs, json, dll文件,
如若发生变化就调用IWebsiteStopper来终止网址,网址下次张开时将会重新编写翻译和载入插件。
IWebsiteStopper是二个浮泛的接口,在Asp.Net中甘休网址调用了HttpRuntime.UnloadAppDomain,而在Asp.Net
Core中甘休网址调用了IApplicationLifetime.StopApplication

Asp.Net甘休网址会卸载当前的AppDomain,下次刷新网页时会自动重新起动。
而Asp.Net
Core停止网址会结束当前的过程,使用IIS托管时IIS会在自行重启进程,但利用自宿主时则供给依据外部工具来重启。

ZKWeb是几个重中之重快速支付和模块开垦的网址框架。

写在结尾

ZKWeb达成的动态编写翻译手艺大幅的削减了开辟时的等待时间,
驷不及舌节省在不供给每一回都按火速键编写翻译和不需求像其余模块化开采同样须求从子项目复制dll文件到主项目,倘诺dll文件较多而且用了形而上学硬盘,复制时间或然会比编写翻译时间还要漫长。

本人将会在这一个博客继续分享ZKWeb框架中运用的技能。
假诺有不知情的一些,应接参预ZKWeb交换群5220838捌六打探,

写在最终

ZKWeb落成的动态编译技艺急剧的收缩了支出时的守候时间,
第一节省在不必要每一遍都按飞速键编写翻译和不需要像别的模块化开垦一样需求从子项目复制dll文件到主项目,假诺dll文件较多而且用了教条硬盘,复制时间或然会比编写翻译时间还要漫长。

本人将会在那个博客继续分享ZKWeb框架中应用的才干。
1经有不知道的片段,招待参预ZKWeb交换群522083886理解,

提供了动态插件和电动管理数据库结构的遵循。

模板系统和自动生成页面参考了Django的做法,并坚守Don’t repeat
yourself原则。

一言九鼎职能

  • .Net Core支持
    • 支撑运营在.Net Framework和.Net Core上
  • 插件系统
    • 使用Roslyn
    • 支撑动态加载插件
    • 支撑修改插件源代码后自动重新编写翻译和加载
  • 模板系统
    • 使用DotLiquid
    • 支撑Django风格的沙盘重载
    • 支撑手提式有线电话机版专用模板(优先从templates.mobile读取模板内容)
    • 支撑区域和针对性区域的动态内容,能够在那基础上落到实处可视化编辑
    • 支撑对页面中的部分剧情张开独立缓存,能够大幅度进步页面包车型客车响应速度
  • IoC容器
    • 轻量且一点也不慢
    • 私下认可扶助使用性质注册程序聚焦的品类到容器
    • 支撑构造函数注入
  • 帮助多个框架的托管
    • 支持托管在Asp.Net
    • 协理托管在Asp.Net Core
    • 援助托管在Owin
    • 插件不需求理会托管在哪些框架,使用抽象层就可以
  • 支撑几个OKugaM
    • 支持Dapper
    • 支持EntityFramework Core
    • 美高梅开户网址,支持InMemory
    • 支持MongoDB
    • 支持NHibernate
      • NHibernate还不能够运作在.Net Core上
    • NHibernate和EFCore援救运转时自动更新数据表结构,不必要手动员搬迁移
    • O奥迪Q3M有统一的抽象层,壹份代码能够而且在颇具OTucsonM上运维,但不能兑现完全相配
  • 本地化
    • 扶助多语言
    • 支撑多时区
    • 提供了gettext风格的翻译函数
  • 测试
    • 支撑在调控台和网页运转测试
    • 支撑在测试中重载IoC容器
    • 支撑在测试中重载Http上下文
    • 支撑在测试中使用一时数据库
  • 项目工具
    • 提供创立项目选用的工具
    • 提供发表项目选拔的工具

默许插件聚集的最首要成效

  • 自动生成和表达表单
  • 自动生成Ajax表格
  • 自动生成CRUD页面
  • 定时任务
  • 验证码
  • 治本后台(使用AdminLTE)
  • 机动伪静态,几乎从未额外费用
  • 多货币和多国家援助
  • 更多职能请查看各插件的文书档案

类型地址

DEMO

地址:

用户名: demo

密码: 123456

项目进程

基本框架已发表标准的本子。
业务插件仍在编写制定,目的是应用那套框架做二个开源的百货市4系统。

讨论QQ群:522083886

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图