Real - World ASP .NET—Building a Content Management System - StephenR. G. Fraser
.pdf
Content content = new Content(appEnv.GetConnection());
AccountProperty property = new AccountProperty(appEnv.GetConnection());
DataRow dr = content.GetContentForIDVer(curId, curVer);
if (dr != null)
{
lbHeadline.Text = dr["Headline"].ToString();
lbSource.Text = dr["Source"].ToString();
lbByline.Text = property.GetValue(Convert.ToInt32(dr["Byline"]),
"UserName").Trim();
lbDate.Text = dr["ModifiedDate"].ToString();
lbTeaser.Text = dr["Teaser"].ToString();
lbBody.Text = dr["Body"].ToString();
lbTagline.Text = dr["Tagline"].ToString();
}
else
{
lbHeadline.Text = "No Stories";
lbBy.Visible = false;
lbDashes.Visible = false;
}
}
In a case in which the content is not found in the database, a "No Stories" message is displayed instead. This should actually never happen, but it is possible that an inquisitive user might try to search for stories by entering content IDs and versions manually in the Address edit field of the browser.
Deploying Content
All the code in this chapter is quite useless if you can't load it with the content from the repository. Content that is in the process of being created should not be accessible for viewing on the Web site. Only after it is completed and approved should content be available. It is ultimately the job of the deployment process to make content available to users of the Web site. Deploying content is the last step of CMS.NET's workflow. Because workflow is the topic of the next chapter, I am going to delay covering this until then. For those of you who want to add content and see it displayed, however, a simplified version of the source code for deploying content (see Figure 13-15) is included on the Apress Web site (www.apress.com) in the Downloads section. Using this is easy. Submit the content from the content development area and then deploy it from the site maintenance area. It is also possible to see all the stories deployed to all zones, or to a particular zone, and shift the viewing order of the stories.
Figure 13-15: The Deploy Web page
Summary
This chapter covered the displaying of dynamic content.
It started by covering the basics of what dynamic content is and how it uses a three-level approach to dynamic content. Next, it took a little side trip and covered stopping and starting CMS.NET so that the user never gets the ugly "The page cannot be found" error. It then got back on topic and described the new database tables needed to handle threelevel navigation. Next, it covered User Controls and you created a few. Finally, you implemented the default dynamic Web pages using these User Controls.
The next chapter returns to the content management application (CMA), which you started building in Chapter 11, and adds a simple workflow.
Chapter 14: Using a Workflow to Enter Content
Overview
Workflow is one of the most important features of a content management system (CMS), so much so that Chapter 3 was devoted entirely to covering the theory behind it. In this chapter, on the other hand, you get to have some fun and actually develop the workflow for CMS.NET.
CMS.NET's workflow isn't the most elaborate, but it does have all the pieces needed to
develop a content management application (CMA) that supports the following: • Multiple users
§Internet maintainability
§Multiversion content
§Interstate communication
§State change e-mail events
§Role-based content development
§Role-based security
§Repeatable process
It supports a few other features that I'm sure I missed, but I'd say this isn't too bad for one chapter's worth of work.
CMS.NET Content Workflow
Most of the more expensive CMSs on the market support a fully dynamic workflow generator. These systems enable the Web system developer to generate a workflow however she wants. I suppose that having this capability could merit a little higher price tag than, say, the hard-coded workflow supported by CMS.NET. Not the few hundred thousand dollars difference but maybe a few thousand dollars at least. On the other hand, these systems don't provide full source code so that you can change the workflow to suit your exact needs.
Figure 14-1 shows CMS.NET's content development workflow. It starts with the author creating the content. When the author is happy with the content, he passes it to an editor who edits it. When the editor is happy, it is passed to an approver, who gives the final rubber stamp and passes it on to the deployer for deployment.
Figure 14-1: The content development workflow
Each piece of content can maintain a running commentary or audit using dated notes attached to it. This facility also can be used to handle all communication between the roles when the content moves from state to state. Keeping the notes separate from the content means no cleanup of the document is needed at the end to remove all unwanted commentary.
When a piece of content migrates from one state to another, CMS.NET will automatically send e-mail notification of the content's availability to all persons that fill the next role in the workflow. As soon as a specific person is designated to that role, only she will get the e-mail notifications. This helps people manage their time better because they no longer have to continually monitor the CMS for new content. Instead, they will be alerted by a simple e-mail.
Content is available on a first-come first-served basis for each role. In other words, the first user to start processing the content when it becomes available is given complete control of it until he relinquishes it to the next role in the workflow.
Unlike many content workflows, nothing enforces an author review, but the editor can handle this procedurally by returning the content to the author before she forwards it for
approval. Forcing this review into the workflow would only require a few minor changes to the code, which I will leave to the reader as an exercise.
It is assumed that testing in this workflow is done throughout the process. Adding an additional role to handle testing should not be difficult. As you will see, much of the functionality in each role is repeated. This will also be the case for the tester role.
CMS.NET Roles
CMS.NET only uses four roles to handle content development:
§Author
§Editor
§Approver
§Deployer
Author
The author's role is to create the original content that the Web site will provide. Unlike the more expensive CMSs on the market, authors for CMS.NET have to be HTML knowledgeable so that they can format their content. For example, paragraphs require
the <P> tag, images use the <img> tag, and boldface uses the <B> tag. In fact, all HTML formatting tags are supported.
All text entered into the edit boxes ignores spaces and new lines, so any manual formatting that the author does (that isn't done using HTML) is ignored when it finally displays on the Web site.
It is true that a good CMS enables an author to not worry about HTML when creating content. To keep the program simple, I overlooked this fact, but nothing is stopping you from augmenting the editor. An easier solution (and the one that I use) if you need any elaborate formatting is to use FrontPage or an equivalent tool and just cut and paste the
HTML generated within the <body></body> tags into the edit boxes.
Figure 14-2 shows a little of what is available to an author in the way of HTML formatting. By the way, I created this Web page first using FrontPage to save time.
Figure 14-2: The Elaborate Web page
As you can see in Listing 14-1, which shows the actual HTML used to create the Elaborate Web page, the code contains bullets, a table, two images, and a numbered
list. You might have noted the <BR Clear=" all"> tag at the end. This little nifty tag makes sure that all subsequent HTML is placed after this visually on the Web page. If
you leave it off, sometimes you might find that the tagline is embedded in the middle of your body.
Listing 14-1: The Elaborate Web Page's HTML
<p>This is an elaborate test page with:</p>
<ul>
<li>Bullets</li>
<li>Bullets</li>
</ul>
<table border="1" width="60%" cellpadding="6" >
<tr>
<td width="75%" bgcolor="#CCFFFF">A table with one</td>
<td width="25%" bgcolor="#FFCCFF">two</td>
</tr>
<tr>
<td width="75%" bgcolor="#FFCC99" > three with a picture
<img border="0" align=" right"
src=" http://localhost/CMSNET/Images/steph.gif"
width="180" height="180" >
</td>
<td width="25%" bgcolor="#99FFCC" >four cells</td>
</tr> </table>
<ol>
<li>A numbered</li>
<li>list</li>
<li>of stuff</li>
</ol>
<p>And some more text to end it off</p>
<BR clear=" all">
Editor
The editor does have a more specific role in most content management systems, but in CMS.NET it is simply a second person to look over the piece of content with the authority to update as she sees fit.
Again, to keep things simple, I merged many roles into the editor role. In CMS.NET, the editor fills all the roles normally broken up into editor, copy editor, proofreader, compositor, and so on. Nothing is stopping you from adding these roles, and I'm sure you will find that much of the code is already provided.
If you have authors who are not HTML savvy, the editor's role could be used to add HTML formatting to the author's content.
As you can see, having a dynamic way of creating a workflow can come in handy. Here's a note to managers reading this: It took me only one week to write this entire workflow. If your software developers are saying it will take months to update it, they are yanking your chain, or you need better developers.
Approver
This role has the last say before the content is deployed in the Web site. CMS.NET only needs one person to approve the content, but it will not take much effort to change it to require more than one.
This role does not have the ability to make any changes to the content. He approves it, sends it back to the editor for more revisions, or withdraws it altogether. As I said in Chapter 3, this workflow has a chance of being wasted effort if the approver withdraws the content at this point. Hopefully, the editor and approver have already had some communication about the content before it gets to this point.
Deployer
This role simply takes the content and places it into the Web site repository. At this point, all the deployer does is place the content into its default content zone(s). If you were to add personalization to the Web site, this role would most likely expand.
The deployer also, frequently, takes content off the main Web site and places it into an archive. Since CMS.NET does not support archiving, the archive process just sends the content back to the editor for more revisions. (I did this so that I could test content workflow without having to keep creating new content.)
Interrole Communication
Let's get back to some coding. CMS.NET uses two methods to handle communication between roles in the workflow: notes and e-mails. CMS.NET uses notes as a means to provide a running commentary on the content and its development. E-mails, on the other hand, are used as an instant notifier of content availability.
Content Notes
Notes are simply time-stamped messages attached to a piece of content. They contain almost any kind of information. They are free format and, unlike content formatting that is manually typed in, will remain. HTML tags do not work.
The ContentNotes Database Table
This is the only new database table in this chapter. It is designed to hold content notes for a specific Content ID. The design of the database table, as shown in Table 14-1, is purposely simple in nature. It enables almost any type of textual information to be stored as a comment to the content.
Table 14-1: The ContentNotes Database Table Design
COLUMN NAME |
|
DATA |
|
LENGTH |
|
KEY |
|
DESCRIPTION |
|
|
TYPE |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NoteID |
|
int |
|
4 |
|
true |
|
Note identifier |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
ContentID |
|
int |
|
4 |
|
false |
|
ContentID for |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
which this |
|
|
|
|
|
|
|
|
content |
|
|
|
|
|
|
|
|
addresses |
|
|
|
|
|
|
|
|
|
Note |
|
text |
|
16 |
|
false |
|
The note text |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
Author |
|
int |
|
4 |
|
false |
|
Author of the |
|
|
|
|
|
|
|
|
Table 14-1: The ContentNotes Database Table Design |
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
COLUMN NAME |
|
DATA |
|
LENGTH |
|
KEY |
|
DESCRIPTION |
|
|
|
|
TYPE |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
note |
|
|
|
|
|
|
|
|
|
|
|
|
|
ModifiedDate |
|
datetime |
|
8 |
|
false |
|
Date domain |
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
was last |
|
|
|
|
|
|
|
|
|
|
changed |
|
|
|
|
|
|
|
|
|
|
|
|
|
CreationDate |
|
datetime |
|
8 |
|
false |
|
Date domain |
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
was created |
|
|
|
|
|
|
|
|
|
|
|
|
Notes Implementation
The implementation of content notes simply involves the standard CURVe processes. For those of you who don't remember, the CURVe processes are creating, updating, removing, and viewing. As you can see in Figure 14-3, the design of the notes process closely resembles that of the author's content development process covered in Chapter 11.
Figure 14-3: The NotesList Web page
No new coding is used to develop the notes process. It is really just a simplified cut and paste of the author's CURVe process. Because there is nothing new, none of the code will be displayed, but like all the source code, it can be found on the Apress Web site (www.apress.com) in the Downloads section.
The thing to remember about the notes process is that there is only one copy of the source. All the roles will use the exact same code to handle the maintenance of notes, unlike much of the rest of the common functionality shared by the roles, which have their own copy.
CMS.NET automatically sends an e-mail whenever the content is sent to another role for processing. If the person fulfilling the role has already been determined, that person will be the only one receiving the e-mail. Otherwise, when the role changes, all persons in the next role in the process will be notified.
Creating e-mails is extremely easy using .NET. In fact, it can take as few as six lines of code. The first five build the e-mail message, and the final one sends the e-mail on its way.
MailMessage mail = new MailMessage();
mail.To |
= "target_email@address.com"; |
mail.From = "your_email@address.com";
mail.Subject = "Subject of the email";
mail.Body = "Main body of the email";
SmtpMail.Send(mail);
The implementation of the e-mail process for CMS.NET is localized in the EmailAlert class (see Listing 14-2). Most of the class is simply little utility functions to help populate the e-mail message without the help of the Web page developer.
Listing 14-2: The EmailAlert Class
public class EmailAlert
{
private int m_code; private int m_towho;
private string m_body = ""; private HttpContext m_context;
public int Code
{
get { return m_code; } set { m_code = value; }
}
public int ToWho
{
get { return m_towho; } set { m_towho = value; }
}
public string Body
{
get { return m_body; } set { m_body = value; }
}
public EmailAlert(HttpContext context, int code, int towho)
{
m_context = context;
m_code = code;
m_towho = towho;
}
public void Send()
{
AppEnv appenv = new AppEnv(m_context);
string SMTPServer = appenv.GetAppSetting("smtpserver").Trim();
if (SMTPServer.Length <= 0)
return; // do not use email notifications
SmtpMail.SmtpServer = SMTPServer;
Account account = new Account(appenv.GetConnection());
MailMessage mail = new MailMessage();
DataRow dr = account.GetAccountForID(1); // Admin account mail.From = dr["Email"].ToString().Trim();
mail.Subject = generateSubject(); mail.Body = m_body; mail.BodyFormat = MailFormat.Text;
if (m_towho != 0)
{
dr = account.GetAccountForID(m_towho); mail.To = dr["Email"].ToString().Trim(); SmtpMail.Send(mail);
}
else
{
AccountRoles roles =
new AccountRoles(new AppEnv(m_context).GetConnection()); DataTable dt = roles.GetAllRole(getRoleForCode());
foreach (DataRow drr in dt.Rows)
{
dr =
account.GetAccountForID(Convert.ToInt32(drr["AccountID"])); mail.To = dr["Email"].ToString().Trim();
SmtpMail.Send(mail);
}
}
}
private string getRoleForCode()
{
switch (m_code)
{
case StatusCodes.RequiresUpdate: return "Author";
case StatusCodes.AwaitingEdit: case StatusCodes.Editing:
case StatusCodes.RequiresEditing: return "Editor";
case StatusCodes.AwaitingApproval: return "Approver";
case StatusCodes.Approved: return "Deployer";
default: return "";
}
}
private string generateSubject()
{
switch (m_code)
{
case StatusCodes.AwaitingEdit:
return "New content available for editing";
case StatusCodes.Editing:
return "Updated content available for editing";
case StatusCodes.AwaitingApproval: return "Content available for approval";
case StatusCodes.RequiresUpdate: return "Content requires updating";
case StatusCodes.RequiresEditing: return "Content requires editing";
case StatusCodes.Approved:
return "Content available for deployment";
case StatusCodes.Discontinued:
return "Content has been discontinued";
default: return "";
}
}
}
