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

SQLite 用户、角色和配置文件提供程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (27投票s)

2008 年 9 月 9 日

CPOL

10分钟阅读

viewsIcon

231938

downloadIcon

4862

SQLite 的完整、可用于生产环境的用户、角色和配置文件提供程序。包含 SQL Server 和 SQLite 之间数据迁移的说明。

引言

这是 ASP.NET 2.0 的用户、角色和配置文件提供程序的实现。它功能齐全,实现了 Microsoft 定义的用户、角色和配置文件 API 的 100%。

这与随 Gallery Server Pro 一起发布的代码相同,因此您可以放心其生产就绪性。但是,如果您发现任何问题,请联系我,我会进行修正。

此代码旨在作为 Microsoft SQL Server 提供程序的直接替代品。您甚至可以使用 Gallery Server Pro 的备份/还原功能将 SQL Server 用户、角色和配置文件数据迁移到 SQLite,反之亦然。(如果您只需要帮助迁移用户,则不必使用 Gallery Server Pro 的其余功能。)

背景

我是 Gallery Server Pro 的创建者和首席开发人员,这是一个开源的照片、视频、音频和文档 Web 图库(阅读 CodeProject 文章)。它最初设计为将数据存储在 SQL Server 中,但在 2008 年 9 月,我添加了 SQLite 作为默认数据提供程序,同时保留了 SQL Server 作为选项。

我找不到任何现成的 SQLite 提供程序,因此我从 另一位 CodeProject 作者的工作开始。我感谢 mascix 的帮助,但最终,我几乎完全重写了代码。原始代码存在许多问题、一些安全漏洞、效率低下、与 SQL Server 提供程序的行为不一致,并且不支持事务(这对 SQLite 的性能至关重要)。

我解决了这些问题,创建了一套坚实的 SQLite 提供程序。我认为它们可能对社区有用,因此我将它们从 Gallery Server Pro 代码库中重构出来,并撰写了这篇文章。请享用!

使用示例代码

源代码是一个包含 SQLite 提供程序和空 SQLite 数据库的 Web 应用程序。下载代码,编译它,然后运行 ASP.NET 配置工具来添加一些角色和用户。(在 Visual Studio 中,从“项目”菜单中选择“ASP.NET 配置”。)

在自己的项目中使用代码

步骤 1:获取最新版本的 System.Data.SQLite,这是 SQLite 核心 DLL 的 ADO.NET 包装器。Robert Simpson 在这个包装器上做得非常出色。请给这位伙计捐款!

步骤 2:将 SQLite 数据库文件复制到项目中的 App_Data 目录。您可以在下载代码的 ~/App_Data/app_data.sqlite 处找到它。或者,创建一个新的空数据库,然后执行 InstallMembership.sql 文件中的 SQL 来设置表。

步骤 3:将包含 SQLiteMembershipProviderSQLiteRoleProviderSQLiteProfileProvider 类的代码文件添加到您的项目中。

步骤 4:编辑 web.config 以配置提供程序,如下所示

