GitHub hosted project:
Note: Each article in the present series could has different data schema for the data service. For historic reasons, they live on different source control branches. Therefore the ones in earlier articles may not be synchronized with later ones. Care should be taken when one tries to re-use data services and sample data in other articles.
Related articles
This is the first of a series of articles on the design, initialization and realization of an extended membership system called Membership+ that provides online membership management, member interaction, member directories of various kinds, advanced member querying, member communication, and member social networking, etc. functionalities.
The article describes the data schema and the process of setting up a development environment that will based upon by subsequent articles which provides details of implementations. It is also be of value to readers who need to setup such an environment for their own web development projects.
We chose an architecture that is service based and database agnostic. The system uses some of the latest stable frameworks available, including Asp.Net Mvc, WCF, HTML5+CSS3, LESS, jQuery, jQuery UI, knockoutjs, bootstrap, etc.
Online member management and coordination is needed by many organizations, like companies, government agencies, education institutions, clubs, internet service providers, many kinds of associations, etc.
The present system is an extension of the one published in previous articles "Service Based Membership Providers for ASP.NET" published here and "Membership Stores for ASP.NET" published here on the CodeProject, which do not have a user and role management interface that implements a certain application logic. The web interface for the data service can of course be used to query and/or manipulate membership related data inside a secured environment. But when one start to think about it he/she may realizes that such an administration setting has limitations. This is because only a few trusted technical oriented members (someone like a DBA) that is located inside of an organization can administrate members, other management oriented members who does real human resource management has to delegate the tasks to the said few members, "manually". If the size of an organization becomes large (say more than 100 people), a hierarchic management structure could be needed in which members shares responsibilities in an orderly fashion. The situation becomes more delicate when dealing with inter-member relationships. For example, if the evolution of the management structure of an organization is guarded by a group of current managers who share a sub-set of responsibilities, how to allow the system to support the proper rule based role assignment process without to much human interventions? It's obvious that roles have different importances; a role should not be assigned to anybody by a member with less important roles, otherwise the system will not be stable. In addition, the set of members having a common role also should have certain differences when dealing with other members of the same role group so that they can proceed according to established rules as much as possible without conflicts in actions before seeking assistance from outside of the system. The role system introduced in the previous article is however too simple to support such a system.
Also a membership system should have at least a few kinds of member directories so that member can be classified according to multiple schemes for various purposes, like to ease member finding, to represent organizational structure, to represent project belonging, to control propagation of information (in announcements, messaging, mail listing, etc). In addition to such a static classification system, a membership system should also allow member searching or filtering. It should be close to the quality of the corresponding smart structured query capability of the underlying data service.
A membership system should also support member relationships management, from simple contact listing to more sophisticated social graph circle navigation.
It should has a group based announcement and personal event scheduling system that could be shared by members in a group or by members related to the owner in certain social circles. All these activities should be doable on the internet outside of the secured environment of the membership data service.
To simplify the bootstrap, the important part, namely a native support for member communication, is not included in the current version of the system.
The Membership+ system is consisted of four major layers:
-
The data service. Member related relational data are abstracted and encapsulated into a common data service, in distributed and database agnostic fashion, that can be called by various applications that need to manipulate or make use of said data. The data service contains complete API for querying, query intelligence and manipulate member related data by clients that are able to communicate with the WCF service, it includes all Microsoft stack of .net frameworks, javascripts and other systems that has WCF adaptors. The data service are provided as a ready to use software package here.
-
Applications that are clients of the data service which enforce specific application logic, view styles, etc. The particular application to be described here is the membership administration application using which a user can maintain his/her own user data, administrators can manage members according to his/her roles and corresponding priorities. It is called "the web application" in the following.
It's not only a client of the data service, a web site, but also a json proxy service, or gateway, between the backend data service and the end client software, namely the javascripts running inside a user's browser. As it will be shown in the subsequent articles, the flexibility in javascript's typing system related to duck typing combined with the auto-generated rigorous and extensive typing system of the back-ends could allow a developer to take advantage of both worlds, making the system flexible to develop but yet easy to maintain, provided, or course, the two are balanced right.
-
Javascripts running inside of the browser. We adopted jQuery + knockoutjs combination. The former is very popular now so we shall not talk about it further. knockoutjs is used throughout the present system. It provides means to support MVVM pattern in browser applications. The (json) data view models and the corresponding views can now be separated and be bound together later declaratively. The javascript view models can be used and re-used in multiple views or even applications. For a data rich application, these models are tedious to create and maintain in a continously changing system. Fortunately, a large portion of them can be auto-generated.
The auto-generated views and javascript view models contained inside of the data service provide rich resources for application developers to reuse them at the application layer after minor modifications. Saving them time that could otherwise be spent in tedious, error prone and repetitive works.
- Layout in HTML5 + CSS3 (less) + jQuery UI + bootstrap. This combination allows the views of the web application be consistently designed, maintained and deployed to various devices, from desktop to mobile phones.
Figure: Service based Membership+ system.
The data schema of Membership+ is extended from the minimal one published in the previously article (see here). A good data schema is important since it determines the possibilities, flexibility, extensibility and maintainability of the system in the future. It is hoped that the following non-inclusive list of extensions could make it possible:
The Asp.Net attribute based role based declarative permission mechanism is sometime too simple and inflexible for more delicate organizational governance. However, thanks to the hierarchic role system here, it can be used to provide coarse grained permission using top level roles for entry points filtering.
In addition to that, a property of type integer called RolePriority
is added to the Roles
data set. The new property is used to characterize which role is permitted for a certain operation or has more priority (larger in value) than another one base on simple arithmetic comparisons. Having operation precedence and/or permission is important for hierarchic structured organizations, like enterprises or government agencies, etc., to function well. A numerical priority type for it makes the logic simpler.
A property of type boolean called CanBeDeleted
is added to the Roles
data set. Some roles should not be deleted at the application level, this is because in a role attribute based declarative authorization system (pre Asp.Net 5), the name of some roles are compiled into the application code. Deleteing such roles may have un-expected effects on the system. Now, applications can use the value of CanBeDeleted
of a Role
to enforce a logic to prevent deteting compiled roles, which in our system should be those top level general roles like Administrators
(see the following).
A property of type integer called SubPriority
is added to the UsersInRoles
data set. A role, which represents an administrative group, is sometimes a privilege characterization too coarse grained to be effective, like, when dealing with inter-person relations having the same role. The SubPriority
is introduced to the user/role association data entry make the distinction possible when needed.
A property AdminID
which is a foreign key that references a user in the Users
data set is added to the UsersInRoles
data set. It is used keep track of who is the last person that created or modified the role assignment.
A new data set UserRoleHistories
is added to keep track of detailed role assignments, for auditing purposes. The current system does not support a vote based role assignment so record in this data set could be used to make role assignment process accountable.
A user may have multiple email addresses for various purposes nowadays. To accormandate such a possibility, the Email
property is removed from Users
data set. It is moved to the UserAppMembers
data set. This change makes it possible for a user to have a different default e-mail address for each application, if he/she choose to.
User icon related data properties (Email
, IconImg
, IconMime
and IconLastModified)
are added to the UserAppMembers
data set. It allows a user to select different personal icons for each application that he/she is a member.
A new data set UserDetails
is added to contain self maintained personal detail information about a user. Currently it acts as placeholder that will be expended in the future.
Data sets that records user's communication channels are added. They are CommunicationTypes
and Communications
. They records various types of ways using which a user can be reached inside a particular application context (namely, it's application dependant).
Data sets that provide hierarchic directory classification of users are added. They are UserGroupTypes
, UserGroups
, UserGroupAdminRoles
, and UserGroupMembers
. Albeit the data service supports smart structured query against any data set with any complexity, including the Users
data set, a organization may still found it necessary to have more static hierarchic classifications schemes. In real life situations, a user can be classified in multiple ways. For example, a user may belong to one or more department(s) in an organization, he/she at the same time may also be a member of multiple cross department projects, topical groups, etc. Also the organization may publish logical member directory that may not be identical to any of the internal ones. These three data sets are introduce to cover all these possibilities, using a small collection of data sets. A record in UserGroupTypes
represents certain type of classification scheme. The data set UserGroups
is a self-referencing hierarchic set used to represent classifications. The records in UserGroupAdminRoles
assigns a set of roles that is responsible for managing the group and, implicitly, for its sub-group hierarchy when their own management roles are absent. The UserGroupMembers
set is used to associate a user with a user group so that a user can belong to multiple user groups and a user group can has multiple users.
Data set Announcements
. Used to publish announcement under possible application and/or user group contexts. The hierarchic nature of user groups make it possible for realizing an application logic that allows users to implicitly receive announcements in parent groups.
Data sets that represent how a user is related to others inside multiple types of social connections (graphs) are added. They are UserAssociationTypes
, UserAssociations
and UserAssocInvitations
. Members are related with each other in a way that sometimes are not equal. For example the relation of a person to his parents is not equivalent to the one the parents related to that person. So a member A should be related to, if any, member B using two records A to B record and B to A record, which may not belong to the same type, recorded in UserAssociationTypes
represents various types of relationships. The data sets can therefore be used to represent directional or non directional (like peer relation) social graphs amongst the members. Some kind of member association, like the peer type, requires mutual agreement, others are formed naturally or assigned by third parties. In the former case, there must be an initiator. The initiation is recorded in the UserAssocInvitations
data set, which will be available for the invited member to decide to accept, reject or put on hold. These data sets are introduced to enable clients to support from as simple as contact lists to as sophisticated as social networking systems. Want a facebook like system that have controllable social graph navigation? These data sets can be used to provide the relational data support.
Data sets that represent various timed events. They are EventTypes
, EventCalendar
and EventCalendarShareCircles
. Event can be visible under any combination of application, user group, individual and connected social circle (represented by records in EventCalendarShareCircles
) contexts. The hierarchic nature of user groups make it possible for realizing an application logic that allows users to implicitly share events in parent groups.
Figure: Schematic view of the data schema for Membership+ data service.
More details about it can be found in source code for the data models and in the corresponding documents hosted by the data service website. The documents are provided as separate downloads because they are quite sizable. The data models are available in source code. For each data sets, there are corresponding set, page and entity data models. The source code for them is contained inside the project under the Service\Shared
sub-directory of the downloaded package.
It service as a starting minimal data schema for the Membership+ data service at this bootstrapping phase of the system. Our production technology for the data service makes it possible for system to extend, branch out, or even shrink in small steps and directions that meet more specific application requirements in the future with reproduce-able quality and without much difficulties.
Try to create an Asp.Net MVC 5 web application in a proper directory first. In our case, the application was created using Visual Studio 2013 Express. Many parts of this web application is expected to be out of date when you set it up. Some efforts are required to properly update it so that the newly updated parts at least do not conflict with each other.
The directory layout under a selected root one should be like the following
Libs\MembershipPlusAppLayer
. It contains the project dealing with application (or business) logic. Libs\AppLayerTests
. It contains the project for unit testings. Service
. It contains projects handling communication between the web application and the Membership+ data service. WebSite\MemberAdminMvc5
. It contains member management web application.
There are two projects located inside the sub-directories Service\Shared
and Service\ServiceProxy\Desktop
of the root directory for the solution. The first one contains source code for shared data structures between the data service and the client (namely the web application under consideration) and the second one contains source code for the service proxies using which clients calls the data service. These two projects are available as part of the data service distribution.
There is very little changes to the Asp.Net membership stores and manager described here except that it is now configured to reference the above projects for the Membership+ data source. Since default membership stores and manager classes have predefined API that is not possible to extend further, we will use the functionalities of the stores and manger where it fits. The article will not go into any more details themselves which are available in the original article.
Those functionalities that are not covered by the data stores are implemented inside of the application layer described below.
An empty project is allocated for it at the current stage.
Please follow the steps given
here to transform the default Asp.Net Mvc 5 project.
In addition to these changes, the following has to be made for this more advanced project:
Add a new static boolean field MemberInitSuccess
to the Startup
class in the App_Start\Startup.Auth.cs
file.
-
Enclose the membership data service initialization block by a 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
{
}
}
Add a BaseController
class file to the wab application's Controllers
sub-directory, which is a base class that is used to contain common properties, methods to controllers of the application. All controllers of the application can derive from it.
For a dynamic web application like the present one, it's contents are more likely to be retrieved from relational data sources rather than from local files. Because of this, one can neither rely on the web server nor even the Asp.Net framework to effectively control the client side of caching for static or slowly changing contents. This is because neither the web server nor Asp.Net framework has information about whether or not the application specific contents were changed. It has to be done inside of specific web applications.
The initial one contains methods that can be called to manage client side caching of site contents base on their last modified property and/or etag property. It is found that the default attribute based cache control mechanism of Asp.Net Mvc 4.0 can not handle client side caching according to Http protocol well.
The methods CheckClientCache
and SetClientCacheHeader
contained are responsible for checking the client side caching status of a content and setting the client side caching of the same content base on the content's last-modified and/or etag properties, if any.
The method CheckMemberInitStatus
is used to check the initialization status of the web application as a client of the membership data service.
Change the controllers of the project (e.g. AccountController
) so that they are derive it from the BaseController
class.
Add a key="EnableClientCache"
item to the <appSettings>
node in the Web.config
file and initialize its value to false
.
The static cascade style sheet (CSS) only bootstrap package included in the default Mvc 5 project of Visual Studio is not easy to modify and customize. One must depends on "third parties" to provide themes for us. This is not what we want as developer because we could have more control due to the fact that bootstrap's "source code" is public and is in the form of dynamic CSS called LESS files where variables, nesting, mixins, operators and functions can be used to reduce information repetitions and maintenance costs, increase styling consistency.
Install the "Bootstrap Less Source" from nuget.org.
The variables.less
file contains major variables used by bootstrap to define styles. Being a general solution, the original one contains some hard coded values that, at least for our purposes, can be further reduced to relate to a few fundamental ones. @base-dark-color
is one of them that controls the overall color scheme of the site. The beauty of less and other similar "computable" style sheet generating system is that it can be used to "compute" the otherwise hard coded styles using some elementary style structures which could lead to more consistent visual effects (see the following).
Add the following variables to the top:
@base-dark-color: darken(#cc5000, 10%);
This is just a random selection. One could of course put more "dark color" here to choose from.
It is found that the following modifications to the variables.less
file have to be made in order to have what I considered a present view at our current usage coverage (of bootstrap). It is expected that more will likely to be needed in the future.
-
Redefine the global "gray" color:
@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%);
-
Redefine the global 'text' color:
@brand-primary: @gray-dark;
-
Relate the color scheme for the navigation bar to @base-dark-color
:
@navbar-inverse-color: @gray-light;
@navbar-inverse-bg: lighten(@base-dark-color, 10%);
@navbar-inverse-border: darken(@navbar-inverse-bg, 10%);
@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;
-
Relate the color scheme for buttons to @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%);
...
-
Relate the color scheme for forms to @base-dark-color
:
@input-border: lighten(@gray,10%);
...
@input-border-focus: lighten(@gray,5%);
The changes made will render the site less colorful (since it will be represented in fewer colors), but to my view, they will increase the consistency of the visual effects. Depending on the tasts, one could choose to keep the original one or make their own combination, of course.
Create a sub-directory less
under the Content
of the web application. Then add a .less file named Site.less
inside of the newly created folder. One should configure one's less compiling program to point the output file to the Site.css
file under the Content
sub-directory of the web application. Add the following to the top of the Site.less
file just created:
@import "..\bootstrap\bootstrap.less";
which will include all the bootstrap styles into the output Site.css
file.
For certain reasons, the new version of the bootstrap dropped the support for nested menus in favor of flat mobile styles. Since our sites still need to support desktop and large screen devices and the kind of information to be displayed is sophisticated enough that is hierarchic in nature, It seems that before we have a way of handling such information effectively, we still need nested menus. Fortunately, it is not realy hard to achieve. It is found that the following should be added to the Site.less
file to support nested menus:
.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;
}
Now one can remove "~/Content/bootstrap.css"
item from bundle "~/Content/css"
in the BundleConfig.cs
file under the App_Start
sub-directory, since it's already included inside of the Site.css
file (see above).
The common _Layout.cshtml
file under the Views\Shared
sub-directory of the web application is re-factored to factor out the common top bar of the web application to put it into a partial view so that it can be used by other views in addition to _Layout.cshtml
.
<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>
Which further includes the _LoginPartial.cshtml
to handle login/logout for a user, depending on the current login status of the user.
There are two views named Login.cshtml
and Register.cshtml
under the Views\Account
sub-directory. These two views contain information about demo users. One should remove the information after viewing the demonstration using the demo data sample (see the following), since such information will not be relevant for any other membership data sources.
The site should be ready to be run for the first time. If there is any problems, please let me know inside the comment/discussion forum below.
Figure: "Hello world!"
Changing the color scheme is easy after configurating the .less files following the above steps. One needs to change the @base-dark-color
variable only (see here). The following is a few example.
Figure: Sample skins.
Extract the files from the Membership+ Data Service package to a folder, configure a website for it (it is a ASP.NET MVC 4 web application). Enable the HTTP activation of WCF inside your system. That's basically it.
If you need to persist your changes, at least the App_Data\MembershipPlus\Data
sub-directory under the service site need to have proper permission. It should allow user IIS_IUSRS
the Write permission.
Warning: This is a demonstration/test version of the system for evaluation purposes. It's based on a transient serialization method. Please do not used it for long time data persistence or for supporting production systems.
There is a Documents
sub-directory under the root directory of the source code that contains a Sandcastle project that can be used to compile the documents for client site (namely the web application side) programming. But before compiling the documents, please make sure the following is done:
- Install Sandcastle, if not yet.
- The solution for the web application is compiled so that the xml document files are generated.
- The default output path for the documents are
..\..\DataService\WebSite\Documents\ClientAPI\
relative to the document directory. Make sure this is an allowed output directory. If not, change it to a proper one. - If the above output directory is not the same as the
Documents\ClientAPI
sub-directory of the data service website, remove all the contents of the later, if any, and copy all the generated document files inside of the former one to the later one.
The membership provider uses machine key (of the web application) to hash passwords and encrypt authentication cookies. To be more in control of your membership system (see, e.g., here), instead of using the system default one, one should set its values by adding the following node
<machineKey
validationKey="..."
decryptionKey="..."
validation="SHA1"
decryption="AES"
/>
under the <system.web>
node inside of the Web.config
file and generate a proper value for validationKey
and decryptionKey
. Of course one should not use the values provided in this article for any realistic usage.
There is a auto generated file named app.config
inside the Service\ServiceProxy\Desktop
sub-directory of the solution. It contains all end points which are exposed by the Membership+ data service. Copy the content of the
<system.serviceModel>
...
</system.serviceModel>
node there into the corresponding one in the web application's Web.config
file. The __servicedomain__
placeholder (or whatever the current value for it is) should be replaced by the actual root url of the Membership+ data service that was just configured inside of the reader's test environment, e.g., http://localhost/memberplus
. The instruction here could also be useful.
Insert the following nodes under the <appSettings>
node inside of the Web.config
file of the web application
<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"/>
where "MemberPlusManager" is the name of the application. Then the membership stores for the Membership+ data service is configured. For more details, one should read this article.
The data for the in-memory database is located inside of the App_Data\MembershipPlus\Data
sub-directory of the web application. An initial system for an application should start with an empty directory and setup the data according to application needs. This is introduced in the next section.
But for demonstration or testing purposes, it could be more productive not to start anew and setup the data each time. If that is the case, one can choose from one of the data sources included in the download packages above and copy all files there to the said directory. It could be helpful because it already contains some initial data, including pair of users (sysadmin and demo-user-a), roles (Administrators and Administrators > System) and the user's roles added. After copying, press the Load Database button to have it loaded.
If not yet, start the data service configured in section 5 first. Now start the web application in the debugger again using the selected sample data, which can be loaded following the above instructions.
Login under user name sysadmin, then the top navigation bar contains additional menus as shown in the following:
Figure: Top navigation bar shown after login.
Note that none of the new menus shown points to an implemented action at present.
After the demo, it's time for some actions. Delete all the file inside of the App_Data\MembershipPlus\Data
sub-directory of the web application and Load Database again. Run the web application (in debugger of course) next. Although it does not seems to have any difference from the previous "hello world" run on the surface, the application (with name defined in the provider configuration shown above, namely "MemberPlusManager") is registered automatically, if the system is configured correctly. One can display the application entry using the data service management front end, which is going to be described next.
The next step is to register a few initial users. It can be done inside the user registration page of the web application, as shown in the following:
Figure: User registration page.
In order to proceed further, one register at least one user, say "sysadmin", who will be the root user described in the following. Since we are using the in-memory relational data backend, one should save it to disk before application pool for the data service management site gets recycled after long idle time (see the following for instructions).
The data service has its own management website. It is a generic, most complete and smart data management front-end that is built according to an extended schema from the relational data schema introduced above. It is for system administrators inside of a secured local network, a majority of create, read, update and delete (CRUD) operations can be performed using this front-end alone.
However, it does not enforce more application specific logic/information beyond what could be encoded into the current extended data schema specifications for the auto-production system. Albeit more could be moved into the auto processing system in the future, there are always more specifics left for the web application layer to take care of.
This section provides a brief introduction to the use of the data service management front-end. One of the other goals of the process is to initialize Membership+ data source so that more users with proper authorization can manage memberships in an secured and controlled manner at any location on the internet through a more user friendly web interface. The current article and the ones following it are all about it.
The data management page is reached by clicking the "Data Source" tab from the home page.
The database page contains a list of all data sets (tables) and a schematic display of the data relationship inside of the data source.
Apart from the listing, there are different sets of operation buttons depending on the kind of supporting relational data backend used. For the current in-memory relational data backend, there are load and save database buttons, which can be used to manually control how data should be persisted during the development or testing. The database is loaded when the data service management site is started. If the database is loaded again manually after some non-trivial operations, then these operations will be discarded and the database restores its state when it is last loaded. If, however, the database is saved manually here after some non-trivial operations, the changes will be saved and the next time when the database is re-loaded, it will start from the state immediatly after the saving.
The data files for the in-memory relational data backend is located inside the App_Data\MembershipPlus\Data
sub-directory of the data service management website. The directory is absent initially. It will be created after the "Save Database" button is clicked for the first time. As it is expected, one can keep multiple versions of data (files) and load them (as whole) according to development needs.
Figure: Database page for in-memory demo/test/development relational data backend.
For relational data back ends that is supported by a traditional relational database engine, there is a "Create Database" button when the database is not created yet. After clicking the button, provided that data service is properly configured so that the operation is successful, the "Create Database" button will disappear the next time the page is refreshed.
The data set level pages provide CRUD operations for the user to perform. The there are to mode for the page, namely the ViewMode and the Edit Mode. The mode is controlled by the corresponding button on the top right corner of the page. Take the Applications data set for example:
Figure: Edit Mode transition button on the data set page in View Mode.
The page does not display any data by default. This is because the user has not specify sorting and filtering query conditions for the data to be placed on the first page.
The user interface is so constructed that it requires a user must specify at least one complete sorting expression (property + direction) before other operations can be performed. If a user is not familiar with the names of the properties of a data set that can be sorted, he/she can place the input cursor inside the textbox next to the "Sort By" label and use the down arrow key to have the available values to be listed. For example, the following is what a user sees when doing so for the Applications data set.
Figure: Applications view page, when initially loaded.
Select a value, say Name and the system will guide the user to provide the next available value. When a user is already familiar with the particular data set, he/she can always type in the first few letters of the name of the property so that the system can auto complete when enough information is provided.
After the sorting expression is in a complete state, the display button will appear, as shown in the following:
Figure: Applications view page, when sorting expression is complete. The display buttons appear and filtering box is opened.
Click the display button (in red circle), the first page of the data will be displayed. There is only one application at present. Its details can be viewed by selecting it:
Figure: The application auto-registered in the preview run of the web application discussed in the previous section.
As it is shown, the DisplayName
of the item is null
. One can set a more friendly name for the application. This can be achieved by going into the Edit Mode first. And then have the items displayed, following the same procedure as the one described above. The DisplayName property has a corresponding textbox. Enter a display name for it, like "Membership+ Management System", then click the Commit button on top to have it saved to the database, as shown in the following:
Figure: Modifications of a collection of Application entities are saved into the database when they are "Committed".
One can make multiple changes on multiple records before "committing". The Commit operation will save all changes into database in one step.
There is one more operation for the user to consider if the in-memory database is used as the backend. Namely, he/she can choose to persist the changes to disk or to rollback the changes or to leave it in-memory. As discussed above, persisting or rolling back of changes are performed inside the database page using the "Save Database" or "Load Database" button.
After warming up, we need to continue to initialize the membership management system.
Since this is a role priority based member management system, the web application (the manager) can not function without creating the root role and assign it to a root user using means outside of the web application. The root role must have the highest priority of the system and the root user must have the highest relative priority of root role. With this setting, the root user can then use the web application interface to add aditional high level management roles and assign them to a few selected users who can then do the same (to share the upper level manager's work load) until the entire administrative organization is formed, in an ordered fashion. This does not exclude the possibility that the root manager does all the initial assigment job alone in a small organization or in cases where the organization is at the stage of initial setup.
Go to the Roles data set page and enter the Edit Mode. Although the data set is empty, it is still recommended to try to list the data first, following the above procedure. When the page is in list mode, newly added items will be listed immediately after been added. Otherwise, the newly added items will only be visible when the user tries to list them!
Figure: Role data set page in edit and listing mode. The "Add" button is red circled.
Click the "Add" button and add the first category role "Administrators", as shown in the following.
Figure: Role addition dialog. Foreign keys ApplicationID and ParentID have buttons on the right to open the corresponding entity selection dialog.
Set proper value for the RoleName
, DisplayName
(optional) and the RolePriority
properties next. Since this is supposed to be a category role for a variety of administration roles, one should set a high enough priority for it. Here we set it to an arbitrary value 10000 since it is for demonstration purposes. A well designed role system should have easy remembered and intuitive values for the role priorities. Click the button next to ApplicationID
property to open the application selection dialog shown in the following.
Figure: Associated application selection dialog.
Find and select the newly created application using the above procedure and click the "OK" button. One should not select any ParentID for this top level role. Click "OK" button again and our first role "Administrators" are added to the data source, as shown in the following.
Figure: The newly added "Administrators" role.
The root role, named "System" is supposed to be under the categorical role just added. So one should follow the same procedure as above, selecting the same application for the ApplicationID
foreign key property and for the ParentID
foreign key, select the "Administrators" role just added. Resulting in the following role list.
Figure: Root role added. A display of the role hierarchy.
Note that all roles under a role hierarchy must reference to the same application. If not, the result is not expected!
OK, the minimum required roles are added. The next task is assign them to users.
We need to assign the root role "Administrators.System" to the root user "sysadmin". Go to the page for the UsersInRoles data set, enter the edit mode and then listing mode and add an association entity into the data set which have to RoleID
points to the root role and UserID
property points to the root user, having sufficiently large sub-priority (or relative priority). The AdminID
property can be left alone (set the null
value) since this is added outside of the system. After a successful adding, the following is displayed.
Figure: Newly added role assignment record.
The system has a data set called UsersRoleHistories
that records the role history of a user and who did the assignment, for auditing purposes. We do not need to enter any entry for the root one since it is done outside of the system.
UserGroups data set contains hierarchic user classification (directory) data. Since a user can belong to multiple groups in the Membership+ data schema, we can use the same data set for various user classifications schemes, for example a user can belongs to a certain department in an organization, logical directories, projects or social groups, etc.. All of these can be characterized by a single data set, namely the UserGroups set, having different group types. The type should be pre-initialized now.
The identity property ID
for the UserGroupTypes data set is not auto-generating. So it should be assigned by inputter manually. Navigate to the UserGroupTypes page, entering the edit + list mode and enter a set of types of interest. Here is a tentative list
Figure: Tentative user group types.
Preliminary values are also inserted into the sample "Communication Types" and "UserAssociationTypes" data sets using similar procedure.
Save the data sets just modified since we are using the in-memory relational data backend.
After saving, a initial sample data set are generated that can be used as a starting point from where various test can branch-out. Then copy the files inside the App_Data\MembershipPlus\Data
sub-directory of the data service website (not the web application of this article!) to somewhere else so it can always be copied back to start anew.
- 2014-02-05. Article Version 1.0.0, initial publication.
- 2014-02-22. Article Version 1.0.1, update to target .Net 4.5.1.
If a reader have sufficient knowledge about the Git source control system, he/she can follow the Git repository on github.com for the project. The source code for the current article is maintained on the codeproject-1 branch, namely here.