这几天BrnShop的开发工作比较多,所以这一篇文章来的晚了一些,还请大家见谅呀!还有通知大家一下BrnShop1.0.312版本已经发布,此版本添加了报表统计等新功能,需要源码的园友可以点此下载。好了,我们现在进入今天的正题。关于BrnShop插件内容比较多,所以我分成两篇文章来讲解,今天先讲第一部分内容:插件的工作机制。

  对于任意一种插件机制来说,基本上只要解决以下三个方面的问题,这个插件机制就算成功了。这三个方面如下:

  • 插件程序集的加载
  • 视图文件的路径和编译
  • 插件的部署

  首先是插件程序集的加载(请仔细阅读,下面的坑比较多),最简单的方式就是直接将插件程序集复制到网站根目录下的bin文件夹(够简单吧!),但是我们不推荐使用这种方式,因为这种方式导致插件的程序集和视图文件等内容分布在不同目录,为以后的维护带来不便。我们期望的是插件的所有内容都在同一个目录中,以BrnShop的支付宝插件为例:

所有的插件文件都在“BrnShop.PayPlugin.Alipay”目录中,这样我们以后的删除,扩展等就方便多了。好了现在第一个坑来了,那就是如何让asp.net加载此目录下的程序集文件?我们可能会想到使用web.config文件中probing节点来配置asp.net运行时探测程序集的目录,但是很不幸它不管用(不信你可以试一下)。代码如下:

  <runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Plugins/bin/" />
</assemblyBinding>
</runtime>

  既然自动加载不行,那我们就手动加载吧。手动加载需要使用一个方法:System.Web.Compilation.BuildManager.AddReferencedAssembly(Assembly assembly),此方法的的MSDN解释如下图:

通过调用这个方法可以手动加载指定的程序集(就是它的参数)到我们的程序中。在调用这个方法时有两个坑需要注意,第一个坑是这个方法必须在Application_Start 事件发生前调用,针对这个坑我们可以使用微软在.NET4.0中提供的PreApplicationStartMethodAttribute性质(此性质的具体介绍大家可以看这篇文章:http://haacked.com/archive/2010/05/16/three-hidden-extensibility-gems-in-asp-net-4.aspx/)来解决这个问题。代码如下:

[assembly: PreApplicationStartMethod(typeof(BrnShop.Core.BSPPlugin), "Load")]

第二个坑是CLR会锁定程序集文件,所以如果我们直接读取此文件会导致异常,解决这个坑的方式是复制它的一个副本,然后不读取原程序集,而是读取程序集的副本并传入AddReferencedAssembly方法中。具体代码如下:

            try
{
//复制程序集
File.Copy(dllFile.FullName, newDllFile.FullName, true);
}
catch
{
//在某些情况下会出现"正由另一进程使用,因此该进程无法访问该文件"错误,所以先重命名再复制
File.Move(newDllFile.FullName, newDllFile.FullName + Guid.NewGuid().ToString("N") + ".locked");
File.Copy(dllFile.FullName, newDllFile.FullName, true);
}
//加载程序集的副本到应用程序中
Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(newDllFile.FullName));
//将程序集添加到当前应用程序域
BuildManager.AddReferencedAssembly(assembly);

不过这时又产生一个坑:此副本复制到哪个目录呢?我们期望这个副本能够保存下来,不必每次应用程序启动都要复制一次,熟悉asp.net编译的园友们估计都会想到,这个目录就是应用程序域的DynamicDirectory目录,一般情况下这个目录位于C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root\15a300ab\6af0b19a类似的目录,截图如下:

不过这时又产生了一个坑(哎,步步有坑呀!):权限问题,就是只有在Full Trust级别下才有操作此目录的权限,而在Medium Trust级别下我们没有操作此目录的权限。信任级别的MSDN解释如下:

针对这个坑我们只能根据不同的级别复制到不同的目录,Full Trust级别时复制到DynamicDirectory目录,Medium Trust级别时复制到一个影子目录(/Plugins/bin)代码如下:

            DirectoryInfo copyFolder;
