Real - World ASP .NET—Building a Content Management System - StephenR. G. Fraser
.pdf
Figure 15-2: The Login User Control
As you can see in the Login User Control design (see Listing 15-3), it takes up as little room as possible yet is clear and functional at the same time.
Listing 15-3: The Login User Control Desi gn
<P> <B>
<FONT color=" darkslategray" size="2"> Account Login</FONT>
</B>
<BR>
<FONT color=" darkslategray" size="2"> Username:</FONT>
<BR>
<asp:TextBox id=" tbUsername" runat=" server" Width="90%" BackColor=" LightCyan">
</asp:TextBox>
<BR>
<FONT color=" darkslategray" size="2"> Password: </FONT>
<BR>
<asp:TextBox id=" tbPassword" runat=" server" Width="90%" TextMode=" Password" BackColor=" LightCyan">
</asp:TextBox>
<BR>
<FONT color=" darkslategray"> <asp:CheckBox id=" cbPersist" runat=" server"
Text=" Remember Login" Font-Size="X-Small">
</asp:CheckBox>
</FONT>
<BR>
<asp:ImageButton id=" ibnSignIn" runat=" server"
ImageUrl=" Images/signin.gif">
</asp:ImageButton>
<BR>
<asp:ImageButton id=" ibnRegister" runat=" server"
ImageUrl=" Images/register.gif" >
</asp:ImageButton>
</P>
<P>
<asp:Label id=" ErrorMsg" runat=" server" ForeColor=" Red">
</asp:Label>
</P>
Another thing you might notice when you look at Figure 15-2 is that the sign-in and register buttons appear to be hyperlinks, but as you can see by the code in Listing 15-3, they are in fact image buttons. I could have used buttons, but I felt that buttons were bulky and distracting and that the hyperlinks better matched the look of the NavBar used by CMS.NET. I could have used a real hyperlink to jump to the registration Web page, but using the image button kept things consistent in the code.
The logic of the Login User Control Codebehind (see Listing 15-4) is handled in the two button event handlers. Because there is no common code that needs to be run by this User Control, the Page_Load() method is empty.
Listing 15-4: The Login User Content Codebehind
private void Page_Load(object sender, System.EventArgs e)
{
}
private void ibnSignIn_Click(object sender, ImageClickEventArgs e)
{
Account account = new Account(new AppEnv(Context).GetConnection());
if (account.Authenticated(tbUsername.Text, tbPassword.Text))
{
FormsAuthentication.SetAuthCookie(tbUsername.Text,
cbPersist.Checked);
Response.Redirect(Request.RawUrl);
}
else
ErrorMsg.Text = account.Message;
}
private void ibnRegister_Click(object sender, ImageClickEventArgs e)
{
Response.Redirect("Register.aspx");
}
The ibnSignIn_Click() method handles the signing in of a Web user. First, it verifies that the user is authentic (or, in other words, has previously registered). If the Web user is authenticated, a temporary session Authentication cookie is created for the user. Also, if the user checked the Remember Login check box, an authentication cookie is placed on the Web user's machine so that the user will not have to log in each time he returns to the site. The method ends by redirecting back to itself. This causes the Web page to reset with any changes due to being authenticated.
The ibnRegister_Click() method is simply a redirect to the registration Web page.
Multipurpose Login.aspx
CMS.NET took the approach that the entire CMS is one system. As a result, CMS.NET was forced to do some magic with the Authentication Web page because only one Authentication Web page is allowed per Web application, and you had already used it to build your administration system.
Another perfectly valid approach would have been to separate the administration application from the content display application and thus get a fresh Authentication Web page to work with.
As you can see in Figures 15-3 and 15-4, the Authentication Web page, better known as the Login page, has two distinct looks, yet they both derive from the same Web page. The Administration Login screen is a little more stark, while the Web User Login provides an explanation of where the user is as well as a way to register if he got here and doesn't have an account to proceed any further.
Figure 15-3: Login for Administration
Figure 15-4: Login for Web site user authentication
Chapter 12 covered the first version of the Login.aspx (see Listing 15-5). If you glance back to that chapter, you will see that not much has been changed in the way of Web design. As you will see, however, significant changes are needed in the Codebehind to allow for this dynamic Login Web page.
Listing 15-5: The Login Web Page Design
<form id=" login" method=" post" runat=" server" >
<IMG src=" Images/login.jpg">
<HR width="100%" SIZE="1">
<TABLE cellSpacing="1" cellPadding="1" width="95%" border="0" >
<TR>
<TD width="25%">
</TD >
<TD>
<H1>
<FONT color=" darkslategray">Login</FONT>
</H1> <P>
<asp:label id=" lbPrompt" runat=" server"></asp:label> </P>
<P>
<asp:validationsummary id=" ValidationSummary1" runat=" server" HeaderText=" The following error(s) occurred while logging in:">
</asp:validationsummary> </P>
<P>
<asp:label id=" ErrorMsg" runat=" server" ForeColor=" Red"> </asp:label>
</P>
<TABLE cellSpacing="1" cellPadding="5" width="300" border="0"> <TR>
<TD width="15%"> <P align=" right">
<STRONG>Username:</STRONG> </P>
</TD>
<TD width="85%">
<asp:textbox id=" tbUsername" runat=" server" Width="100%"> </asp:textbox>
</TD>
<TD width="2%">
<asp:requiredfieldvalidator id=" RequiredFieldValidator1" runat=" server" ControlToValidate=" tbUsername" Display=" Dynamic"
ErrorMessage=" You must enter a Username">*
</asp:requiredfieldvalidator>
</TD>
</TR > <TR> <TD>
<P align=" right">
<STRONG>Password: </STRONG> </P>
</TD> <TD>
<asp:textbox id=" tbPassword" runat=" server" Width="100%" TextMode=" Password" >
</asp:textbox>
</TD>
<TD>
<asp:requiredfieldvalidator id=" RequiredFieldValidator2" runat=" server" ControlToValidate=" tbPassword" Display=" Dynamic"
ErrorMessage=" You must enter a password" >*
</asp:requiredfieldvalidator>
</TD>
</TR >
<TR>
<TD colSpan="3">
<P align=" center">
<asp:checkbox id=" cbPersist" runat=" server"
Text=" Remember Login">
</asp:checkbox>
</P>
</TD>
</TR >
<TR>
<TD colSpan="3">
<P align=" center">
<asp:button id=" bnLogin" runat=" server" Text=" Login">
</asp:button>
<asp:button id=" bnRegister" runat=" server" Text=" Register"
Visible=" False" CausesValidation=" False">
</asp:button>
</P>
</TD>
</TR >
</TABLE>
</TD>
</TR >
</TABLE >
</form>
The design has only two changes. The first change is the addition of a label so that the Login Web page will be able to provide an explanation of where the user has been teleported. You need to remember that the user is expecting to go to a specific Web page, and all of a sudden, she is presented with this Login screen. Without a little bit of explanation, some users may get flustered and leave, and of course, that is the last thing you want to happen.
The other change to the Web design is the addition of the Register button; when clicked, it will cause the user to jump to the registration Web page. This button is a little different than the others you have seen so far. If you take a quick peek at the design code, you will notice two unusual attributes. The first is the Visible attribute. This attribute is a
way of adding a button that you may not want to display right away or that usually isn't displayed. It is safe to place a button like this on a Web page because an invisible button is not created by the ASP.NET parser and thus is not even placed on the Web page. Because it is not on the Web page, the user has no way to access it. Chapter 11 showed
how you can set the attribute in the Codebehind—this is how you do it in the design code.
The second attribute, CauseValidation, is new to CMS.NET. This handy attribute tells the Web page whether or not to perform form validations on the Web page. The default value is true, which is why you have not seen it until now. With the Login Web page, on the other hand, if you don't set this attribute to false for the Register button, validation occurs when the button is clicked. Because the username and password are empty, the validation will fail, and the button will not execute its code to go to the registration Web page. Instead, the Login page will be presented again, asking for a username and password. This is, obviously, not what you want to happen.
The Login Web page can be accessed in three distinct ways:
§Automatically by ASP.NET in the administration system
§Automatically by ASP.NET in the content display application
§Directly called by CMS.NET in the content display application
The Login Codebehind (see Listing 15-6) handles these three ways differently.
Listing 15-6: The Login Web Page Codebehind
private void Page_Load(object sender, System.EventArgs e)
{
URL = Request.QueryString["URL"];
if (URL != null)
URL.Trim();
if (!IsPostBack)
{
if (FormsAuthentication.GetRedirectUrl("",
false).IndexOf("admin.aspx") < 0)
{
lbPrompt.Text = "<h2>Accessing Protected Content.</h2>" +
"<h3>Login required.</h3>"; bnRegister.Visible = true;
}
}
}
private void bnRegister_Click(object sender, System.EventArgs e)
{
Response.Redirect("CDA/Register.aspx");
}
private void bnLogin_Click(object sender, System.EventArgs e)
{
if (Page.IsValid)
{
Account account = new Account(new AppEnv(Context).GetConnection());
if (account.Authenticated(tbUsername.Text, tbPassword.Text))
{
if (URL != null && URL.Length > 0)
{
FormsAuthentication.SetAuthCookie(tbUsername.Text,
cbPersist.Checked);
Response.Redirect(URL);
}
else FormsAuthentication.RedirectFromLoginPage(tbUsername.Text,
cbPersist.Checked);
}
else
ErrorMsg.Text = account.Message;
}
}
The Page_Load() method is mainly in charge of figuring out in which of the three ways the Web page was accessed. It then displays the Login in the appropriate fashion for that access method. It does this in a two-part process. First, it checks to see if it received a URL in the Request. When this happens, it means that Login was called directly by CMS.NET.
Second, it checks to see what Web page caused the redirect to the Login Web page using the FormsAuthentication.GetRedirectUrl() method. This method returns the full URL of the Web page that was supposed to be the destination of the last request, before it was trapped and sent to the Login Web page due to the user not being authenticated. If Login.aspx was called directly and was not the result of a user authentication trap, the GetRedirectURL() method returns Default.aspx.
CMS.NET knows that the Login Web page was automatically called from the administration system when the GetRedirectURL() method returns a URL containing the string admin.aspx
Now that CMS.NET knows how it was called, the Page_Load() method can continue and display the Login Web page appropriately.
The bnLogin_Click() method's job is to authenticate the user (by checking the username and password) and then create an authentication certificate (cookie). How the cookie is created depends on how the Login Web page was called. If it was automatically called by ASP.NET, CMS.NET uses the RedirectFromLoginPage() method, which creates the authentication cookie and then redirects it to the Web page that it was originally going to before it was intercepted by ASP.NET. On the other hand, if the Login Web page was called directly from CMS.NET, it uses the SetAuthCookie() method, which only creates the cookie. CMS.NET then uses the value of the URL variable that is received in the Request to redirect the Web page to its intended destination.
Logging Off
Because it is possible for the user to log in and store his login authentication cookie on his machine, it is a good idea to provide a way to remove this cookie. CMS.NET does this with the Logout User Control. As you can see in Figure 15-5, the Logout User
Control is simply an image button that replaces the Login button on the NavBar after the user has logged in.
Figure 15-5: The Logout User Control
The Logout Codebehind (see Listing 15-7) has nothing you have not covered before. It calls the FormsAuthentication.SignOut() method, which deletes the authentication cookie and then redirects the user back to the home page.
Listing 15-7: The Logout User Content Codebehind
private void ibnLogout_Click(object sender, ImageClickEventArgs e)
{
FormsAuthentication.SignOut();
Response.Redirect("Default.aspx");
}
Restricting Content to Registered Users
The changes required to add protection to the current version of CMS.NET are actually very minor. In all cases, it is just a few lines of code. This is no coincidence, though, because I designed CMS.NET with protection in mind.
To implement protection, CMS.NET uses ASP.NET's built-in authentication functionality. Basically, whenever a user accesses protected content, he is forced to run Web pages in a new directory with authentication enabled. Because authentication is on, only users who are authenticated can continue and view the content; all others will be interrupted with a Login screen that asks for a username and password.
Database Updates
The first things that need to be updated are the Content, Zone, and Domain database tables. Each needs a way to specify whether a row in its table should be protected or not. To do this is actually very easy. All you need to do to each of these database tables is add a Protected column, similar to what is shown in Table 15-1.
