Real - World ASP .NET—Building a Content Management System - StephenR. G. Fraser
.pdf
if (accountNo == 1) // Admin sees all
{
dt = content.GetHeadlines();
}
else
{
dt = content.GetHeadlinesForEdit(accountNo);
}
...
foreach (DataRow dr in dt.Rows)
{
...
if (IsTypeRequested(dr["Status"].ToString(), Convert.ToInt32(dr["Editor"])))
{
...
}
}
}
private bool IsTypeRequested(string status, int editor)
{
switch(Convert.ToInt32(ddlWhichContent.SelectedItem.Value))
{
case 0:
return (StatusCodes.isAwaitingEdit(status) || StatusCodes.isEditing(status));
case 1:
if (editor == 0) return false;
return (StatusCodes.isAwaitingEdit(status) ||
StatusCodes.isRequiresUpdate(status) ||
StatusCodes.isEditing(status) ||
StatusCodes.isAwaitingApproval(status));
case 2:
if (editor == 0)
return false;
return true;
default :
return false;
}
}
Something to note about the GetHeadlinesForEdit() method is the OR condition added to the WHERE clause. Before an editor selects a piece of content to be edited, it is assigned the value of 0. CMS.NET, knowing this, is able to display for the editor a list of all unselected content, as well as content that is specific to the current editor, by selecting both the content with the editor's ID as well as content that has no ID assigned to it.
You might have noticed in the IsTypeRequested() method that if the editor is zero, the second two options return false. This is because those options don't want to view unassigned content.
Listing 14-12: Content Database Helper GetHeadlinesForEdit Method
public DataTable GetHeadlinesForEdit(int Editor)
{
//SELECT ContentID, Version, Headline, Status
//FROM Content
//WHERE Editor=@Editor
//OR Editor=0
SqlCommand Command = new SqlCommand("Content_GetHeadlinesForEdit", m_Connection);
Command.CommandType = CommandType.StoredProcedure;
Command.Parameters.Add(new SqlParameter("@Editor", SqlDbType.Int));
Command.Parameters["@Editor"].Value = Editor;
SqlDataAdapter DAdpt = new SqlDataAdapter(Command);
DataSet ds = new DataSet();
DAdpt.Fill(ds, "Content");
return ds.Tables["Content"];
}
Differences Between Author and Editor Processes
There is no difference between the author and editor's ID, Ver, Headline, Status, View, and Notes columns. In fact, they use almost exactly the same code.
Editor Version Control
The first major difference happens when the editor selects a piece of content to edit. Unlike the author process, in which the only person who has access to a piece of content is the author, in the edit process, all editors have access to the piece of content when it first becomes available. The first thing the edit process must do is restrict the piece of content that is to be edited to one editor.
In CMS.NET, this happens immediately when an editor selects a piece of content for editing within the EdEdit.aspx Web page (see Figure 14-6). The restricting of the content is accomplished by simply setting the editor column in the database to the current author and then setting the status to Editing. If you remember from Chapter 2, this process is how CMS.NET handles version control. This whole process is completely transparent to the editor.
Figure 14-6: The EdEdit Web page
It is a little tricky to handle record locking. You have to remember that more than one person can access the content at exactly the same time. It also is possible to select the content at nearly the same time. It is also possible for an editor to select a piece of content that may be in the process of being selected by another editor. Thus, it is necessary to make sure that an editor who selects content is, in fact, the editor of that content.
CMS.NET takes the simple approach in the EdEdit Codebehind shown in Listing 14-13. The first thing the Page_Load() method does is check to see if the content has been assigned to an editor. If it has not, the SetAsEditor() method is called. This method simply calls the Content database helper method SetEditor() (see Listing 14-14) to assign the current content to the current editor. It then makes one final check to make sure it successfully allocated this content to the current editor.
Listing 14-13: The EdEdit Codebehind
private void Page_Load(object sender, System.EventArgs e)
{
int cid = Convert.ToInt32(Request.QueryString["ContentID"]);
if (cid == 0)
{
Page_Error("ContentID Missing");
}
dt = new Content(appEnv.GetConnection()).GetContentForID(cid);
if (!IsPostBack)
{
if (StatusCodes.isAwaitingEdit(dt.Rows[0]["Status"].ToString())) SetAsEditor();
BuildOrigPage();
}
}
private void SetAsEditor()
{
int id;
Content content = new Content(appEnv.GetConnection()); Account account = new Account(appEnv.GetConnection()); DataRow dr = dt.Rows[0];
content.SetEditor(Convert.ToInt32(dr["ContentID"]),
Convert.ToInt32(dr["Version"]),
(id = account.GetAccountID(User.Identity.Name)));
dt = new Content(appEnv.GetConnection()).GetContentForID( Convert.ToInt32(dr["ContentID"]));
// Only one person can edit a piece of content
if (id != Convert.ToInt32(dt.Rows[0]["Editor"]))
Page_Error(" <h3>Too Slow!!</h3>Someone is editing this already");
}
Listing 14-14: The Content Database Helper SetEditor Method
public void SetEditor(int ContentID, int Version, int Editor)
{
//UPDATE Content
//SET
// |
Status |
= |
@Status, |
|
|
|
|
|
|
// |
Editor |
= |
@Editor, |
|
//ModifiedDate = @ModifiedDate
//WHERE ContentID = @ContentID
//AND Version = @Version
//AND Editor = 0
SqlCommand Command = new SqlCommand("Content_SetEditor", m_Connection); Command.CommandType = CommandType.StoredProcedure;
Command.Parameters.Add(new SqlParameter("@ContentID", SqlDbType.Int));
Command.Parameters.Add(new SqlParameter("@Version", SqlDbType.Int));
Command.Parameters.Add(new SqlParameter("@Editor", SqlDbType.Int));
Command.Parameters.Add(new SqlParameter("@Status", SqlDbType.Int));
Command.Parameters.Add(new SqlParameter("@ModifiedDate",
SqlDbType.DateTime));
Command.Parameters["@ContentID"].Value |
= ContentID; |
|
||
|
|
|
||
Command.Parameters["@Version"].Value |
= Version; |
|
||
|
|
|||
Command.Parameters["@Editor"].Value |
= Editor; |
|
||
|
|
|||
Command.Parameters["@Status"].Value |
= StatusCodes.Editing; |
|||
Command.Parameters["@ModifiedDate"].Value = DateTime.Now;
try
{
m_Connection.Open();
Command.ExecuteNonQuery();
}
finally
{
m_Connection.Close();
}
}
The SetEditor() method shown in Listing 14-14 is not quite as simple as it first seems, as the key to only one editor having access to the content is hidden in it. Databases allow only one user to update a database table one row at a time. Thus, by only allowing the Content table row to be updated, if the editor is equal to zero, this basically eliminates two editors from updating the content. Once one of them has updated the content, the value of the editor column no longer will be zero.
This also shows the reason for the final if statement in the SetAsEditor() method in Listing 14-13. If SetEditor() returns without being able to update the content of the Content database, it will have the value of the other editor who snuck in before. This, of course, will cause this error to be presented to the editor.
Giving Other Editors Access to Content
At times, an editor will need to relinquish control over the content she is editing because she is unable, for some reason, to complete the editing process. Because CMS.NET allows only one editor at a time, CMS.NET needs to add Withdraw to the editor list of functions (see Figure 14-7).
Figure 14-7: The EdWithdraw Web page
Basically, Withdraw sets the editor back to zero and sets the status back to AwaitingEditing. It also creates a new version of the content so that a record of the previous editor's work is maintained. As you can see in the EdWithdraw Codebehind
(see Listing 14-15), the code to handle the editor relinquishing control is very similar to all the submit Codebehinds.
Listing 14-15: The EdWithdraw Codebehind
public class EdWithdraw : PageEx
{
protected System.Web.UI.WebControls.Label lbWhichBody; protected System.Web.UI.WebControls.Label lbWhichHeadline; protected System.Web.UI.WebControls.Button bnWithdraw;
private Content content; private int cid = 0; private DataTable dt;
private void Page_Load(object sender, System.EventArgs e)
{
cid = Convert.ToInt32(Request.QueryString["ContentID"]);
if (cid == 0)
{
Page_Error("ContentID Missing");
}
content = new Content(appEnv.GetConnection());
dt = content.GetContentForID(cid);
lbWhichHeadline.Text = dt.Rows[0]["Headline"].ToString(); lbWhichBody.Text = dt.Rows[0]["Body"].ToString();
}
private void bnSubmit_Click(object sender, System.EventArgs e)
{
try
{
int code;
Content content = new Content(appEnv.GetConnection()); Account account = new Account(appEnv.GetConnection());
DataRow dr = dt.Rows[0];
content.Insert(cid, Convert.ToInt32(dr["Version"]) + 1, dr["Headline"].ToString(), dr["Source"].ToString(), Convert.ToInt32(dr["Byline"]), dr["Teaser"].ToString(), dr["Body"].ToString(), dr["TagLine"].ToString(), 0, Convert.ToInt32(dr["Approver"]), account.GetAccountID(User.Identity.Name),
(code = StatusCodes.AwaitingEdit));
EmailAlert ea = new EmailAlert(Context, code, 0); ea.Send();
Response.Redirect("EdList.aspx");
}
catch (Exception err)
{
Page_Error("The following error occurred: "+ err.Message);
}
}
private void bnCancel_Click(object sender, System.EventArgs e)
{
Response.Redirect("EdList.aspx");
}
}
First, it displays the content so that the editor can verify that this is indeed the content of which she wants to relinquish control. Then, when the editor clicks the Withdraw As Editor button, a new version of the content is created with an editor of zero and a status of AwaitingEdit. Next, an EmailAlert is sent so that the other editors will be notified of the content's availability. Finally, it jumps back to the list Web page where the content should be listed as available again.
Returning Content to the Author
The final difference between the author and editor processes is that an editor can return the content back to the author for updating using the EdReturn Web page (see Figure 14-8).
Figure 14-8: The EdReturn Web page
Code-wise, this process is no different than submitting the content to the next phase of the workflow. In fact, it is sending it to the next phase. It just happens that the next phase is back to the author. The code is very similar to Listing 14-15 except for the bnSubmit_Click() method (see Listing 14-16). This method only sets the status to RequiresUpdate (leaving the editor unchanged) so that when the author resubmits the updated content, it gets routed immediately back to the same editor.
Listing 14-16: The EdReturn Codebehind
private void bnSubmit_Click(object sender, System.EventArgs e)
{
int code;
content.SetStatus(Convert.ToInt32(dt.Rows[0]["ContentID"]),
Convert.ToInt32(dt.Rows[0]["Version"]),
(code = StatusCodes.RequiresUpdate));