<?xml version="1.0" ?>
  <configuration>
  <connectionStrings>
    <clear />
    <add name="SQLiteDbConnection"
     connectionString="Data Source=
        |DataDirectory|app_data.sqlite;Version=3;" />
  </connectionStrings>

  <system.web>
    <compilation debug="false" />
    <authentication mode="Forms" />

    <!-- Configure the Membership provider.-->
    <membership defaultProvider="SQLiteMembershipProvider">
      <providers>
        <clear />
        <add applicationName="SQLite ASP.NET Provider" 
                passwordFormat="Clear"
                minRequiredNonalphanumericCharacters="0" 
                minRequiredPasswordLength="2"
                maxInvalidPasswordAttempts="2" 
                enablePasswordReset="true" 
                enablePasswordRetrieval="true"
                passwordAttemptWindow="10" 
                requiresQuestionAndAnswer="false"
                requiresUniqueEmail="false" 
                connectionStringName="SQLiteDbConnection"
                name="SQLiteMembershipProvider" 
                type="TechInfoSystems.Data.SQLite.SQLiteMembershipProvider, 
                     TechInfoSystems.Data.SQLiteProvider" />
      </providers>
    </membership>

    <!-- Configure the Role provider.-->
    <roleManager enabled="true" cacheRolesInCookie="true" 
                    cookieProtection="Validation"
                    defaultProvider="SQLiteRoleProvider">
      <providers>
        <clear />
        <add applicationName="SQLite ASP.NET Provider" 
            connectionStringName="SQLiteDbConnection"
            name="SQLiteRoleProvider" 
            type="TechInfoSystems.Data.SQLite.SQLiteRoleProvider, 
                 TechInfoSystems.Data.SQLiteProvider" />
      </providers>
    </roleManager>

    <!-- Configure the Profile provider.-->
    <profile defaultProvider="SQLiteProfileProvider">
      <providers>
        <clear />
        <add applicationName="SQLite ASP.NET Provider" 
            connectionStringName="SQLiteDbConnection"
            name="SQLiteProfileProvider" 
            type="TechInfoSystems.Data.SQLite.SQLiteProfileProvider, 
                 TechInfoSystems.Data.SQLiteProvider" />
      </providers>
    </profile>

  </system.web>
</configuration>

步骤 5:为您的 Web 项目添加对 System.Data.SQLite.DLL 的引用。根据您的服务器选择 32 位或 64 位版本。本文下载包含两个版本,但从 此处可能可以获得更新的版本。

步骤 6:编译您的应用程序,即可开始使用!例如,启动 ASP.NET 配置工具来添加一些角色和用户。(在 Visual Studio 中,从“项目”菜单中选择“ASP.NET 配置”。)

此时,您可以使用任何用户、角色和配置文件功能。

管理您的 SQLite 数据库

有几种管理工具可供您查看/编辑表和其他对象。以下是我使用过的一些工具

  • System.Data.SQLite - SQLite 核心 DLL 的 ADO.NET 包装器还包括直接集成到 Visual Studio 中的优秀数据管理工具。太棒了!
  • SQLite Manager - 这是一个插件,可以插入各种应用程序,如 Mozilla Firefox。它轻量级且易于使用。
  • SQLite Administrator - 我不再使用它了,因为前两个已经能胜任工作。但这个也很好用。

事务支持

SQLite 开发者都知道,在单独的事务中执行多个 SQL 语句会导致性能极差。请考虑以下代码

foreach (ProfileInfo profile in ProfileManager.GetAllInactiveProfiles(
  ProfileAuthenticationOption.Anonymous,
  DateTime.Today))
{
  Membership.DeleteUser(profile.UserName, true);
}

此代码删除今天之前访问过您站点的所有匿名用户。每次调用 DeleteUser 都在自己的事务中执行,因此如果需要删除数十或数百个用户,此代码可能需要很长时间。

此代码对 SQL Server 来说不是问题,但对 SQLite 来说是严重的性能问题。解决方案是显式启动一个事务,然后在完成后提交它

private static void DeleteAnonymousProfiles()
{
  BeginTransaction();

  try
  {
    foreach (ProfileInfo profile in ProfileManager.GetAllInactiveProfiles(
      ProfileAuthenticationOption.Anonymous,
      DateTime.Today))
    {
      Membership.DeleteUser(profile.UserName, true);
    }
    CommitTransaction();
  }
  catch
  {
    RollbackTransaction();
    throw;
  }
}

/// <summary>
/// Begins a new database transaction. All subsequent database actions occur within 
/// the context of this transaction. Use <see cref="CommitTransaction"/> to 
/// commit this transaction or <see cref="RollbackTransaction" /> to abort it. 
/// If a transaction is already in progress, then this method returns without any 
/// action, which preserves the original transaction.</summary>
/// <remarks>Transactions are supported only when the client is a web application.
/// This is because the transaction is stored in the HTTP context Items property. If 
/// the client is not a web application, then 
/// <see cref="System.Web.HttpContext.Current"
/// /> is null. When this happens, this method returns without taking any action.
/// </remarks>
public override void BeginTransaction()
{
  // Create new connection and transaction and place in HTTP context.
  if (System.Web.HttpContext.Current == null)
    return;

  if (IsTransactionInProgress())
    return;

  SQLiteConnection cn = GetDBConnection();
  if (cn.State == ConnectionState.Closed)
    cn.Open();

  SQLiteTransaction tran = cn.BeginTransaction();

  System.Web.HttpContext.Current.Items["SQLiteTran"] = tran;
}

