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

Membership+ 管理系统,第一部分:引导

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (35投票s)

2014年2月6日

Apache

36分钟阅读

viewsIcon

73479

downloadIcon

6000

初始化一个 Asp.Net Mvc 5 开发环境,用于一个名为 Membership+ 的扩展会员系统的管理前端。

GitHub 托管项目

注意:本系列文章中的每一篇都可能使用不同的数据模式供数据服务使用。出于历史原因,它们存在于不同的源代码控制分支中。因此,早期文章中的数据模式可能与后期文章中的不一致。尝试复用其他文章中的数据服务和样本数据时应格外小心。

相关文章

目录

1. 引言 ^

这是关于一个名为 Membership+ 的扩展会员系统的设计、初始化和实现的系列文章的第一篇,该系统提供在线会员管理、会员互动、各种类型的会员目录、高级会员查询、会员通信和会员社交网络等功能。

本文档介绍了数据模式以及开发环境的设置过程,后续文章将在此基础上提供详细的实现。对于需要为自己的 Web 开发项目设置此类环境的读者来说,本文档也很有价值。

我们选择了一种基于服务且与数据库无关的架构。该系统使用了一些最新的稳定框架,包括 Asp.Net MvcWCFHTML5+CSS3LESSjQueryjQuery UIknockoutjsbootstrap 等。

2. 背景 ^

许多组织都需要在线会员管理和协调,例如公司、政府机构、教育机构、俱乐部、互联网服务提供商、各种协会等。

本系统是对先前文章“ASP.NET 的服务化会员提供程序”(此处发布)和“ASP.NET 的会员存储”(此处发布)的扩展,它们没有实现特定应用程序逻辑的用户和角色管理界面。当然,用于数据服务 Web 界面可以在安全的环境中查询和/或操作会员相关数据。但是,当人们开始思考时,可能会意识到这种管理设置存在局限性。这是因为只有少数值得信赖的技术人员(例如 DBA)才能在组织内部管理会员,而进行实际人力资源管理的管理人员必须将任务“手动”委托给这少数人。如果一个组织的规模变大(例如超过 100 人),可能就需要一个分层管理结构,其中会员以有序的方式共享职责。在处理成员之间的关系时,情况会变得更加微妙。例如,如果一个组织的管理结构的演变由一群当前经理负责,他们共享一部分职责,如何让系统支持恰当的基于规则的角色分配过程,而无需过多的人工干预?显然,角色具有不同的重要性;不应由角色不那么重要的成员将角色分配给任何人,否则系统将不稳定。此外,具有相同角色的成员集在与其他同角色组的成员打交道时,也应该存在某些差异,以便他们尽可能按照既定规则进行操作,而不会发生冲突,然后在寻求系统外部的帮助。然而,上一篇文章中引入的角色系统过于简单,无法支持这样的系统。

此外,会员系统至少应该有几种会员目录,以便可以根据多种方案对会员进行分类,以达到各种目的,例如方便会员查找、代表组织结构、代表项目归属、控制信息传播(在公告、消息、邮件列表中等)。除了这样的静态分类系统外,会员系统还应该允许会员搜索或过滤。它应该接近底层数据服务的相应智能结构化查询能力的质量。

会员系统还应该支持会员关系管理,从简单的联系人列表到更复杂的社交图谱圈子导航。

它应该有一个基于组的公告和个人事件调度系统,可以被组内的成员或与所有者在特定社交圈内相关的成员共享。所有这些活动都应该能够在互联网上完成,而无需访问会员数据服务的安全环境。

为了简化引导,关键部分,即对会员通信的原生支持,不包含在当前版本的系统中。

Membership+ 系统由四个主要层组成

  1. 数据服务。会员相关关系数据被抽象并封装到一个通用的数据服务中,以分布式和数据库无关的方式,可供需要操作或使用所述数据的各种应用程序调用。该数据服务包含完整的 API,供能够与 WCF 服务通信的客户端查询、进行查询智能处理以及操作会员相关数据,它包含了所有 Microsoft 的 .net 框架、JavaScript 和其他具有 WCF 适配器的系统。数据服务在此提供为现成的软件包。

  2. 作为数据服务客户端的应用程序,它们强制执行特定的应用程序逻辑、视图样式等。本文描述的特定应用程序是会员管理应用程序,用户可以使用该应用程序维护自己的用户数据,管理员可以根据其角色和相应优先级管理会员。在接下来的内容中,它被称为“Web 应用程序”。

    它不仅是数据服务的客户端,也是一个 Web 网站,还是后端数据服务与最终客户端软件(即用户浏览器中运行的 JavaScript)之间的 JSON 代理服务或网关。正如后续文章所示,JavaScript 的类型系统(与鸭子类型相关)的灵活性,结合后端自动生成的严格而广泛的类型系统,可以让开发人员利用两全其美,使系统在开发时具有灵活性,同时又易于维护,当然前提是两者能正确平衡。

  3. 在浏览器中运行的 JavaScript。我们采用了 jQuery + knockoutjs 的组合。前者现在非常流行,所以我们不再赘述。knockoutjs 在整个当前系统中都有使用。它提供了在浏览器应用程序中支持 MVVM 模式的方法。(JSON)数据视图模型和相应的视图现在可以分离,并在稍后以声明式方式绑定在一起。JavaScript 视图模型可以在多个视图甚至应用程序中重复使用。对于数据丰富的应用程序,在不断变化的系统中创建和维护这些模型是繁琐的。幸运的是,其中很大一部分可以自动生成。

    数据服务中包含的自动生成的视图和 JavaScript 视图模型为应用程序开发人员在应用程序层进行少量修改后重用它们提供了丰富的资源。这可以节省他们花费在繁琐、易错和重复性工作上的时间。

  4. 布局采用 HTML5 + CSS3 (less) + jQuery UI + bootstrap。这种组合允许 Web 应用程序的视图在各种设备上(从桌面到移动电话)保持一致的设计、维护和部署。

