Net网站的的编译与发布原理,vs中web网站和web应用
分类:计算机编程

ZKWeb网站框架是一个自主开发的网页框架,实现了动态插件和自动编译功能。
ZKWeb把一个文件夹当成是一个插件,无需使用csproj或xproj等形式的项目文件管理,并且支持修改插件代码后自动重新编译加载。

Vs2005和VS2008中都有建立web应用程序和Web网站,总搞的大家不知所戳。
web应用程序可能是微软为了让程序员很好的从winform过渡到web开发而保留了。Web网站就完全要应用到web开发的。其实两者之间没有什么大的区别,自己从表象总结了一下他们的异同点。
相同:
1、都是设计Asp网页的。
2、都可以添加ASP.Net文件夹(都包括App_Browsers、App_Data、App_GlobalResources、App_LocalResources、App_Themes)。
不同:
1、web应用程序Default.aspx显示有两个原有文件及Default.aspx.cs和Default.aspx.designer.cs;Web网站Default.aspx显示有一个原有文件Default.aspx.cs。
2、web应用程序有重新生成和发布两项;Web网站只有一个发布网站。
3、web应用程序和一般的winform没有什么区别都有引用的是命名空间等;Web网站在引用后出现一个bin文件夹那里存放dll和pdb文件。
4、web应用程序可以作为类库被引用;Web网站则不可以作为类库被引用。
5、web应用程序可以添加ASP.Net文件夹中不包括bin、App_Code;Web网站可以添加ASP.Net文件夹包括bin、App_Code。
6、web应用程序还可添加组件和类;Web网站则没有。
7、源文件虽然都是Default.aspx.cs但是web应用程序有命名空间,多了一项System.Collections空间引用。

如下所示创建一个简单的asp.Net Web应用程序

下面将说明ZKWeb如何实现这个功能,您也可以参考下面的代码和流程在自己的项目中实现。
ZKWeb的开源协议是MIT,有需要的代码可以直接搬,不需要担心协议问题。

 

                 图片 1

实现动态编译依赖的主要技术

编译: Roslyn Compiler
Roslyn是微软提供的开源的c# 6.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.net2005 中没有web应用程序项目。只有新建网站的功能。SP1中增加了web应用程序的功能。此功能推出,满足了众多VS.NET2003开发网站的朋友们。
vs2005的“网站”项目中。其实也有一些优点。原来的vs2003和VS2005SP1中的WEB应用程序.是将整个网站应用程序编译成一个DLL。而网站项目中是对每个aspx生成的代码文件,单独编译。特殊目录App_Code中代码文件才编译成单独一个程序集。这种设计。可以单独生成一个页和该页程序集。上传的时候,可以只更新此页。
但这个“网站”项目,编译速度慢,类型检查不彻底。两个不同的ASPX可以生成相同的两个名称的类。发布的时候,也很慢,会删除所有原始发布目录中的所有文件,且复制所有新的文件。并且中间还有停顿,需要用户主动按覆盖文件的按钮才能发布。
而在SP1中的WEB应用程序中,编译和发布速度中,明显变快,发布的时候一开始就可以设置是否覆盖。原来的网站要升级过来,需要生成一个设计类代码页。有了此文件,编译的时候,编译器就不用再分析ASPX页面了。明显加快了编译速度。且只生成一个程序集。执行的速度页快了。
WebApplication编程模型的优点:
●编译速度快,使用非增量编译模式,编译成单独的dll方便管理,。
●生成的程序集
WebSite:生成随机的程序集名,需要通过插件WebDeployment才可以生成单一程序集
WebApplication:可以指定网站项目生成单一程序集,因为是独立的程序集,所以和其他项目一样可以指定应用程序集的名字、版本、输出位置等信息
●可以将网站拆分成多个项目以方便管理
●可以从项目中和源代码管理中排除一个文件
●支持VSTS的Team Build方便每日构建
●更强大的代码检查功能,并且检查策略受源代码控制
●可以对编译前后进行自己规定的处理
●对App_GlobalResources 的Resource强类支持(网上说的,还没有了解过)
●直接升级使用VS2003构建的大型系统
WebSite编程模型的优点:
●动态编译该页面,马上可以看到效果,不用编译整个站点(主要优势)
●同上,可以使错误的部分和使用的部分不相干扰(可以要求只有编译通过才能签入)
●可以每个页面生成一个程序集(不会采用这种方式)
●可以把一个目录当做一个Web应用来处理,直接复制文件就可以发布,不需要项目文件(无所谓,只适合小站点)
●可以把页面也编译到程序集中(应该用不到,而且WebApplication也可以通过WebDeployment插件来实现)
两种编程模型的互相转换:
VS2005 SP1内置了转换程序,可以非常方便的从WebSite转换到WebApplication
只需要复制文件,右键执行“转换为Web应用程序”即可。
未查到有专门的反向转换工具,但比较后发现如果转换也非常简单。
删除所有*.designer.cs
将*.aspx、*.ascx、*.master页面文件中的 Codebehind="FileList.aspx.cs" 批量替换成 CodeFile="FileList.aspx.cs"

 