//根据当前的信任级别设置复制目录
if (WebHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)//非完全信任级别
{
//shadowFolder就是"/Plugins/bin"影子目录
copyFolder = shadowFolder;
}
else//完全信任级别
{
copyFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);
}

在跳过上面众多坑后,我们终于长舒一口气,来到胜利的彼岸:插件的程序集文件能够正确加载了。下面附上插件加载的完整代码:

using System;
using System.IO;
using System.Web;
using System.Reflection;
using System.Web.Compilation;
using System.Collections.Generic; [assembly: PreApplicationStartMethod(typeof(BrnShop.Core.BSPPlugin), "Load")]
namespace BrnShop.Core
{
/// <summary>
/// BrnShop插件管理类
/// </summary>
public class BSPPlugin
{
private static object _locker = new object();//锁对象 private static string _installedfilepath = "/App_Data/InstalledPlugin.config";//插件安装文件
private static string _pluginfolderpath = "/Plugins";//插件目录
private static string _shadowfolderpath = "/Plugins/bin";//插件影子目录 private static List<PluginInfo> _oauthpluginlist = new List<PluginInfo>();//开放授权插件列表
private static List<PluginInfo> _paypluginlist = new List<PluginInfo>();//支付插件列表
private static List<PluginInfo> _shippluginlist = new List<PluginInfo>();//配送插件列表
private static List<PluginInfo> _uninstalledpluginlist = new List<PluginInfo>();//未安装插件列表 /// <summary>
/// 开放授权插件列表
/// </summary>
public static List<PluginInfo> OAuthPluginList
{
get { return _oauthpluginlist; }
}
/// <summary>
/// 支付插件列表
/// </summary>
public static List<PluginInfo> PayPluginList
{
get { return _paypluginlist; }
}
/// <summary>
/// 配送插件列表
/// </summary>
public static List<PluginInfo> ShipPluginList
{
get { return _shippluginlist; }
}
/// <summary>
/// 未安装插件列表
/// </summary>
public static List<PluginInfo> UnInstalledPluginList
{
get { return _uninstalledpluginlist; }
} /// <summary>
/// 加载插件程序集到应用程序域中
/// </summary>
public static void Load()
{
try
{
//插件目录
DirectoryInfo pluginFolder = new DirectoryInfo(IOHelper.GetMapPath(_pluginfolderpath));
if (!pluginFolder.Exists)
pluginFolder.Create();
//插件bin目录
DirectoryInfo shadowFolder = new DirectoryInfo(IOHelper.GetMapPath(_shadowfolderpath));
if (!shadowFolder.Exists)
{
shadowFolder.Create();
}
else
{
//清空影子复制目录中的dll文件
foreach (FileInfo fileInfo in shadowFolder.GetFiles())
{
fileInfo.Delete();
}
} //获得安装的插件系统名称列表
List<string> installedPluginSystemNameList = GetInstalledPluginSystemNameList();
//获得全部插件
List<KeyValuePair<FileInfo, PluginInfo>> allPluginFileAndInfo = GetAllPluginFileAndInfo(pluginFolder);
foreach (KeyValuePair<FileInfo, PluginInfo> fileAndInfo in allPluginFileAndInfo)
{
FileInfo pluginFile = fileAndInfo.Key;
PluginInfo pluginInfo = fileAndInfo.Value; if (String.IsNullOrWhiteSpace(pluginInfo.SystemName))
throw new BSPException(string.Format("插件'{0}'没有\"systemName\", 请输入一个唯一的\"systemName\"", pluginFile.FullName));
if (pluginInfo.Type < 0 || pluginInfo.Type > 2)
throw new BSPException(string.Format("插件'{0}'不属于任何一种类型, 请输入正确的的\"type\"", pluginFile.FullName)); //加载插件dll文件
FileInfo[] dllFiles = pluginFile.Directory.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
foreach (FileInfo dllFile in dllFiles)
{
//部署dll文件
DeployDllFile(dllFile, shadowFolder);
} if (IsInstalledlPlugin(pluginInfo.SystemName, installedPluginSystemNameList))//安装的插件
{
//根据插件类型将插件添加到相应列表
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Add(pluginInfo);
break;
case 1:
_paypluginlist.Add(pluginInfo);
break;
case 2:
_shippluginlist.Add(pluginInfo);
break;
}
}
else//未安装的插件
{
_uninstalledpluginlist.Add(pluginInfo);
}
}
}
catch (Exception ex)
{
throw new BSPException("加载BrnShop插件时出错", ex);
}
} /// <summary>
/// 安装插件
/// </summary>
/// <param name="systemName">插件系统名称</param>
public static void Install(string systemName)
{
lock (_locker)
{
if (string.IsNullOrWhiteSpace(systemName))
return; //在未安装的插件列表中获得对应插件
PluginInfo pluginInfo = _uninstalledpluginlist.Find(x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase)); //当插件为空时直接返回
if (pluginInfo == null)
return; //当插件不为空时将插件添加到相应列表
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Add(pluginInfo);
_oauthpluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 1:
_paypluginlist.Add(pluginInfo);
_paypluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 2:
_shippluginlist.Add(pluginInfo);
_shippluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
} //在未安装的插件列表中移除对应插件
_uninstalledpluginlist.Remove(pluginInfo); //将新安装的插件保存到安装的插件列表中
List<string> installedPluginSystemNameList = GetInstalledPluginSystemNameList();
installedPluginSystemNameList.Add(pluginInfo.SystemName);
SaveInstalledPluginSystemNameList(installedPluginSystemNameList);
}
} /// <summary>
/// 卸载插件
/// </summary>
/// <param name="systemName">插件系统名称</param>
public static void Uninstall(string systemName)
{
lock (_locker)
{
if (string.IsNullOrEmpty(systemName))
return; PluginInfo pluginInfo = null;
Predicate<PluginInfo> condition = x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase);
pluginInfo = _oauthpluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _paypluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _shippluginlist.Find(condition); //当插件为空时直接返回
if (pluginInfo == null)
return; //根据插件类型移除对应插件
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Remove(pluginInfo);
break;
case 1:
_paypluginlist.Remove(pluginInfo);
break;
case 2:
_shippluginlist.Remove(pluginInfo);
break;
} //将插件添加到未安装插件列表
_uninstalledpluginlist.Add(pluginInfo); //将卸载的插件从安装的插件列表中移除
List<string> installedPluginSystemNameList = GetInstalledPluginSystemNameList();
installedPluginSystemNameList.Remove(pluginInfo.SystemName);
SaveInstalledPluginSystemNameList(installedPluginSystemNameList);
}
} /// <summary>
/// 编辑插件信息
/// </summary>
/// <param name="systemName">插件系统名称</param>
/// <param name="friendlyName">插件友好名称</param>
/// <param name="description">插件描述</param>
/// <param name="displayOrder">插件排序</param>
public static void Edit(string systemName, string friendlyName, string description, int displayOrder)
{
lock (_locker)
{
bool isInstalled = true;//是否安装
PluginInfo pluginInfo = null;
Predicate<PluginInfo> condition = x => x.SystemName.Equals(systemName, StringComparison.InvariantCultureIgnoreCase); pluginInfo = _oauthpluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _paypluginlist.Find(condition);
if (pluginInfo == null)
pluginInfo = _shippluginlist.Find(condition); //当插件为空时直接返回
if (pluginInfo == null)
{
pluginInfo = _uninstalledpluginlist.Find(condition); ;
//当插件为空时直接返回
if (pluginInfo == null)
return;
else
isInstalled = false;
} pluginInfo.FriendlyName = friendlyName;
pluginInfo.Description = description;
pluginInfo.DisplayOrder = displayOrder; //将插件信息持久化到对应文件中
IOHelper.SerializeToXml(pluginInfo, IOHelper.GetMapPath("/Plugins/" + pluginInfo.Folder + "/PluginInfo.config")); //插件列表重新排序
if (isInstalled)
{
switch (pluginInfo.Type)
{
case 0:
_oauthpluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 1:
_paypluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
case 2:
_shippluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
break;
}
}
else
{
_uninstalledpluginlist.Sort((first, next) => first.DisplayOrder.CompareTo(next.DisplayOrder));
} }
} /// <summary>
/// 获得安装的插件系统名称列表
/// </summary>
private static List<string> GetInstalledPluginSystemNameList()
{
return (List<string>)IOHelper.DeserializeFromXML(typeof(List<string>), IOHelper.GetMapPath(_installedfilepath));
} /// <summary>
/// 保存安装的插件系统名称列表
/// </summary>
/// <param name="installedPluginSystemNameList">安装的插件系统名称列表</param>
private static void SaveInstalledPluginSystemNameList(List<string> installedPluginSystemNameList)
{
IOHelper.SerializeToXml(installedPluginSystemNameList, IOHelper.GetMapPath(_installedfilepath));
} /// <summary>
/// 获得全部插件
/// </summary>
/// <param name="pluginFolder">插件目录</param>
/// <returns></returns>
private static List<KeyValuePair<FileInfo, PluginInfo>> GetAllPluginFileAndInfo(DirectoryInfo pluginFolder)
{
List<KeyValuePair<FileInfo, PluginInfo>> list = new List<KeyValuePair<FileInfo, PluginInfo>>();
FileInfo[] PluginInfoes = pluginFolder.GetFiles("PluginInfo.config", SearchOption.AllDirectories);
Type pluginType = typeof(PluginInfo);
foreach (FileInfo file in PluginInfoes)
{
PluginInfo info = (PluginInfo)IOHelper.DeserializeFromXML(pluginType, file.FullName);
list.Add(new KeyValuePair<FileInfo, PluginInfo>(file, info));
} list.Sort((firstPair, nextPair) => firstPair.Value.DisplayOrder.CompareTo(nextPair.Value.DisplayOrder));
return list;
} /// <summary>
/// 判断插件是否已经安装
/// </summary>
/// <param name="systemName">插件系统名称</param>
/// <param name="installedPluginSystemNameList">安装的插件系统名称列表</param>
/// <returns> </returns>
private static bool IsInstalledlPlugin(string systemName, List<string> installedPluginSystemNameList)
{
foreach (string name in installedPluginSystemNameList)
{
if (name.Equals(systemName, StringComparison.InvariantCultureIgnoreCase))
return true;
}
return false;
} /// <summary>
/// 部署程序集
/// </summary>
/// <param name="dllFile">插件程序集文件</param>
/// <param name="shadowFolder">/Plugins/bin目录</param>
private static void DeployDllFile(FileInfo dllFile, DirectoryInfo shadowFolder)
{
DirectoryInfo copyFolder;
//根据当前的信任级别设置复制目录
if (WebHelper.GetTrustLevel() != AspNetHostingPermissionLevel.Unrestricted)//非完全信任级别
{
copyFolder = shadowFolder;
}
else//完全信任级别
{
copyFolder = new DirectoryInfo(AppDomain.CurrentDomain.DynamicDirectory);
} FileInfo newDllFile = new FileInfo(copyFolder.FullName + "\\" + dllFile.Name);
try
{
File.Copy(dllFile.FullName, newDllFile.FullName, true);
}
catch
{
//在某些情况下会出现"正由另一进程使用,因此该进程无法访问该文件"错误,所以先重命名再复制
File.Move(newDllFile.FullName, newDllFile.FullName + Guid.NewGuid().ToString("N") + ".locked");
File.Copy(dllFile.FullName, newDllFile.FullName, true);
} Assembly assembly = Assembly.Load(AssemblyName.GetAssemblyName(newDllFile.FullName));
//将程序集添加到当前应用程序域
BuildManager.AddReferencedAssembly(assembly);
}
}
}

   在解决了插件程序集的加载问题后我们再来解决视图文件的路径和编译问题,由于插件目录不在传统View文件夹中,所以我们像普通视图那样返回插件路径,我们需要使用根目录路径来返回视图文件路径,以支付宝插件为例:

        /// <summary>