图:基于服务的 Membership+ 系统。

3. Membership+ 数据模式 ^

Membership+ 的数据模式是基于先前文章中发布的最小模式(参见此处)扩展的。一个好的数据模式很重要,因为它决定了系统未来发展的可能性、灵活性、可扩展性和可维护性。希望以下非详尽的扩展列表能够实现这一点

  1. 基于 Asp.Net 属性的基于角色的声明式权限机制有时过于简单和不灵活,无法满足更精细的组织治理需求。然而,得益于此处的层级角色系统,它可用于提供粗粒度权限,使用顶级角色进行入口过滤。

    此外,在 Roles 数据集中添加了一个类型为整数的属性 RolePriority。新属性用于表征哪个角色被允许执行某个操作,或者基于简单的算术比较,哪个角色比另一个角色优先级更高(值越大)。对于企业或政府机构等层级结构化的组织来说,操作优先级和/或权限对于其良好运作至关重要。为此使用数字优先级类型可以简化逻辑。

  2. Roles 数据集中添加了一个类型为布尔的属性 CanBeDeleted。某些角色不应在应用程序级别删除,这是因为在基于角色属性的声明式授权系统中(Asp.Net 5 之前),某些角色的名称会被编译到应用程序代码中。删除这些角色可能会对系统产生意外影响。现在,应用程序可以使用 RoleCanBeDeleted 属性来强制执行逻辑,以防止删除编译角色,在我们系统中,这些应该是顶层通用角色,例如 Administrators(见下文)。

  3. UsersInRoles 数据集中添加了一个类型为整数的属性 SubPriority。角色(代表管理组)有时是一种过于粗粒度的特权特征,不够有效,例如,在处理具有相同角色的成员之间的关系时。SubPriority 被引入到用户/角色关联数据录入中,以便在需要时进行区分。

  4. UsersInRoles 数据集中添加了一个属性 AdminID,这是一个指向 Users 数据集中用户的外键。它用于跟踪是最后谁创建或修改了角色分配。

  5. 添加了一个新的数据集 UserRoleHistories,用于跟踪详细的角色分配,以进行审计。当前系统不支持基于投票的角色分配,因此此数据集中的记录可用于追溯角色分配过程的责任。

  6. 用户如今可能拥有多个电子邮件地址用于各种目的。为了适应这种情况,已从 Users 数据集中删除了 Email 属性。它已移至 UserAppMembers 数据集。此更改使得用户可以选择不同的默认电子邮件地址用于每个应用程序(如果他们愿意)。

  7. 用户图标相关数据属性(EmailIconImgIconMimeIconLastModified) 已添加到 UserAppMembers 数据集中。这允许用户为他们是成员的每个应用程序选择不同的个人图标。

  8. 添加了一个新的数据集 UserDetails,用于包含用户可自行维护的个人详细信息。目前它作为一个占位符,将来会进行扩展。

  9. 添加了记录用户通信渠道的数据集。它们是 CommunicationTypesCommunications。它们记录了用户可以在特定应用程序上下文中联系到的各种通信方式(即,它依赖于应用程序)。

  10. 添加了提供用户层级目录分类的数据集。它们是 UserGroupTypesUserGroupsUserGroupAdminRolesUserGroupMembers。尽管数据服务支持针对任何数据集进行智能结构化查询,包括 Users 数据集,但组织可能仍然认为需要更静态的层级分类方案。在现实情况中,一个用户可以被多种方式分类。例如,一个用户可能属于组织中的一个或多个部门,同时,他/她也可能属于多个跨部门项目、主题组等。此外,组织可能发布逻辑会员目录,这些目录可能与内部目录任何一个都不相同。这三个数据集的引入是为了涵盖所有这些可能性,使用少量数据集。UserGroupTypes 中的记录代表某种类型的分类方案。UserGroups 数据集是一个自引用的层级集合,用于表示分类。UserGroupAdminRoles 中的记录分配了一组负责管理该组的角色,以及,隐含地,负责其子组层级(在其自身管理角色缺位时)。UserGroupMembers 集用于将用户与用户组关联,以便一个用户可以属于多个用户组,一个用户组可以有多个用户。

  11. 数据集 Announcements。用于在可能的应用程序和/或用户组上下文中发布公告。用户组的层级性质使得实现允许用户隐式接收父组公告的应用程序逻辑成为可能。

  12. 添加了表示用户在多种社交连接(图)中与其他用户关系的数据集。它们是 UserAssociationTypesUserAssociationsUserAssocInvitations。成员之间的关系有时并不相等。例如,一个人与其父母的关系不等同于父母与那个人的关系。所以,成员 A 应该通过两个记录 A 到 B 的记录和 B 到 A 的记录与成员 B 相关联,它们可能不属于同一类型,在 UserAssociationTypes 中记录了各种关系类型。因此,这些数据集可以用来表示成员之间的定向或非定向(如同伴关系)社交图谱。某些类型的成员关联,如同伴类型,需要相互同意,其他则自然形成或由第三方分配。在前一种情况下,必须有一个发起者。发起操作记录在 UserAssocInvitations 数据集中,该数据集可供被邀请成员决定接受、拒绝或搁置。引入这些数据集是为了使客户端能够支持从简单的联系人列表到更复杂的社交网络系统。想要一个类似 Facebook 的系统,具有可控的社交图谱导航?这些数据集可以用来提供关系数据支持。

  13. 添加了表示各种定时事件的数据集。它们是 EventTypesEventCalendarEventCalendarShareCircles。事件可以在任何应用程序、用户组、个人和连接的社交圈(在 EventCalendarShareCircles 中记录)的组合上下文中可见。用户组的层级性质使得实现允许用户隐式共享父组中事件的应用程序逻辑成为可能。