实现动态编译插件系统的流程

在ZKWeb框架中,插件是一个文件夹,网站的配置文件中的插件列表就是文件夹的列表。
在网站启动时,会查找每个文件夹下的*.cs文件对比文件列表和修改时间是否与上次编译的不同,如果不同则重新编译该文件夹下的代码。
网站启动后,会监视*.cs*.dll文件是否有变化,如果有变化则重新启动网站以重新编译。
ZKWeb的插件文件夹结构如下

  • 插件文件夹
    • bin:程序集文件夹
      • net: .Net Framework编译的程序集
        • 插件名称.dll: 编译出来的程序集
        • 插件名称.pdb: 调试文件
        • CompileInfo.txt: 储存了文件列表和修改时间
      • netstandard: .Net Core编译的程序集
        • 同net文件夹下的内容
    • src 源代码文件夹
    • static 静态文件的文件夹
    • 其他文件夹……

在VS中生成解决方案之后,可以在项目的目录下看到以下的文件:

通过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("rn",
        emitResult.Diagnostics.Where(d => d.WarningLevel == 0)));
}

到此已经完成了代码文件(cs)到程序集(dll)的编译,下面来看如何载入这个程序集。

                     图片 2

载入程序集

在.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)文件了,下面来看如何实现修改代码后自动重新编译。

 

检测代码文件变化并自动重新编译

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实现的动态编译技术大幅度的减少了开发时的等待时间,
主要节省在不需要每次都按快捷键编译和不需要像其他模块化开发一样需要从子项目复制dll文件到主项目,如果dll文件较多而且用了机械硬盘,复制时间可能会比编译时间还要漫长。

我将会在这个博客继续分享ZKWeb框架中使用的技术。
如果有不明白的部分,欢迎加入ZKWeb交流群522083886询问,

当我们通过VS将网站发布出去之后,可以看到,最后生成的文件,如下图所示:

                         图片 3

 

 

我们可以发现,发布之后的项目文件夹内少了很多文件,其实这是VS将aspx页面和一般处理程序以及Global文件等的后台文件都编译成了一个dll文件,这个dll文件存放在bin文件夹内:

                         图片 4

 

 

对这个程序集进行反编译之后,可以看到我们写的后台代码都编译到这个dll文件中了

                       图片 5

 

这个是一般处理程序的ProcessRequest方法中的代码,可以看到就是我们写的源代码:

                         图片 6

 

此时项目文件夹内的几个文件中只剩下一些简单的声明了,所有后台代码都已经不见了:

Global文件:图片 7

 

一般处理程序:

图片 8

aspx页面:

图片 9

 

 

 

然后我们通过以下这句代码分别获得页面,一般处理程序以及Global编译运行时所在的位置:

        System.Reflection.Assembly.GetExecutingAssembly().Location

 

经过对比可以发现Global文件、一般处理程序、aspx后台代码所在的程序集文件为同一个,而aspx前台页面的代码运行在另一个程序集中,以下为运行的结果:

 

Global和aspx页面的前台与后台运行文件:图片 10

一般处理程序的后台代码:

图片 11

 

 

我们可以看到IIS运行网站时,实际将网站编译之后的dll文件都放到了对应的Framework版本中的临时文件夹中了

即在C:WindowsMicrosoft.NETFrameworkv4.0.30319Temporary ASP.NET Files下,网站中自己写的后台代码与类库以及引用的类库都被编译到了这个临时文件夹下当前项目所在的文件夹下的**assembly文件下。**

 

 

网站发布后的文件分布图:

图片 12

 

 

 

 

 

此时对比发布的网站根目录下的bin文件夹里的dll文件和一般处理程序运行时所在的程序集,可以发现两者是同一个文件:

         图片 13

 

 

此时如果删除网站根目录先的bin文件夹内的dll,然后再浏览页面内,可以发现网站无法正常运行:

        图片 14

 

由上面的结果可以看出,虽然网站中已经存在了一个后台代码的dll文件,但是网站实际运行的是系统文件夹中的那个dll文件,这个应该是直接从网站中拷贝过去的,它们的MD5值完全相同。

在这里可以推测,对于asp.Net应用程序来说,IIS只会编译aspx页面,一般处理程序,Global等文件,但是不会编译其他的类文件,所有的类文件对IIS来说没有用处,IIS只能使用编译好的dll文件。

 

 

 

当网站正在运行时,无法删除系统文件夹中的那个dll文件,显示被IIS Worker Process占用,这个更加说明网站实际运行时使用的是这个dll文件

 

 

