65.9K
CodeProject 正在变化。 阅读更多。
Home

模块安装程序

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.94/5 (6投票s)

2008 年 7 月 5 日

BSD

5分钟阅读

viewsIcon

24425

downloadIcon

395

一个 DotNetNuke 风格的框架,允许您上传模块,以便最终用户可以轻松地更新和增强 Web 应用程序。

HelloWorldModule_source_

为什么要使用模块安装程序?

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.ascxHellowWorld.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。但是,当这不是一个选项时,您可能会发现此代码可以提供一种方法,让最终用户轻松地更新和增强您的应用程序。

实现这样一个框架不仅可以提供一种方便的实现增强功能的方式,还可以让其他人有机会“扩展”您的应用程序。这增加了您的应用程序对最终用户的价值。

© . All rights reserved.