Real - World ASP .NET—Building a Content Management System - StephenR. G. Fraser
.pdf
<MenuName>Author</MenuName>
<MenuItem>
<Name>List Content</Name> <Link>Aut/AutList.aspx</Link >
</MenuItem>
<MenuItem>
<Name>Create Content</Name> <Link>Aut/AutCreate.aspx</Link>
</MenuItem>
</Menu>
<Menu>
<MenuName>All</MenuName>
<MenuItem>
<Name>Welcome</Name>
<Link>Welcome.html</Link>
</MenuItem>
</Menu>
</MainMenu>
The NavBar Codebehind (see Listing 12-19) to handle the updated NavBar is really quite
simple. All it does is check to see if the first element in the menu is <authorization>. If it is, it grabs all the roles that the menu supports using its own Roles() method and then compares it to all the roles that the user performs. If there is a match, the menu is displayed, just like it was originally.
Listing 12-19: NavBar Updated Codebehind with Authorization
private void Page_Load(object sender, System.EventArgs e)
{
bool authorized = false;
...
for (int i = 0; i < Menus.Count; i++)
{
int currnode = 0;
XmlNodeList MenuNodes = Menus[i].ChildNodes;
if (MenuNodes[currnode].Name.Equals("authorization"))
{
AppEnv appEnv = new AppEnv(Context); AccountRoles accountRoles =
new AccountRoles(appEnv.GetConnection());
if (accountRoles.Authorization( Roles(MenuNodes[currnode++].InnerText), User.Identity.Name))
{
authorized = true;
}
else
{
authorized = false;
}
}
else
{
authorized = true;
}
if (authorized)
{
...
}
}
}
public ArrayList Roles (string role)
{
ArrayList list = new ArrayList();
string[] temp = role.Split(',');
for (int k = 0; k < temp.Length; k++) list.Add(temp[k]);
return list;
}
Account Maintenance
One of the strong points of CMS.NET's approach to role-based authentication and authorization is that the user account and role information is stored with the rest of the CMS.NET data. Thus, you don't have to leave the CMS.NET system for user management. These databases were covered earlier in the chapter. It is up to the account maintenance portion of the administration system to maintain these databases. The administration system handles the creating, updating, removing, and viewing (CURVe) of accounts and the roles they perform. The code to handle CURVe is almost identical to that of the authors covered in Chapter 11, so much so that I will only cover the differences. As always, the full code can be found on the Apress Web site in the Downloads section.
List
The first thing to add is the AdmAcntList Web page, which looks like Figure 12-4.
Figure 12-4: The AdmAcntList Web page
There is really no difference between the AdmAcntList Web page and the AutView Web page covered in Chapter 11, except that the AdmAcntList Web page does not support submit. It displays account columns (obviously), and the Administrator account can only be updated by the Administrator account itself (and it can never be deleted). Listing 12-20 shows the code that handles the special administrator scenarios.
Listing 12-20: The AdmAcntList Codebehind for Administrator Scenarios
private void Page_Load(object sender, System.EventArgs e)
{
...
foreach (DataRow dr in dt.Rows)
{
...
if (dr["AccountID"].ToString().Trim().Equals("1"))
{
int i = 0;
//is the current user the administrator
//allow viewing and updating
if (dr["UserName"].ToString().Trim().Equals( User.Identity.Name.Trim()))
{
BuildImageButton(row, "AdmAcntView.aspx?AccountID=" +
dr["AccountID"].ToString());
BuildImageButton(row, "AdmAcntUpdate.aspx?AccountID=" +
dr["AccountID"].ToString());
i = 2;
}
for (; i < 3; i++)
{
lit = new LiteralControl(" "); cell = new TableCell(); cell.Controls.Add(lit); row.Cells.Add(cell);
}
}
else
{
BuildImageButton(row, "AdmAcntView.aspx?AccountID=" + dr["AccountID"].ToString());
BuildImageButton(row, "AdmAcntUpdate.aspx?AccountID=" +
dr["AccountID"].ToString());
BuildImageButton(row, "AdmAcntRemove.aspx?AccountID=" + dr["AccountID"].ToString());
}
}
}
Create
The account administration would be quite useless if you couldn't create a new account. Figure 12-5 shows the AdmAcntCreate Web page that is designed for this task.
Figure 12-5: The AdmAcntCreate Web page
Looking at Figure 12-5, you should spot something you haven't used before: the multiselect list box. The list box stores all the possible roles in the system that a user can have. Listing 12-21 shows the design code specific to the list box.
Listing 12-21: The Multiselect List Box
<H3>Roles:</H3>
<FONT size=2>
(Select role or ctrl click to select more than one role)</FONT>
<br>
<asp:ListBox id=lbRoles runat="server" Width="40%" SelectionMode="Multiple">
</asp:ListBox>
The Codebehind to handle the list box (see Listing 12-22) is pretty straight-forward. The first time the page is built, the Role list box is loaded from an auxiliary database table called Roles that is made up of one column: Role. Then, when the user enters a valid page, you check to see if the username is already taken. If it is, you error out; otherwise, you insert all the information into the database.
Listing 12-22: The AdmAcntCreate Codebehind
private void Page_Load(object sender, System.EventArgs e)
{
if (!IsPostBack)
{
string Cmd = "Select * FROM Roles";
SqlDataAdapter DAdpt = new SqlDataAdapter(Cmd, appEnv.GetConnection());
DataSet ds = new DataSet();
DAdpt.Fill(ds, "Roles");
DataTable dt = ds.Tables["Roles"];
foreach (DataRow dr in dt.Rows)
{
lbRoles.Items.Add(dr["Role"].ToString());
}
}
else
{
account = new Account(appEnv.GetConnection());
property = new AccountProperty(appEnv.GetConnection());
accountRoles = new AccountRoles(appEnv.GetConnection());
Page.Validate();
if (Page.IsValid)
{
try
{
if (account.GetAccountID(tbUserID.Text) > 0) lblError.Text = "UserID already in use";
}
catch (Exception)
{
try
{
account.Insert(tbUserID.Text, tbPassword.Text, tbEmail.Text);
int AccountID = account.GetAccountID(tbUserID.Text); ProcessUserName(AccountID); ProcessAccountRoles(AccountID);
Response.Redirect("AdmAcntList.aspx");
}
catch (Exception err)
{
Page_Error("The following error occurred "+ err.Message);
}
}
}
}
}
private void ProcessAccountRoles(int AccountID)
{
for (int i = 0; i < lbRoles.Items.Count; i++)
{
if (lbRoles.Items[i].Selected)
{
accountRoles.Insert(AccountID, lbRoles.Items[i].Text);
}
}
}
The ProcessAccountRoles() method simply goes through the list, and if a row is selected, it is added to the AccountRoles table.
Update
The AdmAcntUpdate Web page is very similar to the create Web page, as you can see in Figure 12-6. The only big difference is that it comes prepopulated with the account information that needs to be updated.
Figure 12-6: The AdmAcntUpdate Web page
The only thing of note code-wise is how the multiselect list box is built. As you can see in Listing 12-23, you build a DataSet of all the roles found in the auxiliary database Roles. Then you compare them row by row with what is in the AccountRoles database. If the row is found in both the Roles DataSet and the AccountRoles database table, the selected property is set to true. Obviously, if the reverse is true, the selected property is set to false.
Listing 12-23: The AdmAcntUpdate Codebehind
private void Page_Load(object sender, System.EventArgs e)
{
...
if (!IsPostBack)
{
...
DataTable roledt = roles.GetRolesForID(aid);
string Cmd = "Select * FROM Roles";
SqlDataAdapter DAdpt = new SqlDataAdapter(Cmd, appEnv.GetConnection());
DataSet ds = new DataSet();
DAdpt.Fill(ds, "Roles");
DataTable allRolesdt = ds.Tables["Roles"];
foreach (DataRow dr in allRolesdt.Rows)
{
ListItem li = new ListItem(dr["Role"].ToString());
foreach (DataRow adr in roledt.Rows)
{
if (dr["Role"].ToString().Equals(adr["Role"].ToString()))
li.Selected = true;
}
lbRoles.Items.Add(li);
}
if (aid == 1)
{
bnRemove.Visible = false;
lbRoles.Enabled = false;
}
}
}
Remove
As you can see in Figure 12-7, there is virtually no difference between AdmAcntRemove and AutRemove except the obvious account and content differences.
Figure 12-7: The AdmAcntRemove Web page
The only thing of interest in the code is the line to get the username:
lbUserName.Text =
property.GetValue(Convert.ToInt32(dt.Rows[0]["AccountID"]), "UserName");
If you remember, the username is stored in the AccountProperty table, not the Account table. You might think that this overly complicates things, but as you can see in the AccountProperty database helper GetValue() method (see Listing 12-24), it is actually very easy to extract the needed information. It is just a standard select on the key fields. If the key exists, it returns the value; otherwise, it returns an empty string value.
Listing 12-24: The AccountProperty Database Table Helper GetValue Method
public string GetValue(int AccountID, string Property)
{
//SELECT Value
//FROM AccountProperty
//WHERE (AccountID = @AccountID AND Property = @Property)
SqlCommand Command = new SqlCommand("AccountProperty_GetValue", m_Connection);
Command.CommandType = CommandType.StoredProcedure;
Command.Parameters.Add(new SqlParameter("@AccountID", SqlDbType.Int));
Command.Parameters.Add(new SqlParameter("@Property", SqlDbType.Char, 32));
Command.Parameters["@AccountID"].Value = AccountID;
Command.Parameters["@Property"].Value = Property;
string retval = "";
try
{
m_Connection.Open();
SqlDataReader dr = Command.ExecuteReader();
if (dr.Read())
{
retval = dr["Value"].ToString();
}
}
finally
{
m_Connection.Close();
}
return retval;
}
The versatility of being able to add any information about the user you want, without having to change the database schema, quickly overshadows any complication that the
AccountProperty might present.
View
Let's finish this chapter with the AdmAcntView Web page (see Figure 12-8). There is no new code in the Web page at all, but it still is a necessary Web page because it provides a safe way to view an account. There are no fields to edit, so there is no chance that it might accidentally get updated. This Web page could be used by the administrator to see the last time the user logged on and for how long (or a myriad of other things), but for now, let's just show generic user information.