/// 配置
/// </summary>
[HttpGet]
[ChildActionOnly]
public ActionResult Config()
{
ConfigModel model = new ConfigModel(); model.Partner = PluginUtils.GetPluginSet().Partner;
model.Key = PluginUtils.GetPluginSet().Key;
model.Seller = PluginUtils.GetPluginSet().Seller;
model.AllowRecharge = PluginUtils.GetPluginSet().AllowRecharge; //插件视图文件路径必须以"~"开头
return View("~/Plugins/BrnShop.PayPlugin.Alipay/Views/AdminAlipay/Config.cshtml", model);
}

通过使用根目录路径,我们可以忽视一切路由匹配问题了。再来说说视图的编译问题,为了能够正确指导asp.net编译视图文件,我们需要在“/Plugins”文件夹(此文件夹中的web.cong能够覆盖所有插件目录)中添加一个web.config文件,并指定编译要求如下:

  <system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Text" />
<add namespace="System.Data" />
<add namespace="System.Collections"/>
<add namespace="System.Collections.Generic"/>
<add namespace="BrnShop.Core" />
<add namespace="BrnShop.Services" />
<add namespace="BrnShop.Web.Framework" />
<add namespace="BrnShop.Web.Models" />
</namespaces>
</pages>
</system.web.webPages.razor>