/// <summary>
/// Commits the current transaction, if one exists. A transaction is created with 
/// the <see cref="BeginTransaction"/> method. If there is not an existing 
/// transaction, no action is taken. If this method is called when a datareader 
/// is open, the actual commit is delayed until all datareaders are disposed.
/// </summary>
/// <remarks>Transactions are supported only when the client is a web 
/// application. This is because the transaction is stored in the HTTP context Items 
/// property. If the client is not a web application, then <see 
/// cref="System.Web.HttpContext.Current" /> is null. When this happens, this 
/// method returns without taking any action.</remarks>
public override void CommitTransaction()
{
  // Look in HTTP context for previously created connection and transaction.
  // Commit transaction.
  if (System.Web.HttpContext.Current == null)
    return;

  SQLiteTransaction tran =
    (SQLiteTransaction)System.Web.HttpContext.Current.Items["SQLiteTran"];
   
  if (tran == null)
    return;

  // This closes the connection and nulls out the Connection property 
  // on the transaction.
  tran.Commit(); 

 System.Web.HttpContext.Current.Items.Remove("SQLiteTran");
}

/// <summary>
/// Aborts the current transaction, if one exists. A transaction is created with 
/// the <see cref="BeginTransaction"/> method. If there is not an existing 
/// transaction, no action is taken./// </summary>
/// <remarks>Transactions are supported only when the client is a web 
/// application. This is because the transaction is stored in the HTTP context Items 
/// property. If the client is not a web application, then <see 
/// cref="System.Web.HttpContext.Current" /> is null. When this happens, this 
/// method returns without taking any action.</remarks>
public override void RollbackTransaction()
{
  // Look in HTTP context for previously created connection and transaction.
  // Abort transaction.
  if (System.Web.HttpContext.Current == null)
    return;

  SQLiteTransaction tran = 
    (SQLiteTransaction)System.Web.HttpContext.Current.Items["SQLiteTran"];
    
  if (tran == null)
    return;

  // This closes the connection and nulls out the Connection property 
  // on the transaction.
	try
	{
		tran.Rollback();
	}
	catch (SQLiteException) { }

  System.Web.HttpContext.Current.Items.Remove("SQLiteTran");
}

/// <summary>
/// Determines whether a database transaction is in progress.
/// </summary>
/// <returns>
///     <c>true</c> if a database transaction is in progress; 
/// otherwise, <c>false</c>.
/// </returns>
/// <remarks>A transaction is considered in progress if an instance of 
/// <see cref="SQLiteTransaction"/> is found in the <see 
/// cref="System.Web.HttpContext.Current"/> Items property and its connection 
/// string is equal to the current provider's connection string.</remarks>
private static bool IsTransactionInProgress()
{
  if (System.Web.HttpContext.Current == null)
    return false;

  SQLiteTransaction tran = 
      (SQLiteTransaction)System.Web.HttpContext.Current.Items["SQLiteTran"];

  if ((tran != null) && (String.Equals
        (tran.Connection.ConnectionString, _connectionString)))
    return true;
  else
    return false;
}

SQLite 提供程序硬编码为在当前 HttpContext Items 字典中查找 SQLiteTransaction 对象实例。如果找到,则将该事务用于当前操作。如果未找到,则(因为您未将事务添加到 HttpContext 或当前应用程序不是 Web 应用程序),当前操作将在其自己的事务中运行。

通常,您不需要担心事务。例如,假设您正在使用 Membership.CreateUser 创建用户。它会在后台执行一个 SQL 语句(最多两三个),因此通过将其包装在事务中不会获得太多好处。只需关注您在单个 HTTP 请求中执行大量调用的情况。

