Advanced Basics:在 Visual Basic .NET 中记住用户信息

  • 来源: msdn 作者: 若水   2008-04-01/10:45
  • Duncan Mackenzie

    下载本文的代码: AdvancedBasics0504.exe (130KB)

    很多应用程序需要存储要在会话之间持续保持的特定于用户的设置。但是,如何在基于 Microsoft® .NET Framework 的应用程序中保存和恢复这些设置呢?找到正确的答案并非易事。您可以在新闻组和论坛中找到各种各样的解决方案,但在已发布的解决方案中,只有一小部分阐明了处理这一需求的正确方式。

    首先,让我们定义应用程序可能具有的两种主要类型的设置:应用程序设置和用户设置。应用程序设置随应用程序一起交付,影响每个用户,并且根据预期,只在应用程序的总体行为需要更改时,才会作为管理任务进行修改。

    请考虑一个商业人力资源应用程序。当该应用程序部署到特定公司内部时,应用程序设置会被自定义,以指定用户将要连接到的数据库服务器、在哪里找到要添加到 UI 中的公司徽标,以及其他信息。这些都是应用程序设置,而不是用户设置,并且可以直接安装到与程序本身相同的目录中。这些设置很可能安装到只有管理员有权在其中编辑或删除文件的目录(例如,c:\Program Files\)中,这有助于加深这些设置绝对不是用户设置这一印象。应用程序应该只需要读取这些设置,而不去更改它们。

    用户设置是特定于用户的首选项,并且需要可从代码中进行编辑。对于一个虚构的 HR 应用程序而言,用户设置可能包括应用程序窗口的大小/位置、应用程序的启动状态,甚至可能包括快速更改的信息 — 如所查看的最后五个雇员(用于那个有用的“File”-“Recently Viewed Items…”菜单选项)。上述所有设置都需要在应用程序会话启动时检索,在应用程序运行过程中根据需要进行编辑,持久保留到磁盘中以便在用户下一次启动程序时可用。

    设置和检索应用程序设置


    .NET Framework SDK 中对于应用程序配置文件的创建进行了比较好的说明,但是我想最好在此为您提供简要的概述。第一步是在项目内部创建一个新的设置文件,方法是从“Project”菜单中选择“Add New Item”,然后选择“Application Configuration File”选项。使该文件保留其默认名称 App.Config;当您生成项目时,该文件将移动至输出目录中,并且被适当重命名 (yourappname.exe.config)。在该新文件内部,您可以定义自定义配置节(使用您的数据所需要的任何结构),或者只是通过使用可以容纳名称-值对列表的 appSettings 块来添加您自己的设置。存储几个简单的值可以产生如下所示的文件:

    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
       <appSettings>
           <add key="Company Name" value="Java Jitters" />
           <add key="Database Server" value="BigSQLServer" />
       </appSettings>    
    </configuration>
    

    通过 System.Configuration 命名空间的类和方法,可以容易地检索 appSettings 节中存储的值。您甚至不需要亲自加载该文件;如果该文件被正确命名并且位于正确的位置(应用程序目录中的 yourappname.exe.config),则它会被自动发现和加载。

    Dim companyName As String _
       = ConfigurationSettings.AppSettings("Company Name")
    

    正如我提到的那样,应用程序配置文件的创建和访问得到了良好的说明(请在 .NET Framework SDK 中搜索“System.Configuration”),因此我不会在此进一步讨论该主题。

    返回页首返回页首

    为什么不应该使用 App.Config?

    我经常在论坛或新闻组中看到以下问题:如何向我的 app.config 中写入?我们可以很容易地了解开发人员是如何提出该问题的。当在 .NET 内部写入的开发人员使用了应用程序配置文件及其简易的编程界面之后,他们想知道为什么不能使用同一个文件来存储用户设置。为什么有两种不同的配置应用程序的方法?稍后,他们发现 System.Configuration 命名空间没有提供任何用于向 app.config 文件中进行写入的方法。这本来应该是一种警示,但对于意志坚定的编程人员而言,这只是一个小小的障碍。随后,他们通常会搜索 Web,张贴新闻组问题,然后使用 XmlDocument 直接加载和编辑,甚至求助于常规文件 IO。

    这不是一个好主意。您的应用程序不应该向该文件中写入,这至少有下列三个原因:

    权限?安装、移除和编辑应用程序文件是一项管理任务,要求具有与普通用户不同的权限。在 Windows® XP 中,所示的一种方法是通过 Program Files 目录上的默认权限:对于用户而言是只读,但对于 Administrators 组的成员而言是读/写。如果您的应用程序安装到 Program Files 目录中,并且使用它的 app.config 文件来存储用户首选项,则保存对那些设置所做更改的操作需要本地计算机上的管理员权限。尽管很多开发人员(和用户)以他们自己计算机的管理员身份运行,但在大多数企业环境中,这肯定是一个问题。请注意,您不应该允许对 Program Files 进行自由访问;关于限制写入访问有一些很好的理由。

    没有用户隔离?应用程序配置文件适用于计算机上的每个用户,因此如果多个用户共享同一台计算机,则每个人都将共享同一组选项。其后果可能只是令人烦恼(一个用户喜欢在启动时将窗口最大化,而另一个用户则希望将其窗口化),也可能导致极端的功能和安全问题(例如,应用程序需要您的 POP3 服务器信息,于是该计算机的每个用户都可以自由地读取该信息)。即使您的当前用户群全都是一个用户使用一台计算机,您也可以断定并不能保证总是如此;值得一提的是,甚至该系统中的 Guest 帐户也可能具有对 Program Files 目录的读取访问权限(因而具有对应用程序设置文件的访问权限),从而公开了该文件中存储的任何可能具有敏感性的信息。

    app.config 文件是应用程序安装的一部分?该文件是应用程序本身的一部分,它将由新的安装进行重写并可能在应用程序卸载时移除。该文件中存储的任何用户数据都会丢失,取决于存储数据的类型的不同,这可能非常严重。#p#分页标题#e#

    由于上述原因以及可能存在的其他一些原因,您不应该使用 app.config 文件来存储用户设置;但请不要担心,保存用户的首选项并不像您想象的那样困难。

    返回页首返回页首

    创建设置类


    轻松保存特定于用户设置的关键在于,定义一个表示所有相关数据的类。该类将是您与所有用户设置之间的接口,而且,一旦您确定您需要存储用户数据,则您首先应该生成该类。在此,我将使用可以从磁盘打开文件的简单的 Windows 窗体;用户设置文件将存储用户的背景色选择、上一个窗口位置以及最近打开文件的列表。通过在类中描述所有这些数据,我得到了如图 1 所示的定义。

    BackgroundColor 属性比较复杂,因为我希望将颜色保存为 HTML 表示形式,但若非如此,则该类只是一组简单的属性而已。我将最近打开文件的列表实现为 StringCollection 而不是数组,因为向集合中添加新文件更为容易一些;但这两种方法都是可行的。

    返回页首返回页首

    将设置序列化到磁盘


    一旦您定义了类,则下一步就是编写代码,以便在磁盘上的文件中读/写该信息。使用 System.Xml.XmlReader 类和 System.Xml.XmlWriter 类生成和分析 XML 文件是可以的,但只使用 XML 序列化要稍微简单一些。序列化负责完成将设置类的实例转换为 XML 所需的所有工作,并且还知道如何将其转换回来。序列化方法非常可靠,它能够处理值丢失、添加新属性(可能发生在应用程序的将来版本中)以及其他问题,因此它是一个很好的选择。您可以通过 System.Xml.Serialization.XmlSerializer 类的实例将设置类保存到磁盘,并且还可以使用同一个类从磁盘中反序列化您的数据。这两个操作的基本代码如图 2 所示。

    图 2只是一个简单的示例;我喜欢将真正的保存/加载代码作为 Shared(C# 类型为静态)和 Instance 方法的组合直接放到我的设置类中。我提供了几个不同的选项、一个用于从所提供的流中进行加载的例程、一个负责自行获取流的选项以及用于将设置重新保存到磁盘的相同选项。

    序列化或反序列化的操作只需要两个对象。首先,您需要 XmlSerializer 类的实例;您可以通过将您的类的类型传递给以下构造函数来创建该实例:

    Dim xs As New XmlSerializer(GetType(Settings))
    

    您还需要一个流、TextWriter 或 XmlWriter 实例来表示序列化的输出,或者一个流、TextReader 或 XmlReader 来表示要反序列化的输入。

    Dim sw As New IO.StreamWriter("C:\settings.xml")
    

    将您的设置保存到磁盘上的哪个位置是另外一个重要决定。我建议您选择用户的应用程序数据目录或独立存储的特定于应用程序的区域。第一个选择会将设置存储到“C:\Documents and Settings\userid\Application Data\”(驱动器号和确切的位置可能因计算机而异)下的某个位置,这对于设置而言非常理想,因为它是用户可写的,并且与大多数其他应用程序选项的存储一致。您可以使用 Application.UserAppDataPath 或 System.Environment.GetFolderPath 获得该位置的完整路径。Application 方法将返回一个如下所示的路径,它包含您的 AssemblyInfo.vb 文件中的 AssemblyCompany、AssemblyProduct 和 AssemblyVersion 值:

    C:\Documents and Settings\\Application Data\
    company\product\version
    

    该路径易于获得,但它是一个特定于版本的位置。作为一个替代方案,从 GetFolderPath 中返回的值不包含您的公司和产品名称信息:

    C:\Documents and Settings\userid\Application Data
    

    如果您采用第一个位置,则您的产品的任何将来版本(假设您递增版本号)在启动时都好像它不具有现有的用户首选项,这对于某些应用程序而言会是一个问题;如果您采用第二个位置,则您的文件可能与其他应用程序的文件发生冲突。我喜欢将我的文件保存在比第一个位置高一层的位置中,以使其成为独立于版本的设置文件。可以采用几种不同的方式来获得我期望的路径(例如,获得 UserAppDataPath 的父对象),但以下代码片段可以为我很好地工作:

    Dim filePath As String
    filePath = Path.Combine(GetFolderPath( _
           SpecialFolder.ApplicationData), Application.CompanyName)
    filePath = Path.Combine(filePath, Application.ProductName)
    filePath = Path.Combine(filePath, FILENAME)
    Dim settingsFile As New FileStream(filePath, FileMode.Create)
    

    相反,您可以执行标准的字符串串联(或者使用 StringBuilder),但每当我要使用路径信息时,我总是喜欢使用 Path.Combine。

    返回页首返回页首

    独立存储


    另一个存储用户设置的理想位置是独立存储。独立存储是对用户的应用程序数据区域的抽象,它提供与当前用户和正在运行的应用程序的某个方面相联系的唯一文件存储区域。我通常使用与用户和当前程序集本体相联系的独立存储,并且使用 System.IO.IsolatedStorage 中的类来打开到该存储区域内部的新文件或现有文件的流:

    Dim ifs As IsolatedStorageFile
    ifs = IsolatedStorageFile.GetUserStoreForAssembly()
    Dim settingsFile As New IsolatedStorageFileStream( _
       FILENAME, FileMode.Create, ifs)
    Return settingsFile
    

    如果您愿意浏览“C:\Documents and Settings\userid\Local Settings\Application Data\IsolatedStorage”(位置可能不同)下的各个文件夹,则您可以在磁盘上找到这一文件。尽管它肯定没有被完全隐藏,但与直接写到普通文件位置的文件相比,用户不太容易发现该文件。#p#分页标题#e#

    对于我的大多数应用程序,我都使用应用程序数据位置来保存我的设置文件。在某些有限信任情况(此时,常规文件访问受到限制)下,独立存储可以更好地工作。为了向应用程序数据位置写入,您的应用程序需要有 FileIOPermission;但要在独立存储内部打开文件流,则需要有 IsolatedStorageFilePermission。在 .NET Framework 1.1 中的默认安全配置中,只有具有“完全信任”的应用程序才具有文件 IO 权限,而即使是从 Internet 站点中启动的应用程序也具有向独立存储中写入的权限。

    出于这里讨论的与安全有关的原因,有一些应用程序需要使用独立存储,而其他应用程序则无所谓。为了使我的设置代码具有更高的可重用性,我添加了一个布尔型常量,该常量在两种类型的文件存储之间切换值:

    Public Const USE_ISOLATED_STORAGE As Boolean = False
    
    返回页首返回页首

    在应用程序内部保存和加载


    在应用程序的主窗体或函数中,您需要在应用程序启动时从磁盘中加载设置文件,然后在该应用程序终止时持久保存该文件(只有当它已更改时,才真正需要这样做)。您需要的所有功能都直接附加到您的类中,因此您真正需要完成的工作是调用那些函数,并且使用该类根据需要编辑用户的首选项。在简单的应用程序(只有一个主窗体)中,仅用几行代码就可以非常轻松地完成该工作。

    要在启动时加载您的设置,可以使用如图 3 所示的代码。然后,在保存时,可以使用如图 4 所示的代码。

    您还可以在用户的选项更改时(例如,在向用户显示选项对话框之后),只是调用 SaveSettings;或者,您可以使用诸如“dirty”之类的附加属性来跟踪它在修改后的状态。如果您决定添加附加属性(您可能不希望将其序列化到磁盘或进行反序列化),则应该用 XmlIgnore 属性标记它。该属性通知 XmlSerialization 过程忽略这一特定属性;它不会被写入磁盘上的设置文件中。在我的示例中,我使用 XmlIgnore 来指示 LastWindowBounds 属性不应该序列化。该属性表示一个真正的值,即指示主应用程序窗体的位置和大小的矩形;但是,该值的两个部分(位置和大小)还通过它们各自的属性加以公开,并且没有理由将相同数据多次放到设置文件中:

    <Xml.Serialization.XmlIgnore()> _
    Public Property LastWindowBounds() As Rectangle
        Get
            Return m_LastWindowBounds
        End Get
        Set(ByVal Value As Rectangle)
            m_LastWindowBounds = Value
        End Set
    End Property
    

    在您使自己的应用程序正确工作(包括编辑、加载和保存您的设置)之后,您可能希望添加一些与设置有关的附加功能。两个可能有用的功能是:将应用程序的设置重置为默认设置(可以从 app.config 文件中加载),以及将设置保存到任意文件位置和从该位置加载设置。我已经将这两个选择添加到我的示例应用程序中,以供您检验。

    返回页首返回页首

    Visual Basic2005 如何?


    本专栏已经讨论了在哪里存储用户设置、为什么不应该使用与应用程序设置相同的方法,以及如何创建自定义设置类;多数这些问题将在即将发布的 Visual Basic® 中得以处理。在 Visual Basic2005 中,有一个内置到 Visual Studio® 中的设置设计器(参见图 5),通过它可以设置应用程序和用户级别设置列表。


    图 5 Visual Basic 设置设计器


    在为您的项目配置了该信息之后,您就可以轻松地通过 My.Settings 类来设置和检索这些值:

    Me.Text = My.Settings.CompanyName
    If My.Settings.StartMaximized Then
       Me.WindowState = FormWindowState.Maximized
    End If
    

    我不会对这一新功能进行更多的讨论,但如果您对此感兴趣,我建议您阅读 MSDN® Online 上由 Emad Ibrahim 撰写的文章“Using My.Settings in Visual Basic 2005”。


    评论 {{userinfo.comments}}

    {{money}}

    {{question.question}}

    A {{question.A}}
    B {{question.B}}
    C {{question.C}}
    D {{question.D}}
    提交

    驱动号 更多