Database schema

图:Membership+ 数据服务的数据模式示意图。

更多细节可以在数据模型源代码以及数据服务网站托管的相应文档中找到。文档作为单独的下载提供,因为它们相当庞大。数据模型可以在源代码中找到。对于每个数据集,都有相应的集合、页面和实体数据模型。它们的源代码包含在下载包的 Service\Shared 子目录的项目中。

它作为系统引导阶段 Membership+ 数据服务的最小数据模式的起点。我们数据服务的生产技术使得系统能够以可复现的质量且没有太大困难地在未来朝着更具体的应用程序需求进行扩展、分支甚至小步收缩。

4. 设置解决方案 ^

尝试在适当的目录中创建一个 Asp.Net MVC 5 Web 应用程序。在本例中,应用程序是使用 Visual Studio 2013 Express 创建的。此 Web 应用程序的许多部分在您设置时可能会过时。需要付出一些努力才能正确更新它,以便新更新的部分至少不会相互冲突。

在选定的根目录下的目录结构应如下所示

  • Libs\MembershipPlusAppLayer。它包含处理应用程序(或业务)逻辑的项目。
  • Libs\AppLayerTests。它包含单元测试项目。
  • Service。它包含处理 Web 应用程序与 Membership+ 数据服务之间通信的项目。
  • WebSite\MemberAdminMvc5。它包含会员管理 Web 应用程序。

4.1 数据服务相关项目 ^

解决方案根目录下的 Service\SharedService\ServiceProxy\Desktop 子目录中有两个项目。第一个项目包含数据服务与客户端(即所考虑的 Web 应用程序)之间共享数据结构的源代码,第二个项目包含用于客户端调用数据服务的服务代理的源代码。这两个项目作为数据服务分发的一部分提供。

4.2 Asp.Net 会员 API ^

对前面此处描述的 Asp.Net 会员存储和管理器几乎没有改动,只是现在配置为引用上述项目作为 Membership+ 的数据源。由于默认的会员存储和管理器类具有预定义的 API,无法进一步扩展,因此我们将尽可能使用存储和管理器提供的功能。本文档将不再深入探讨这些细节,它们在原始文章中都有。

未被数据存储覆盖的功能是在下面描述的应用层中实现的。

4.3 Membership+ 应用层和测试 ^

目前为此分配了一个空项目。

4.4 创建和更新 Web 应用程序 ^

请按照此处给出的步骤转换默认的 Asp.Net Mvc 5 项目。

除了这些更改之外,对于这个更高级的项目还需要进行以下操作:

  1. App_Start\Startup.Auth.cs 文件中的 Startup 类中添加一个新的静态布尔字段 MemberInitSuccess

  2. 将会员数据服务初始化块包含在 try ... catch ... block 中。

    public void ConfigureAuth(IAppBuilder app)
    {
        ...
        try
        {
            cctx.DirectDataAccess = true;
            Application_ServiceProxy apprepo = new Application_ServiceProxy();
            List<Application_> apps = apprepo.LoadEntityByNature(cctx, 
                                                               ApplicationName);
            if (apps == null || apps.Count == 0)
            {
                cctx.OverrideExisting = true;
                var tuple = apprepo.AddOrUpdateEntities(cctx, 
                                              new Application_Set(), 
                                              new Application_[] 
                                                  { 
                                                     new Application_ 
                                                         { 
                                                           Name = ApplicationName 
                                                         } 
                                                  });
                App = tuple.ChangedEntities.Length == 1 && 
                              IsValidUpdate(tuple.ChangedEntities[0].OpStatus) ? 
                      tuple.ChangedEntities[0].UpdatedItem : null;
                cctx.OverrideExisting = false;
            }
            else
                App = apps[0];
            MemberInitSuccess = true;
        }
        catch
        {
        }
    }            
  3. 向应用程序的 Controllers 子目录添加一个 BaseController 类文件,这是一个基类,用于包含应用程序控制器共有的属性和方法。应用程序的所有控制器都可以继承它。

    对于像当前这样的动态 Web 应用程序,其内容更有可能从关系数据源检索,而不是从本地文件检索。因此,不能依靠 Web 服务器甚至 Asp.Net 框架来有效地控制静态或缓慢更改内容的客户端缓存。这是因为 Web 服务器和 Asp.Net 框架都不知道应用程序特定的内容是否已更改。这必须在特定的 Web 应用程序内部完成。

    初始版本包含一些方法,这些方法可以被调用来管理站点内容的客户端缓存,这些缓存基于内容的最后修改属性和/或ETag属性。发现 Asp.Net Mvc 4.0 的默认基于属性的缓存控制机制无法按照HTTP 协议很好地处理客户端缓存。

    包含的 CheckClientCacheSetClientCacheHeader 方法负责检查内容的客户端缓存状态,并根据内容的最后修改日期和/或 ETag 属性(如果存在)设置同一内容的客户端缓存。

    CheckMemberInitStatus 方法用于检查 Web 应用程序作为会员数据服务客户端的初始化状态。

  4. 将项目中的控制器(例如 AccountController)更改为从 BaseController 类继承。

  5. Web.config 文件中的 <appSettings> 节点下添加一个 key="EnableClientCache" 项,并将其值初始化为 false