我建议您将 BeginTransactionCommitTransactionRollbackTransaction 函数放在您的数据层中,以及提供程序实现。这将使您不必在 UI 层中引用 SQLite DLL。有关在生产应用程序中如何使用这些提供程序的示例,请下载 Gallery Server Pro 的源代码。

将角色和配置文件拆分到单独的应用程序中

这些提供程序包含一个标准提供程序 API 中不存在的功能 - 能够将角色和配置文件与不同于用户信息的应用程序相关联。首先,我将展示如何执行此操作,然后提供一些背景信息并解释为什么您可能会使用此功能。

这是一个角色和配置文件提供程序使用与用户信息不同的应用程序的示例

<membership defaultProvider="SQLiteMembershipProvider"> 
 <providers> 
  <clear /> 
   <add
    applicationName="App A"
    name="SQLiteMembershipProvider"
    ...additional stuff here... />   
 </providers> 
</membership>

<roleManager defaultProvider="SQLiteRoleProvider" ...> 
 <providers> 
  <clear /> 
  <add
   applicationName="App B"
   membershipApplicationName="App A"
   name="SQLiteRoleProvider"
   ...additional stuff here... /> 
 </providers> 
</roleManager>
	
<profile defaultProvider="SQLiteProfileProvider">
 <providers>
  <clear />
  <add
   applicationName="App B"
   membershipApplicationName="App A"
   name="SQLiteProfileProvider"
   ...additional stuff here...  />
 </providers>
</profile>
	

请注意,用户信息部分指定“App A”作为应用程序名称,而角色和配置文件部分指定“App B”。这将导致角色和配置文件数据与与用户信息的不同应用程序相关联。但是,为了正常运行,角色和配置文件提供程序需要知道用户存储在哪里,因此我创建了一个新的属性 membershipApplicationName 并将其分配给“App A”。这使角色和配置文件提供程序能够查找用户(如果需要),同时将其自己的角色和配置文件数据存储在不同的应用程序中。

如果您不确定我所说的“应用程序”是什么意思,请回想一下,ASP.NET 用户信息系统可以使用单个数据存储来支持一个或多个应用程序。应用程序由 web.config 中用户信息、角色和提供程序定义中的 applicationName 属性唯一定义。在上面的示例中,定义了两个应用程序:“App A”和“App B”。如果您查看数据库中的 aspnet_Applications 表,您会看到每个应用程序有一个记录。如果您定义第三个具有新应用程序名称的提供程序,ASP.NET 将在初始化期间自动插入第三条记录。

您为什么要这样做?也许您有几个网站都使用同一组用户进行身份验证,但您希望每个网站的角色和配置文件彼此独立。通过将角色分离到自己的应用程序空间,您可以跨网站共享用户,同时为每个网站保留唯一的角色。

在 SQL Server 和 SQLite 之间迁移数据

如果您在 SQL Server 中已有帐户,可以使用 Gallery Server Pro 的备份/还原功能将其导入 SQLite。基本步骤是:

  1. 安装 Gallery Server Pro 并将其配置为指向您在 SQL Server 中现有的用户信息、角色和配置文件数据。
  2. 使用 Gallery Server Pro 的备份功能将您的用户帐户导出到 XML 文件。
  3. 再次运行 Gallery Server Pro Web 安装程序,这次选择 SQLite 作为数据存储。
  4. 使用还原功能从 XML 文件导入您的用户帐户。
  5. 使用您的 SQLite 管理工具打开 SQLite 数据库(~/App_Data/galleryserverpro_data.sqlite)。删除以“gs_”开头的表。这些是 Gallery Server Pro 的表,您不需要它们 - 您只需要保留以“aspnet_”开头的表。
  6. 删除 Gallery Server Pro Web 应用程序,但保留 SQLite 数据库文件。
  7. 现在,您的用户、角色和配置文件数据已迁移到 SQLite!

这坦率地说是一种比较“hacky”的迁移方式,但如果您必须在 SQL Server 和 SQLite 之间移动帐户,它将为您节省大量时间。

有关安装 Gallery Server Pro 和使用备份和还原功能的详细说明,请参阅管理员指南,该指南可在 此处找到。

