模块安装程序






3.94/5 (6投票s)
一个 DotNetNuke 风格的框架,允许您上传模块,以便最终用户可以轻松地更新和增强 Web 应用程序。
为什么要使用模块安装程序?
DotNetNuke 最好的优点之一是它允许您简单地上传一个包含“模块包”的 .zip 文件,即可立即为您的网站添加功能。如果您需要为网站添加留言板,只需上传并配置即可。您无需自己编写代码。您上传的是别人创建的代码,它将直接可用。
SilverlightDesktop.net 项目需要一种方法来允许开发人员为该框架创建模块,我们借鉴了成熟的 DotNetNuke 方法。我们将代码在此处发布为一个非 Silverlight 应用程序,因为我们认为该代码可能对他人有用。
应用程序
该应用程序使用了我们创建的另一个 CodeProject 应用程序的代码,一个基于 Web 的应用程序配置向导,它提供了一个向导,允许您轻松地配置应用程序。之后,我们将此代码用于 SilverlightDesktop.net 项目。
首先,您需要创建一个 SQL 数据库
配置 Web 应用程序
导航到网站并使用安装程序配置应用程序
应用程序设置完成后,点击链接导航到模块管理。
上传 HelloWorldModule.zip 文件(下载链接在本文章顶部)
点击 **继续** 链接后,页面将刷新,模块将被配置。
**HelloWorld** 模块将显示在主页的下拉列表中,点击 **加载模块** 按钮将动态地将模块注入页面并显示其内容。
模块包
对安装过程的探索始于模块包。这是一个 .zip 文件,包含程序、数据库脚本(.sql 脚本)和配置信息。
- HelloWorldModule.dll - 程序代码。
- uninstall.sql - 包含一个数据库脚本,当模块被卸载时,该脚本会删除所有数据库表。该脚本最初被放置在 Modules 表的 uninstall 字段中。当管理员点击 **删除** 按钮卸载模块时,它将被运行。
- module.config - 包含模块的配置信息。
- HelloWorld.ascx 和 HellowWorld.ascx.cs - 模块的其他程序文件。
- 01.00.00.sql - 在模块安装时运行的脚本,用于创建所需的数据库对象。
这些文件被压缩时,会保持它们安装时需要放置的相对位置。HelloWorldModule.dll 文件需要放置在“bin”目录中,因此在压缩时也会被放置在“bin”目录中。其余文件将放置在应用程序的根目录。请注意,.sql 和 .config 文件将在安装后被删除,不会保留在应用程序中。
Module.config 文件
module.config 文件具有以下格式:
<SilverlightDesktop>
<configversion>1.0</configversion>
<configtype>Module</configtype>
<modulename>HelloWorld</modulename>
<description>A simple module that says Hello World</description>
<assembly>HelloWorld.dll</assembly>
<version>01.00.00</version>
<removefiles>
<file>
<name>HelloWorld.ascx</name>
</file>
<file>
<name>HelloWorld.ascx.cs</name>
</file>
<file>
<name>bin\HelloWorldModule.dll</name>
</file>
</removefiles>
</SilverlightDesktop>
SilverlightDesktop
- 标识配置文件。configversion
- 标识配置文件的版本。这允许您稍后更改格式,但仍能处理旧格式的模块。configtype
- 这很重要,因为您可以为模块以外的其他元素(例如皮肤)拥有不同类型的配置文件。modulename
- 这是其他数据(例如 ModuleFiles 表中存储的文件名和路径)将使用的键名。description
- 这是在模块配置屏幕上显示的模块的描述。assembly
- 在 SilverlightDesktop.net 项目中,我们需要此项来指示要加载的第一个程序集。这对于普通的 ASP.NET 应用程序来说并非必需。version
- 这允许您指定模块的新版本。安装程序将此版本号与当前已安装的模块版本进行比较。它将阻止安装旧版本。如果是一个较新版本,它将升级模块。removefiles
- 允许您指定不再需要的文件。这些文件将在模块安装前被删除。此示例中的文件不需要删除,因为它们将被简单地覆盖。它们仅作为格式示例包含(删除它们也不会导致错误,因为它们将被替换)。
安装程序
ModuleAdmin.aspx.cs 文件包含安装模块的所有逻辑。首先,会创建一个临时目录,并将 .zip 文件解压缩到该目录中。SharpZipLib 用于解压缩文件。
string strZipFilename = File1.PostedFile.FileName;
strZipFilename = System.IO.Path.GetFileName(strZipFilename);
File1.PostedFile.SaveAs(strTempDirectory + strZipFilename);
UploadMessage.Add(String.Format("File saved to {0}",
strTempDirectory + strZipFilename));
UnzipFile(strTempDirectory + strZipFilename);
接下来,使用 **LINQ to XML** 读取配置文件并构建一个要删除文件的可能列表。
// Load the config file
XElement doc = XElement.Load(strTempDirectory + @"\Module.config");
string strconfigversion = doc.Element("configversion").Value;
string strconfigtype = doc.Element("configtype").Value;
string strmodulename = doc.Element("modulename").Value;
string strdescription = doc.Element("description").Value;
string strassembly = doc.Element("assembly").Value;
string strversion = doc.Element("version").Value;
// Build a list of files to remove
List<string> colFilesToRemove = new List<string>();
foreach (XElement Element in doc.Element("removefiles").Elements())
{
colFilesToRemove.Add(Element.Value);
}
colFilesToRemove.Sort();
使用 **LINQ to SQL** 来确定模块版本是否合适。
// Get the current module version if any
int intCurrentModuleVersion = 0;
var result = from Module in DataClassesDataContext.Modules
where Module.ModuleName.ToLower() == strmodulename.ToLower()
select Module.ModuleVersion;
intCurrentModuleVersion = (result.FirstOrDefault().ToString() == "") ?
intCurrentModuleVersion : result.FirstOrDefault();
int intModuleVersion = Convert.ToInt32(strversion.Replace(".", ""));
if (intModuleVersion <= intCurrentModuleVersion)
{
UploadMessage.Add(String.Format("Current module version is {0}. " +
"Installing module version is {1}. Aborting installation.",
intCurrentModuleVersion.ToString(), intModuleVersion.ToString()));
lbUploadMessage.DataSource = UploadMessage;
lbUploadMessage.DataBind();
return;
// Exit
}
else
{
UploadMessage.Add(String.Format("Current module version is {0}. " +
"Installing module version {1}.",
intCurrentModuleVersion.ToString(),
intModuleVersion.ToString()));
}
根据当前模块版本号(如果存在),会找到并执行相应的 .sql 脚本。LINQ to SQL 方法 “ExecuteCommand
” 允许您传递一个 .sql 字符串,该字符串将针对当前配置的数据源执行。
// Get a list of all .sql scripts
List<string> colSQLScripts =
Directory.GetFiles(strTempDirectory, "*.sql").ToList();
colSQLScripts.Sort();
foreach (string strFile in colSQLScripts)
{
string strFileName = Path.GetFileNameWithoutExtension(strFile);
if (strFileName.ToLower() != "uninstall")
{
int intVersion =
Convert.ToInt32(strFileName.Replace(".", ""));
if (intVersion <= intModuleVersion)
{
try
{
string strSqlScript = GetSQLScript(strFile);
DataClassesDataContext.ExecuteCommand(strSqlScript);
File.Delete(strFile);
UploadMessage.Add(String.Format("SQL Script processed: {0}", strFileName));
}
catch (Exception ex)
{
UploadMessage.Add(String.Format("SQL Script error " +
"in script: {0} - {1}", strFileName, ex.ToString()));
lbUploadMessage.DataSource = UploadMessage;
lbUploadMessage.DataBind();
return;
}
}
}
}
模块表将被更新。
// Delete record if it exists
Module ModuleEntry = (from Module in DataClassesDataContext.Modules
where Module.ModuleName.ToLower() == strmodulename.ToLower()
select Module).FirstOrDefault();
// // If the Module entry does not already exist, create it
if (ModuleEntry == null)
{
ModuleEntry = new Module();
}
ModuleEntry.AssemblyName = strmodulename;
ModuleEntry.ModuleDescription = strdescription;
ModuleEntry.ModuleName = strmodulename;
ModuleEntry.ModuleVersion = Convert.ToInt32(strversion.Replace(".", ""));
//Read and insert the uninstall script
if (File.Exists(strTempDirectory + "uninstall.sql"))
{
string strUninstall = GetSQLScript(strTempDirectory + "uninstall.sql");
ModuleEntry.uninstall = strUninstall;
}
// If the Module entry does not already exist insert it
if (ModuleEntry.ModuleID == 0)
{
DataClassesDataContext.Modules.InsertOnSubmit(ModuleEntry);
UploadMessage.Add(String.Format("Created Module entry {0}", strmodulename));
}
DataClassesDataContext.SubmitChanges();
清理不需要的文件和已处理但不会成为已安装包一部分的文件。
//Delete files
foreach (string strDeleteFile in colFilesToRemove)
{
File.Delete(strTempDirectory.Replace(@"\Temp", "") + strDeleteFile);
UploadMessage.Add(String.Format("Removed File: {0}", strDeleteFile));
}
//Delete the .zip, .config and uninstall files
File.Delete(strTempDirectory + strZipFilename);
File.Delete(strTempDirectory + "uninstall.sql");
File.Delete(strTempDirectory + "Module.config");
//Delete any file details in the database
var colModuleFiles = from ModuleFiles in DataClassesDataContext.ModuleFiles
where ModuleFiles.ModuleName.ToLower() == strmodulename.ToLower()
select ModuleFiles;
DataClassesDataContext.ModuleFiles.DeleteAllOnSubmit(colModuleFiles);
DataClassesDataContext.SubmitChanges();
剩余的文件将被添加到 **ModuleFiles** 表(以便在卸载模块时可以删除它们),并移动到其正确的位置。
//Add The Module File information to the database
List<string> colDirectories =
Directory.GetDirectories(strTempDirectory).ToList();
colDirectories.Add(strTempDirectory);
foreach (string strDirectory in colDirectories)
{
List<string> colFiles = Directory.GetFiles(strDirectory).ToList();
foreach (string strFile in colFiles)
{
ModuleFile objModuleFile = new ModuleFile();
objModuleFile.ModuleName = strmodulename.ToLower();
objModuleFile.FileNameAndPath = strDirectory.Replace(strTempDirectory,
"") + @"\" + Path.GetFileName(strFile);
DataClassesDataContext.ModuleFiles.InsertOnSubmit(objModuleFile);
DataClassesDataContext.SubmitChanges();
// Move the file to it's destination
File.Move(strFile, strFile.Replace(@"\Temp", ""));
}
}
直接使用 DotNetNuke
如果您需要可扩展框架的功能,您应该直接使用 DotNetNuke。但是,当这不是一个选项时,您可能会发现此代码可以提供一种方法,让最终用户轻松地更新和增强您的应用程序。
实现这样一个框架不仅可以提供一种方便的实现增强功能的方式,还可以让其他人有机会“扩展”您的应用程序。这增加了您的应用程序对最终用户的价值。