4.5 添加 Bootstrap LESS 源代码 NuGet 包 ^

Visual Studio 默认的 Mvc 5 项目中包含的静态级联样式表(CSS)Bootstrap 包不易于修改和定制。我们必须依赖“第三方”为我们提供主题。这并非我们作为开发人员所期望的,因为我们可以拥有更多控制权,原因是 Bootstrap 的“源代码”是公开的,并且是动态 CSS(称为 LESS 文件)的形式,其中可以使用变量、嵌套、混合、运算符和函数来减少信息重复和维护成本,提高样式一致性。

从 nuget.org 安装“Bootstrap Less Source”。

4.6 自定义 Bootstrap ^

variables.less 文件包含 Bootstrap 用于定义样式的核心变量。作为一个通用解决方案,原始文件包含一些硬编码值,至少在我们的目的中,可以进一步简化为与几个基本值相关联。@base-dark-color 是其中之一,它控制站点的整体配色方案。LESS 和其他类似的“可计算”样式表生成系统的优点在于,它们可以使用一些基本的样式结构来“计算”原本硬编码的样式,从而产生更一致的视觉效果(参见下文)。

4.6.1 修改 variables.less 文件 ^

在顶部添加以下变量:

//@base-dark-color: #333;
//@base-dark-color:  darken(#428BCA, 20%);
//@base-dark-color: darken(#CF4132, 10%);
//@base-dark-color: darken(#DF691E, 10%);
@base-dark-color: darken(#cc5000, 10%);
//@base-dark-color:  darken(#41CF32, 30%);

这只是一个随机选择。当然,人们可以放更多“深色”在这里供选择。

发现为了在我们目前的 Bootstrap 使用覆盖范围内获得我认为的满意视图,需要对 variables.less 文件进行以下修改。预计将来可能需要更多。

  1. 重新定义全局“灰色”颜色

    @gray-darker:            lighten(@base-dark-color, 13.5%); 
    @gray-dark:              lighten(@base-dark-color, 20%);   
    @gray:                   lighten(@base-dark-color, 33.5%); 
    @gray-light:             lighten(@base-dark-color, 60%);   
    @gray-lighter:           lighten(@base-dark-color, 93.5%); 
          
  2. 重新定义全局“文本”颜色

    @brand-primary:         @gray-dark; 
  3. 将导航栏的配色方案与 @base-dark-color 相关联

    // Inverted navbar
    // Reset inverted navbar basics
    @navbar-inverse-color:                      @gray-light;
    @navbar-inverse-bg:                         lighten(@base-dark-color, 10%);
    @navbar-inverse-border:                     darken(@navbar-inverse-bg, 10%);
    // Inverted navbar links
    @navbar-inverse-link-color:                 @gray-light;
    @navbar-inverse-link-hover-color:           @gray-lighter;
    @navbar-inverse-link-hover-bg:              lighten(@navbar-inverse-bg, 10%);
    @navbar-inverse-link-active-color:          @navbar-inverse-link-hover-color;
    @navbar-inverse-link-active-bg:             darken(@navbar-inverse-bg, 10%);
    @navbar-inverse-link-disabled-color:        @gray-dark;
    @navbar-inverse-link-disabled-bg:           transparent;
  4. 将按钮的配色方案与 @base-dark-color 相关联

    @btn-default-color:              @gray-darker;
    @btn-default-bg:                 @gray-light;
    @btn-default-border:             lighten(@gray, 5%);
    @btn-primary-color:              @gray-lighter;
    @btn-primary-bg:                 @brand-primary;
    @btn-primary-border:             darken(@btn-primary-bg, 5%);
    ...
  5. 将表单的配色方案与 @base-dark-color 相关联

    @input-border:                   lighten(@gray,10%);
    ...
    @input-border-focus:             lighten(@gray,5%);

所做的更改将使站点色彩变少(因为它将使用更少的颜色表示),但对我而言,它们将增加视觉效果的一致性。当然,根据喜好,人们可以选择保留原始版本或创建自己的组合。

4.6.2 创建 site.less ^

在 Web 应用程序的 Content 下创建一个名为 less 的子目录。然后,在刚刚创建的文件夹中添加一个名为 Site.less 的 .less 文件。应该配置 LESS 编译程序,将输出文件指向 Web 应用程序 Content 子目录下的 Site.css 文件。在刚刚创建的 Site.less 文件顶部添加以下内容:

@import "..\bootstrap\bootstrap.less";

这将把所有 Bootstrap 样式包含到输出的 Site.css 文件中。

出于某些原因,新版本的 Bootstrap 放弃了对嵌套菜单的支持,转而支持扁平的移动样式。由于我们的网站仍然需要支持桌面和大型屏幕设备,并且要显示的信息足够复杂,本质上是层级的,因此似乎在我们找到有效处理此类信息的方法之前,我们仍然需要嵌套菜单。幸运的是,这并不难实现。发现在 Site.less 文件中添加以下内容可以支持嵌套菜单:

.dropdown-submenu
{
    position:relative;
}
 
.dropdown-submenu>.dropdown-menu
{
    top:0;
    left:100%;
    margin-top:2px;
    margin-left:0px;
    -webkit-border-radius:0 6px 6px 6px;
    -moz-border-radius:0 6px 6px 6px;
    border-radius:0 6px 6px 6px;
}

.dropdown-submenu:hover>.dropdown-menu
{
    display:block;
}

.dropdown-submenu>a:after
{
    display:block;
    content:" ";
    float:right;
    width:0;height:0;
    border-color:transparent;
    border-style:solid;
    border-width:5px 0 5px 5px;
    border-left-color:@gray;
    margin-top:5px;
    margin-right:-10px;
}

.dropdown-submenu:hover>a:after
{
    border-left-color:@gray-darker;
}

.dropdown-submenu.pull-left
{
    float:none;
}

.dropdown-submenu.pull-left>.dropdown-menu
{
    left:-100%;
    margin-left:10px;
    -webkit-border-radius:6px 0 6px 6px;
    -moz-border-radius:6px 0 6px 6px;
    border-radius:6px 0 6px 6px;
}

现在,您可以从 App_Start 子目录下的 BundleConfig.cs 文件中删除束 "~/Content/css""~/Content/bootstrap.css" 项,因为它已经包含在 Site.css 文件中了(见上文)。

4.7 更改整体布局 ^

Web 应用程序 Views\Shared 子目录下的通用 _Layout.cshtml 文件被重构,将 Web 应用程序的通用顶部栏提取到一个部分视图中,以便除了 _Layout.cshtml 之外,其他视图也可以使用它。

4.7.1 顶部导航栏 ^

<div class="container">
    <div class="navbar-header">
        <button type="button" class="navbar-toggle"
                   data-toggle="collapse"
                   data-target=".navbar-collapse">
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
        </button>
        @Html.ActionLink("+", "Index", "Home", null, 
              new { @class = "navbar-brand ion-android-social" })
    </div>
    <div class="navbar-collapse collapse">
        <ul class="nav navbar-nav">
            <li>@Html.ActionLink("Home", "Index", "Home")</li>
            @if (Request.IsAuthenticated)
            {
                <li class="dropdown">
                    <a data-toggle="dropdown" 
                          class="dropdown-toggle" href="#">
                       Organizers<b class="caret"></b>
                    </a>
                    <ul class="dropdown-menu">
                        <li class="dropdown-submenu">
                            <a href="#">Bookmarks</a>
                            <ul class="dropdown-menu" role="menu">
                                <li>
            @Html.ActionLink("By Data Source", "Bookmarks", "Home")
                                </li>
                                <li>
            @Html.ActionLink("By Categories", "CategBookmarks", "Home")
                                </li>
                            </ul>
                        </li>
                    </ul>
                </li>
                <li class="dropdown">
                    <a data-toggle="dropdown" class="dropdown-toggle" href="#">
                        Settings<b class="caret"></b>
                    </a>
                    <ul class="dropdown-menu" role="menu">
                        <li class="dropdown-submenu">
                            <a href="#">Account</a>
                            <ul class="dropdown-menu" role="menu">
                                <li>
            @Html.ActionLink("Change account info", "ChangeAccountInfo", "Account")
                                </li>
                                <li>
            @Html.ActionLink("Change preferences", "EditUserPreferences", "Account")
                                </li>
                                <li>
            @Html.ActionLink("Add/Change profile", "UserDetails", "Account")
                                </li>
                            </ul>
                        </li>
                    </ul>
                </li>
                <li class="dropdown">
                    <a data-toggle="dropdown" class="dropdown-toggle">
                        Admin<b class="caret"></b>
                    </a>
                    <ul class="dropdown-menu">
                        <li>
            @Html.ActionLink("Admin Users", "UserAdmin", "Admin")
                        </li>
                        <li>
            @Html.ActionLink("Admin Roles", "RoleAdmin", "Admin")
                        </li>
                        <li class="dropdown-submenu">
                            <a href="#">Statistics</a>
                            <ul class="dropdown-menu">
                                <li>
            @Html.ActionLink("Online Users", "OnlineUsers", "Admin")
                                </li>
                            </ul>
                        </li>
                    </ul>
                </li>
            }
            <li>@Html.ActionLink("About", "About", "Home")</li>
            @*<li>@Html.ActionLink("Contact", "Contact", "Home")</li>*@
        </ul>
        @Html.Partial("_LoginPartial")
    </div>
</div>
    

它进一步包含 _LoginPartial.cshtml,用于根据用户当前的登录状态处理用户登录/注销。

4.7.2 修改登录和注册视图 ^

Views\Account 子目录下有两个名为 Login.cshtmlRegister.cshtml 的视图。这两个视图包含演示用户的信息。在查看了演示数据样本的使用情况后(参见下文),应该删除这些信息,因为这些信息对于任何其他会员数据源都不相关。

4.8 “Hello world!” ^

网站应该可以第一次运行了。如果遇到任何问题,请在下面的评论/讨论论坛中告知我。

Front page

图:“Hello world!”

在按照上述步骤配置好 .less 文件后,更改配色方案很容易。您只需要更改 @base-dark-color 变量(参见此处)。以下是一些示例。

Front page Skins

图:示例皮肤。

5. 设置数据服务 ^

Membership+ 数据服务包中的文件解压到一个文件夹,为它配置一个网站(这是一个 ASP.NET MVC 4 Web 应用程序)。在您的系统中启用 WCF 的 HTTP 激活。基本上就是这样。

如果您需要持久化更改,至少服务站点下的 App_Data\MembershipPlus\Data 子目录需要有正确的权限。它应该允许用户 IIS_IUSRS 写入权限。

警告:这是用于评估目的的系统演示/测试版本。它基于瞬态序列化方法。请不要将其用于长期数据持久化或支持生产系统。

5.1 编译文档 ^

源代码根目录下的 Documents 子目录包含一个 Sandcastle 项目,可用于编译客户端(即 Web 应用程序端)编程的文档。但在编译文档之前,请确保已完成以下操作:

  • 安装 Sandcastle(如果尚未安装)。
  • Web 应用程序的解决方案已编译,以便生成 XML 文档文件。
  • 文档的默认输出路径是相对于文档目录的 ..\..\DataService\WebSite\Documents\ClientAPI\。确保这是允许的输出目录。如果不是,请将其更改为正确的目录。
  • 如果上述输出目录与数据服务网站的 Documents\ClientAPI 子目录不同,则删除后者中的所有内容(如果存在),然后将前者中生成的所有文档文件复制到后者。

6. 连接到数据服务 ^

6.1 设置机器密钥 ^

会员提供程序使用机器密钥(Web 应用程序的)来哈希密码和加密身份验证 Cookie。为了更好地控制您的会员系统(例如,参见此处),而不是使用系统默认的密钥,您应该通过在 Web.config 文件中的 <system.web> 节点下添加以下节点来设置其值:

<machineKey
    validationKey="..."
    decryptionKey="..."
    validation="SHA1"
    decryption="AES"
/>

并为 validationKeydecryptionKey 生成一个适当的值。当然,您不应该在任何实际使用中都使用本文提供的这些值。

6.2 将 WCF 配置为客户端 ^

解决方案的 Service\ServiceProxy\Desktop 子目录下有一个名为 app.config 的自动生成文件。它包含 Membership+ 数据服务公开的所有端点。将其中

<system.serviceModel>
   ...
</system.serviceModel>

节点的内容复制到 Web 应用程序的 Web.config 文件中的相应节点。__servicedomain__ 占位符(或当前的值)应替换为读者在测试环境中刚刚配置好的 Membership+ 数据服务的实际根 URL,例如 https:///memberplus。前面的说明也可能有用。

6.3 配置会员存储 ^

在 Web 应用程序的 Web.config 文件中的 <appSettings> 节点下插入以下节点:

    <add key="ApplicationName" value="MemberPlusManager"/>
    <add key="WriteAuthExceptionsToEventLog" value="false" />
    <add key="RequiresUniqueUserEmail" value="true" />
    <add key="UserApprovedOnAddition" value="true" />
    <add key="ThrowOnDeletePopulatedRole" value="true"/>
    <add key="DeleteUserMembershipOnly" value="true"/>
    <add key="PasswordAttemptWindow" value="20" />
    <add key="MaxInvalidPasswordAttempts" value ="5" />
    <add key="UserStoreAutoCleanupRoles" value="true"/> 

其中,“MemberPlusManager”是应用程序的名称。然后,就配置好了 Membership+ 数据服务的会员存储。有关更多详细信息,您应该阅读这篇文档

6.4 选择样本数据源 ^

内存数据库的数据位于 Web 应用程序的 App_Data\MembershipPlus\Data 子目录下。应用程序的初始系统应该从一个空目录开始,并根据应用程序需求设置数据。这将在下一节介绍。

但是,为了演示或测试目的,从头开始每次都设置数据可能效率不高。如果是这种情况,您可以选择上面下载包中包含的一个数据源,并将其中所有文件复制到上述目录。这可能很有帮助,因为它已经包含了一些初始数据,包括用户对(sysadmindemo-user-a)、角色(AdministratorsAdministrators > System)以及用户角色。

6.5 再次运行 ^

6.5.1 尝试演示 ^

如果尚未启动,请先启动第 5 节中配置的数据服务。现在,使用选定的样本数据再次在调试器中启动 Web 应用程序,可以通过上述说明加载样本数据。

以用户名 sysadmin 登录,然后顶部导航栏会显示额外的菜单,如下所示:

Front page, logged in

图:登录后显示的顶部导航栏。

请注意,目前显示的任何新菜单都没有指向已实现的动作。

6.5.2 重新开始 ^

演示结束后,是时候采取行动了。删除 Web 应用程序 App_Data\MembershipPlus\Data 子目录下的所有文件,然后再次加载数据库。接下来,在调试器中运行 Web 应用程序。尽管表面上与之前的“hello world”运行没有区别,但应用程序(在上述提供程序配置中定义的名称,即“MemberPlusManager”)会自动注册,前提是系统配置正确。您可以通过数据服务管理前端显示应用程序条目,这将在下一节中介绍。

下一步是注册一些初始用户。这可以在 Web 应用程序的用户注册页面中完成,如下所示:

User registration

图:用户注册页面。

为了进一步进行,您需要注册至少一个用户,例如“sysadmin”,他将是下文所述的根用户。由于我们使用的是内存中的关系数据后端,因此在数据服务管理网站的应用程序池因长时间空闲而被回收之前,应将其保存到磁盘(参见下文的说明)。

7. 初始化会员数据 ^

数据服务有自己的管理网站。它是一个通用、最完整、最智能的数据管理前端,根据上面介绍的关系数据模式进行了扩展。它面向安全本地网络内的系统管理员,大多数创建、读取、更新和删除(CRUD)操作都可以仅通过此前端完成。

然而,它并没有强制执行超出当前扩展数据模式规范(用于自动生产系统)编码的信息。尽管将来可能会将更多内容移入自动处理系统,但 Web 应用程序层总是有更多的具体细节需要处理。

本节简要介绍数据服务管理前端的使用。该过程的其他目标之一是初始化 Membership+ 数据源,以便更多具有适当授权的用户可以通过更用户友好的 Web 界面在任何位置管理会员,并以安全可控的方式进行管理。本文档和后续的文档都围绕这一点展开。

可以通过从主页上的“数据源”选项卡来访问数据管理页面。

7.1 使用数据服务操作前端 ^

7.1.1 数据库级别 ^

数据库页面包含所有数据集(表)的列表以及数据源中数据关系的示意图。

除了列表之外,根据所使用的关系数据后端类型,还有不同组的操作按钮。对于当前的内存关系数据后端,有加载和保存数据库按钮,可用于手动控制开发或测试期间数据的持久化方式。数据库在数据服务管理站点启动时加载。如果在执行一些非平凡操作后再次手动加载数据库,那么这些操作将被丢弃,数据库将恢复到上次加载时的状态。但是,如果手动保存了执行了一些非平凡操作后的数据库,更改将被保存,下次重新加载数据库时,它将从保存后的状态开始。

内存关系数据后端的数据库文件位于数据服务管理网站的 App_Data\MembershipPlus\Data 子目录下。该目录最初不存在。它将在首次单击“保存数据库”按钮后创建。如预期,您可以保留多个数据版本(文件),并根据开发需求(整体)加载它们。

database page

图:内存演示/测试/开发关系数据后端的数据库页面。

对于由传统关系数据库引擎支持的关系数据后端,当数据库尚未创建时,会有一个“创建数据库”按钮。单击该按钮后,假设数据服务配置正确且操作成功,“创建数据库”按钮将在下次刷新页面时消失。

7.1.2 数据集级别 ^

数据集级别的页面提供用户执行的 CRUD 操作。页面有两种模式,即视图模式编辑模式。模式由页面右上角的相应按钮控制。以 Applications 数据集为例:

图:视图模式下数据集页面的编辑模式切换按钮。

页面默认不显示任何数据。这是因为用户尚未指定用于放置第一页数据的排序和过滤查询条件。

用户界面的构建方式要求用户必须指定至少一个完整的排序表达式(属性 + 方向),然后才能执行其他操作。如果用户不熟悉数据集可排序属性的名称,他/她可以将输入光标放在“Sort By”标签旁边的文本框中,并使用向下箭头键调出可用值列表。例如,以下是用户为 Applications 数据集执行此操作时看到的内容:

图:Applications 视图页面,初始加载时。

选择一个值,例如 Name,系统将引导用户提供下一个可用值。当用户熟悉特定数据集时,他/她可以随时输入属性名称的前几个字母,以便在提供足够信息时系统可以自动完成。

排序表达式完整后,将出现显示按钮,如下图所示:

图:Applications 视图页面,排序表达式完整时。显示按钮出现,过滤框打开。

单击显示按钮(红色圆圈),将显示数据的首页。目前只有一个应用程序。可以通过选择它来查看其详细信息:

图:在上一节讨论的 Web 应用程序预览运行时自动注册的应用程序。

如所示,该项的 DisplayNamenull。您可以为应用程序设置一个更友好的名称。这可以通过先进入编辑模式来实现。然后按照上述相同的过程显示项目。DisplayName 属性有一个相应的文本框。为其输入一个显示名称,例如“Membership+ Management System”,然后单击顶部的提交按钮将其保存到数据库,如下图所示:

图:对一组 Application 实体的修改在“提交”时保存到数据库。

您可以在“提交”之前对多个记录进行多次更改。“提交”操作将一次性将所有更改保存到数据库。

对于用户需要考虑的还有一项操作,如果使用内存数据库作为后端。即,他/她可以选择将更改持久化到磁盘,或者回滚更改,或者将其保留在内存中。如上所述,更改的持久化或回滚是在数据库页面中通过“保存数据库”或“加载数据库”按钮执行的。

7.2 添加根角色和根用户 ^

热身之后,我们需要继续初始化会员管理系统。

由于这是一个基于角色优先级的会员管理系统,Web 应用程序(管理器)在不创建根角色并使用 Web 应用程序外部的方式将其分配给根用户的情况下将无法正常运行。根角色必须具有系统的最高优先级,而根用户必须具有根角色的最高相对优先级。通过此设置,根用户然后可以使用 Web 应用程序界面添加额外的更高级别的管理角色,并将它们分配给选定的几位用户,这些用户可以执行相同的操作(共享上层管理员的工作负载),直到整个管理组织以有序的方式形成。这并不排除根管理员在小型组织或组织处于初始设置阶段时独自完成所有初始分配工作的可能性。

7.2.1 添加根角色 ^

转到 Roles 数据集页面,进入编辑模式。尽管数据集为空,但仍建议尝试先列出数据,遵循上述过程。当页面处于列表模式时,新添加的项目将立即显示在添加之后。否则,新添加的项目只有在用户尝试列出它们时才会可见!

图:处于编辑和列表模式下的 Role 数据集页面。“Add”按钮被红色圆圈标出。

单击“Add”按钮并添加第一个类别角色“Administrators”,如下图所示。

图:角色添加对话框。外键 ApplicationID 和 ParentID 右侧有按钮,用于打开相应的实体选择对话框。

RoleNameDisplayName(可选)和 RolePriority 属性设置适当的值。由于这应该是一个适用于各种管理角色的类别角色,因此应为其设置足够高的优先级。这里我们将其设置为任意值 10000,因为它是用于演示目的的。一个设计良好的角色系统应该具有易于记住且直观的角色优先级值。单击 ApplicationID 属性旁边的按钮打开应用程序选择对话框,如下图所示。

图:关联应用程序选择对话框。

使用上述过程找到并选择新创建的应用程序,然后单击“OK”按钮。对于这个顶层角色,不应选择任何 ParentID。再次单击“OK”按钮,我们的第一个角色“Administrators”将被添加到数据源,如下所示。

图:新添加的“Administrators”角色。

根角色“System”应该在刚刚添加的类别角色之下。因此,您应该遵循与上面相同的过程,为 ApplicationID 外键属性选择相同的应用程序,并为 ParentID 外键选择刚刚添加的“Administrators”角色。结果如下面的角色列表所示。

图:添加了根角色。显示角色层级。

请注意,层级结构下的所有角色都必须引用同一个应用程序。否则,结果将不符合预期!

好的,已添加最少必需的角色。下一个任务是将它们分配给用户。

7.2.2 将根角色分配给根用户 ^

我们需要将根角色“Administrators.System”分配给根用户“sysadmin”。转到 UsersInRoles 数据集页面,进入编辑模式,然后进入列表模式,并在数据集中添加一个关联实体,该实体具有指向根角色的 RoleID 和指向根用户的 UserID 属性,并具有足够大的子优先级(或相对优先级)。AdminID 属性可以留空(设置为 null 值),因为这是在系统外部添加的。成功添加后,将显示如下:

图:新添加的角色分配记录。

系统有一个名为 UsersRoleHistories 的数据集,用于记录用户的角色历史以及谁进行了分配,以供审计。对于根用户,我们不需要输入任何条目,因为它是在系统外部完成的。

7.3 添加各种类型 ^

7.3.1 用户组类型 ^

UserGroups 数据集包含层级用户分类(目录)数据。由于在 Membership+ 数据模式中,一个用户可以属于多个组,因此我们可以为各种用户分类方案使用同一个数据集,例如,用户可以属于组织中的某个部门、逻辑目录、项目或社交组等。所有这些都可以通过一个数据集,即 UserGroups 集,具有不同的组类型来表征。类型现在应该预先初始化。

UserGroupTypes 数据集的身份属性 ID 不是自动生成的。因此,它应由输入者手动分配。导航到 UserGroupTypes 页面,进入编辑+列表模式,然后输入一组您感兴趣的类型。以下是一个暂定列表:

图:暂定的用户组类型。

7.3.2 其他类型 ^

使用类似的程序,还将初步值插入到示例“Communication Types”和“UserAssociationTypes”数据集中。

7.3.3 提醒 ^

保存刚刚修改的数据集,因为我们使用的是内存关系数据后端。

保存后,会生成一个初始样本数据集,可用作起点,从中可以分支出各种测试。然后,将数据服务网站(而不是本文档的 Web 应用程序!)App_Data\MembershipPlus\Data 子目录中的文件复制到其他地方,以便始终可以将其复制回来重新开始。

8. 历史 ^

  • 2014-02-05。文章版本 1.0.0,首次发布。
  • 2014-02-22。文章版本 1.0.1,更新以支持 .Net 4.5.1。

如果读者对 Git 源代码控制系统有足够的了解,他/她可以在 github.com 上关注该项目的 Git 存储库。当前文章的源代码托管在 codeproject-1 分支上,即此处

© . All rights reserved.