您也可以反向迁移,即从 SQLite 到 SQL Server,只需相应地修改步骤即可。

关注点

我曾询问 Microsoft 是否有任何单元测试可以验证新的提供程序(例如这些 SQLite 的)是否与 SQL Server 的对应程序行为相同。不幸的是,答案是否定的。这使得很难保证 SQLite 提供程序 100% 兼容 SQL Server 的。

但是,在我能力范围内,我确保了行为相同。我使用了 Reflector 来研究现有的 SQL Server 提供程序。我查看了 SQL Server 存储过程,以了解底层发生了什么。

所有 SQL 都经过参数化处理,并能防止 SQL 注入攻击。

历史

2011 年 4 月 26 日 - 添加了对二进制配置文件属性的支持。向角色和配置文件提供程序添加了 MembershipApplicationName 属性。修复了几个错误。

  • SQLiteProfileProvider: (新功能) 添加了对二进制配置文件属性的支持
  • SQLiteRoleProvider, SQLiteProfileProvider: (新功能) 添加了 MembershipApplicationName 属性,允许角色和配置文件提供程序使用与用户信息提供程序不同的应用程序
  • NVARCHAR(XX) 列的数据类型更改为 TEXT
  • SQLiteMembershipProvider.CreateUser:修复了当空电子邮件地址导致 NullReferenceException 的错误
  • SQLiteMembershipProvider.UpdateUser:修复了电子邮件地址未正确更新的错误
  • SQLiteMembershipProvider.GetUser(object providerUserKey, bool userIsOnline):修复了当指定了有效的 providerUserKey 时,方法未能返回用户的错误
  • SQLiteMembershipProvider.Initialize:当 ApplicationName 属性未在配置文件中指定时,现在默认值为 System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath
  • SQLiteMembershipProvider.UnEncodePassword:修复了当密码为空导致 NullReferenceException 的错误
  • SQLiteRoleProvider.Initialize:如果在配置文件中的配置节中存在未识别的属性,则抛出 ProviderException
  • 将所有对 SQLiteTransaction.Rollback 的调用包装在 try..catch 中,以忽略所有抛出的 SQLiteException 实例

2009 年 3 月 30 日 - 修复了一些错误,并纠正了与 SQL Server 提供程序不一致的行为。

  • SQLiteMembershipProvider.CreateUserLastLoginDate LastPasswordChangedDate 现在默认为 CreateDate 而不是 1753-01-01。这与 SQL Server 用户信息提供程序一致。
  • SQLiteMembershipProvider.ChangePassword:它现在会验证密码是否符合验证要求,例如 MinRequiredNonAlphanumericCharactersPasswordStrengthRegularExpressionMinRequiredPasswordLength
  • SQLiteMembershipProvider.ChangePassword:修复了 ValidatePasswordEventArgs 构造函数的 isNewUser 参数被错误设置为 true 的错误。现在将其设置为 false
  • SQLiteMembershipProvider.ChangePasswordQuestionAndAnswer:修复了 SQL 参数名称在 SQL 与后续为参数赋值的代码之间不一致的错误。
  • SQLiteRoleProvider.GetUsersInRole:修复了当角色名包含一个或多个大写字母时,方法不返回任何用户的错误。
  • SQLiteRoleProvider.CreateRole:如果用户尝试创建与另一个角色仅在大小写不同的角色,则现在会抛出 ProviderException (例如:SysAdmin 和 sysadmin 不再同时有效)。这与 SQL Server 角色提供程序一致。
  • 上两个错误的修复需要向 aspnet_Roles 表添加一个新列 LoweredRoleName 。此更改引起了连锁反应,大多数 SQL 语句都需要更新以考虑新列。
  • SQLiteRoleProvider.DeleteRole:修复了在删除角色时,用户/角色关系记录未从 aspnet_UsersInRoles 表中删除的错误。

2008 年 11 月 3 日 - 向代码添加了 XML 注释,修复了 DataReader 被错误关闭的几个错误,将几个次要常量重构为变量,并纠正了本文示例代码中的一个错误。

2008 年 9 月 9 日 - 初始发布

© . All rights reserved.