图片 15

 

那么这个IIS Worker Process进程是什么呢,当我们结束当前网站对应的w3wp.exe进程时,系统文件夹中dll被成功删除,这个可以说明,这个dll的调用者正是w3wp.exe这个进行,也说明了w3wp.exe是网站的工作进程。

 

 

当我们删除系统文件夹中的dll文件时,再次访问网站时会重新在系统文件夹中再次生成dll文件,而且第一次访问时候报错:

                图片 16

 

 

再次访问就正常了

 图片 17

 

 

总结:

所以可以总结出,在IIS中运行asp.Net应用程序时,前台页面的代码是即时进行更新和编译的,当我们修改前台代码时,不需要重新编译项目或者重新发布网站,在访问网站时,IIS(或者.Net框架?)检测到页面发生了修改会帮我们重新编译页面,而在修改了后台代码和其他的类文件的时候就需要我们手动对源代码进行重新编译了。

 

 



以上的内容都是针对Asp.Net应用程序来说的

 

 

对于Asp.Net网站来说发布网站时不会将页面和一般处理程序以及其他一些代码编译成dll文件,只会将源文件和引用的一些类库拷贝到网站的目录下,对于解决方案中的其他类库,也会在编译好之后再拷贝到Bin文件夹中。

在这里可以推测,在asp.Net网站中,IIS或者其他的程序,在有人第一次访问网站时,将App_Code文件里的源代码编译成了dll程序集

                 图片 18

当用户第一次请求时才进行即时编译,编译好的文件也是存在于系统文件夹下

如:C:WindowsMicrosoft.NETFrameworkv4.0.30319Temporary ASP.NET Filesroot7812c4be73872874

此时生成的文件有以下这些:

                     图片 19

 

 

直接在VS中调试时,情况与上述在IIS中运行类似,只是最后所在的文件目录有所不同,而且会生成很多调试用的文件:

C:WindowsMicrosoft.NETFrameworkv2.0.50727Temporary ASP.NET Fileswebsite1\0e58724ca4f149c8

             图片 20           

 

 

 

 

总结:

         Web应用程序和Web网站的前台页面的代码都是在网站运行时,被初次访问时进行编译的,而对于后台代码和网站中的一些类文件来说,Web网站也会在网站第一次被访问的时候被编译,所以,Web网站中的后台代码更改后不需要手动进行编译,而在Web应用程序中,后台代码和类文件都会在发布的时候进行编译,编译成功才能够正常发布,所以对于Web应用程序,修改了后台代码就必须重新编译然后发布。

 

 

 

注:**网站编译的临时目录可以在IIS中或者Web.config配置文件中进行配置,一下是IIS中的修改方法:**

在服务器级或者网站级的".Net编译"选项中修改编译的临时目录,默认情况下临时目录为网站对应的.Net Framework的版本下的Temporary ASP.NET Files文件夹。

图片 21

图片 22

 

 

本文由pc28.am发布于计算机编程,转载请注明出处:Net网站的的编译与发布原理,vs中web网站和web应用

上一篇:布局体和枚举类型,枚举类型和结构体 下一篇:学学笔记17
猜你喜欢
热门排行
精彩图文
  • Pycharm的安装和使用,Adelaide装修网深入分析厨房
    Pycharm的安装和使用,Adelaide装修网深入分析厨房
      MapServer linux上服务安装 关于厨房中水管的安装常见的就是下水管的安装,对于下水管的安装可能很多人都不知道该如何安装,青岛装修网资深装修达人说
  • 电子商务货品库的成品设计,PHP数组内容不重复
    电子商务货品库的成品设计,PHP数组内容不重复
    多年来在做ecshop的货物仓库储存模块,分别给黄金年代款商品的两性情格组合设置仓库储存,如下图: # 手艺文书档案 每一天逛天猫和京东的时候,映着重
  • 九彩拼盘的前端技能,LayUI框架的应用
    九彩拼盘的前端技能,LayUI框架的应用
    内容: HTML 普及标签和总体性 文书档案类型申明 转义字符 网页访问无障碍(只是掌握卡塔 尔(阿拉伯语:قطر‎ CSS 常用采取器 体制生效准绳(浏览器的
  • 编制程序总计,动态目的
    编制程序总计,动态目的
    dynamic是FrameWork4.0的新特色。dynamic的现身让C#具备了弱语言类型的风味。编写翻译器在编写翻译的时候不再对项目举行检查,编译期暗中同意dynamic对象扶植
  • 动态编写翻译,在线运转
    动态编写翻译,在线运转
    千帆竞发产生c#代码的在线编辑。     在帮顾客写JAVA客商端访谈.NET达成的Webservice的示范代码发掘了一个有意思的标题。为有保持安全性,使用了wse2.0sp