这样我们的插件视图文件就能够正确编译了。

  现在只剩下插件的部署问题了。如果是手动部署我们只需要将插件目录及其文件复制到"/Plugins"目录中就可以。如果是使用vs自动部署我们需要做以下几步配置:

第一步配置插件程序集的输出路径,通过在项目上点击右键选择属性进入,具体配置如下:

现在你生成一下解决方案就会发现插件程序集已经到"/Plugins"文件夹中。

第二步是筛选程序集,就是只输出插件程序集,其它的程序集(包括系统自带和引用的程序集)不输出。具体配置如下如图:

最后一步是输出内容文件,例如视图文件,具体配置如下图:

到此BrnShop的插件能够正常工作了。

  PS:其实asp.net mvc插件的实现方式有许多种,大家可以google一下就会发现。而BrnShop之所以采用这种插件机制其实是服从于程序整体框架设计理念的。我们在设计BrnShop框架之初确定的框架设计理念是:在不损失框架的扩展性,稳定性和性能的条件下,一切从简,直接,一目了然,返璞归真。最后奉上本人的框架设计理念(如不喜,请轻喷!):

BrnShop开源网上商城第三讲:插件的工作机制的更多相关文章

  1. BrnShop开源网上商城第一讲:架构设计

    首先在此感谢大家对BrnShop项目的支持和鼓励!我们在发布BrnShop以前曾推测项目会受到不少园友的支持,但没想到园友们的支持大大超过我们的预测.4天6000次浏览,140个推荐,170个评论,8 ...

  2. BrnShop开源网上商城第四讲:自定义插件

    重要通知:BrnShop企业版NOSQL设计(基于Redis)已经开源!源码内置于最新版的BrnShop中,感兴趣的园友可以去下载来看看.官网地址:www.brnshop.com. 好了现在进入今天的 ...

  3. BrnShop开源网上商城第六讲:扩展视图功能

    在正式讲解扩展视图功能以前,我们有必要把视图的工作原理简单说明下.任何一个视图都会被翻译成一个c#类,并保存到指定的位置,然后被编译.这也就是为什么能在视图中包含c#代码片段的原因.下面我们通过一个项 ...

  4. BrnShop开源网上商城第五讲:自定义视图引擎

    今天这篇博文主要讲解自定义视图引擎,大家都知道在asp.net mvc框架中默认自带一个Razor视图引擎,除此之外我们也可以自定义自己的视图引擎,只需要实现IViewEngine接口,接口定义如下: ...

  5. BrnShop开源网上商城第二讲:ASP.NET MVC框架

    在团队设计BrnShop的web项目之初,我们碰到了两个问题,第一个是数据的复用和传递,第二个是大mvc框架和小mvc框架的选择.下面我依次来说明下. 首先是数据的复用和传递:对于BrnShop的每一 ...

  6. [原][资料整理][osg]osgDB文件读取插件,工作机制,支持格式,自定义插件

    参考: osgPlugins相关 osg读取文件的原理(插件工作机制) 当使用osgDB读取文件时,会自动根据文件的扩展名来到插件目录中寻找相应的插件,来实现. 比如: osgviewer cow.o ...

  7. Python 开源网上商城项目

    django-oscar  https://github.com/django-oscar/django-oscar#screenshots django-shop  https://github.c ...

  8. 开源Asp.Net MVC网上商城BrnShop

    开源Asp.Net MVC网上商城BrnShop正式发布,提供源码下载 BrnShop网上商城是以Asp.Net mvc3为基础开发的网上商城,源代码完全开源(企业版的源代码目前还没有完全整理完成,一 ...

  9. 重磅来袭,开源Asp.Net MVC网上商城BrnShop正式发布,提供源码下载(转)

    BrnShop网上商城是以Asp.Net mvc3为基础开发的网上商城,源代码完全开源(企业版的源代码目前还没有完全整理完成,一旦整理完成也全部开源). 啥话也不说了,直接上源码:下载源码(由于公司服 ...

随机推荐

  1. Linux内核中的通用双向循环链表

    开发中接触Linux越来越多,休息放松之余,免不了翻看翻看神秘的Linux的内核.看到双向链表时,觉得挺有意思的,此文记下. 作为众多基础数据结构中的一员,双向循环链表在各种“教科书”中的实现是相当的 ...

  2. 一个基本jquery的评论留言模块

    <div class="productDiscuss"> <div class="title"><span class=" ...

  3. Windows Linux HackMacintosh

    我想把Windows Linux HackMacintosh三类系统融入到一台笔记本上的神经病应该不多. 我的电脑就一个SATA硬盘,BIOS还不是EFI的.一共同时安装了Windows 8.1.Op ...

  4. power designer

    概述 Power Designer 是Sybase公司的CASE工具集,使用它可以方便地对管理信息系统进行分析设计,他几乎包括了数据库模型设计的全过程.利用Power Designer可以制作数据流程 ...

  5. C# 多线程基础

    多线程 无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其 他工作.要使应用程序能够快速响应用户操作,同时在用户 ...

  6. UBUNTU下如何开启SSHD服务

    “人生本来就有很多事是徒劳无功的.”就像青春,虽然徒劳,却在人的心里住得最久,挥之不去,怀念那段青涩年华,怀念它的... sudo apt-get install openssh-server Ubu ...

  7. ECSHOP 订单状态 记录

    记录订单状态 order_status /* 订单状态 */ define(‘OS_UNCONFIRMED’,            0); // 未确认 define(‘OS_CONFIRMED’, ...

  8. wpf单实例运行

    默认情况下我们可以打开一个应用程序多个实例,例如你双击一个exe多次.当然有些时候这么做会带来很多好处,但是有时我们又不希望这么做,要避免这个问题其实很简单,同WinForm中单实例运行一个应用是一样 ...

  9. hdu 1695 GCD 莫比乌斯反演入门

    GCD 题意:输入5个数a,b,c,d,k;(a = c = 1, 0 < b,d,k <= 100000);问有多少对a <= p <= b, c <= q <= ...

  10. ASP.NET MVC NHibernate 整合

    请注明转载地址:http://www.cnblogs.com/arhat 在整合这三个技术之前,首先得说明一下整合的步骤,俗话说汗要一口一口吃,事要一件一件做.同理这个三个技术也是.那么在整合之前,需 ...