r tanenbaum .net portfolio
DESCRIPTION
Portfolio of .Net work by Robert Tanenbaum showing both code and screenshots of Windows Forms, Web Sites, polymorphism, T-SQL and other .Net technologies.TRANSCRIPT
CONTENTS:
Demonstrate the use of polymorphism
Demonstrate the use of polymorphism
Use of a strongly typed dataset
Use of a strongly typed dataset
Showing data entry field validation
Showing data entry field validation
Use of ADO.Net and T-SQL stored procedures
Use of ADO.Net and T-SQL stored procedures
Demonstrate role-based security and navigation
Demonstrate role-based security and navigation
Proper use of n-tier layer architecture
Proper use of n-tier layer architecture
Using an AJAX client-side control
Using an AJAX client-side control
Use of CSS classes to unify a color scheme
Use of CSS classes to unify a color s
Demonstrate the use of polymorphism – screenshot.
Demonstrate the use of polymorphism – code sample.
Use of a strongly typed dataset – screenshot.
Use of a strongly typed dataset – code sample.
Showing data entry field validation – screenshot.
Showing data entry field validation – code sample.
SQL stored procedures – screenshot.
SQL stored procedures – code sample.
based security and navigation – screenshot.
based security and navigation – code sample.
tier layer architecture – screenshot.
tier layer architecture – code sample.
side control – screenshot.
side control – code sample.
Use of CSS classes to unify a color scheme – screenshot.
Use of CSS classes to unify a color scheme – code sample.
Page 3
Page 4
Page 5
Page 6
Page 7
Page 8
Page 9
Page 10
Page 12
Page 13
Page 14
Page 15
Page 16
Page 16
Page 17
Page 17
Demonstrate the use of polymorphism – screenshot. Page 3
This screenshot shows the Member Lookup tab for the RT Library Phase 2 project as developed using windows
forms. What is interesting about this particular screen is that, although it was not specified in the project
requirements, there are two ways that the member data can be displayed. First, the user can enter the member
identifier in the text box and click the Lookup button. Alternatively, the user can enter the member identifier
and simply hit the tab key.
Normally, clicking on a button and tabbing out of a text box trigger two different kinds of events which would
require nearly identical code --- code to display the member data --- to be written twice. Having nearly identical
code in more than one place is a problem for code maintenance. However, by employing the C# use of
polymorphism, Robert was able to avoid this problem.
The following code sample shows how this is done.
Demonstrate the use of polymorphism – code sample. Page 4 Two different actions performed by the user have the same desired result – perform a lookup and display of the
member data.
Here is the code for the event handler when clicking the Lookup button.
Here is the code for the event handler when tabbing out of the member identifier text box:
The Click event passes an EventArgs object to its event handler, but the Validating event passes a
CancelEventArgs object to its handler. The CancelEventArgs object has the Cancel property which is used when
validating a field, and the EventArgs object lacks that property. You would think that calling the
lookupButton_Click method with a CancelEventArgs object would fail. However, it does not fail, because a
CancelEventArgs object inherits from EventArgs and can be implicitly cast to an EventArgs object.
But wait. How can we then access the Cancel property on the EventArgs object in the lookupButton_Click
method? That problem is solved by the code highlighted in yellow which tests to be sure that the underlying
object is a CancelEventArgs object and then casts the EventArgs object to its underlying identity and legitimately
accesses the Cancel property.
private void memberIdTextBox_Validating(object sender, CancelEventArgs e)
{
lookupButton_Click(sender, e);
}
private void lookupButton_Click(object sender, EventArgs e)
{
String errString = "";
String memberString = "";
Member myMember = null;
// Check that a valid MemberId is entered
myMember = isExistingMemberId(memberIdTextBox.Text, ref errString);
if (myMember == null)
{
if (e is CancelEventArgs)
{
// We were called from memberIdTextBox_Validating()
((CancelEventArgs)e).Cancel = true; // validation failed
}
errorProvider1.SetError((Control)memberIdTextBox, errString);
MemberStatusButton.BackColor = DisplayBackColor.errorColor;
MemberStatusButton.Text = errString;
return;
}
// The member lookup succeeded so let's build the display string
memberString = CreateMemberDataString(out backColor, myMember);
// Now display the string
MemberDisplay.BackColor = backColor;
MemberDisplay.Text = memberString;
}
Use of a strongly typed dataset – screenshot. Page 5
The data displayed in the DataGridView comes from a dataset defined by a strongly typed dataset which was
automatically generated by Visual Studio 2008 from the GetItems T-SQL stored procedure.
The following code sample shows how simple it is to acquire this dataset from the database using the C# highly
typed dataset generated by Visual Studio 2008.
Use of a strongly typed dataset – code sample. Page 6 This code sample shows how simple it is to acquire a dataset using the strongly typed dataset which was
automatically generated by Visual Studio 2008.
In just 3 simple statements highlighted in yellow above, the dataset is created and filled.
public ItemsDataSet GetItems(short memberNumber)
{
try
{
using (ItemsDataSet itemsDs = new ItemsDataSet())
{
using (ItemsTableAdapter itemsTab = new ItemsTableAdapter())
{
int ret_val = itemsTab.Fill(itemsDs.Items, memberNumber);
if (ret_val < 0)
{
throw new LibraryException(ErrorCode.GenericException,
"GetItems reports error retrieving Items from database");
}
return itemsDs;
}
}
}
catch (SqlException sqlEx)
{
if (sqlEx.State == 1)
{
throw new ArgumentOutOfRangeException("Missing member number", sqlEx);
}
throw new LibraryException(ErrorCode.GenericException, "SQL Error", sqlEx);
}
catch (Exception ex)
{
throw new LibraryException(ErrorCode.GenericException, ex.Message, ex)
}
}
Showing data entry field validation – screenshot. Page 7
There a few things to notice about this screen.
First, the fields are validated one-by-one as the user tabs through each field.
Second, when the Add adult button is clicked, all the data entry fields are validated before the action takes
place.
Third, an error provider component is included on this form and puts a little red circle next to each field which
failed validation.
Fourth, if the mouse cursor hovers over one of the red circles, the descriptive error message pops up to tell the
user why that field failed validation.
Fifth, notice that the Zip code and Phone number fields show parentheses and hyphens as guides to assist the
user to type in the proper format.
The following code sample describes something interesting about validating the phone number when taking the
guiding parentheses and hyphen into consideration.
Showing data entry field validation – code sample. Page 8
The business rules for the Library project permit a new adult member to be added without a phone number.
However, the validation of the phone number requires a little trick to check to see if no phone number was
entered at all. It is not possible to use length of the field to determine whether the field is empty because the
guiding parentheses and hyphen are counted in the length of the field. Also, we don’t want to store the
parentheses in the database table if no phone number is entered.
This code sample shows how to manage the data mask to sometimes consider the masking characters and to
sometimes ignore them.
The code above which is highlighted in yellow first turns the masking characters off so the length of use- entered
text can be checked. If there was any user-entered text, then the mask is turned back on before checking to see
if the phone number is in the correct format.
Similar code which selectively includes or excludes the masking characters when placing the phone number and
zip code into the object which is stored in the database.
private void phoneNumberTextBox_Validating(object sender, CancelEventArgs e)
{
MaskedTextBox ctrl = sender as MaskedTextBox;
if (ctrl != null)
{
// First check the length without the mask literals
// to see if anything was entered
ctrl.TextMaskFormat = MaskFormat.ExcludePromptAndLiterals;
if (ctrl.Text.Length > 0)
{
// Something was entered so include the mask literals and validate
ctrl.TextMaskFormat = MaskFormat.IncludeLiterals;
if (!isPhoneNumber(ctrl.Text))
{
// Failed the phone number test
e.Cancel = true;
errorProvider1.SetError(ctrl, "Not a phone number");
}
}
}
}
Use of ADO.Net and T-SQL stored procedures – screenshot. Page 9
This screenshot shows the result of a successful addition of a juvenile library member.
A number of things need to be done In order to add a new juvenile member to the library.
The name and birth date fields need to be validated according to the library’s business rules.
An adult sponsor id needs to be entered and validated to be sure the id belongs to an adult whose membership
is current.
The information about the adult sponsor needs to be retrieved and displayed.
Then the information about the juvenile member needs to be stored in the database and the successful result
needs to be displayed to the librarian.
The following code sample is an example of the ADO.Net code for taking the information about the juvenile
member and storing it in the database.
Use of ADO.Net and T-SQL stored procedures – code sample. Page 10
Here is the code which adds the juvenile member to the database.
Validate the arguments, setup the connection string and setup the input parameters.
Then setup the output parameters.
SqlParameter street = new SqlParameter("@street", SqlDbType.VarChar);
street.Direction = ParameterDirection.Output;
street.Size = 15;
procCmd.Parameters.Add(street);
SqlParameter city = new SqlParameter("@city", SqlDbType.VarChar);
city.Direction = ParameterDirection.Output;
city.Size = 15;
… etc. …
// set the parameter for the return value
SqlParameter rVal = new SqlParameter("@return_value", SqlDbType.Int);
rVal.Direction = ParameterDirection.ReturnValue;
procCmd.Parameters.Add(rVal);
public void AddMember(JuvenileMember member)
{
int ret_val = 0;
// Check for bad data being passed in
if (member == null)
{
throw new ArgumentOutOfRangeException("member",
"member object must not be null.");
}
// Set up the connection string
try
{
using (SqlConnection conn = new SqlConnection(LibraryConnectString))
{
conn.Open();
using (SqlCommand procCmd =
new SqlCommand("AddJuvenileMember", conn))
{
procCmd.CommandType = CommandType.StoredProcedure;
// Set the input parameter
procCmd.Parameters.AddWithValue("@lastname",
member.LastName);
procCmd.Parameters.AddWithValue("@firstname",
member.FirstName);
procCmd.Parameters.AddWithValue("@middleinitial",
member.MiddleInitial);
procCmd.Parameters.AddWithValue("@birth_date",
member.BirthDate);
procCmd.Parameters.AddWithValue("@adult_member_no",
member.AdultMemberID);
Now run the query, check the return code and save the returned values.
// Now run the NonQuery
procCmd.ExecuteNonQuery();
ret_val = (int)rVal.Value;
// Be sure we got the successful return value
if (ret_val != 0)
{
throw new LibraryException(ErrorCode.GenericException,
"Unknown error in AddJuvenileMember stored procedure");
}
// Now save all the returned values
// These first values should be non-Null
member.MemberID = (short)MemberNo.Value;
member.Street = (string)street.Value;
member.City = (string)city.Value;
member.State = (string)state.Value;
member.ZipCode = (string)zip.Value;
member.ExpirationDate = (DateTime)ExprDate.Value;
// These values might be null
member.PhoneNumber = phone_no.Value.GetType() == typeof(DBNull) ? "" :
(string)phone_no.Value;
// That's all there is to it.
}
}
}
// Handle the possible exceptions that were raised.
Demonstrate role-based security and navigation – screenshot. Page 12
This screenshot shows the Check In Item screen for the RT Library Phase 3 project as developed using ASP.Net
tools. The navigation and security controls, as well as the picture and heading are placed in a master page which
is the master for all pages in this website.
Access Security:
This application implements role-based security using ASP.Net login controls and is tied to an SQL Server
database. This screen is only accessible to users who are associated with the Librarian role.
Navigation:
There are 3 navigation elements on this page:
1. The TreeView control on the left shows all the pages accessible to a user in the role of Librarian.
2. A Menu control is on the top left and is shown expanded out horizontally.
3. Just above the main content is the SiteMapPath where this screen is located in the menu hierarchy.
Demonstrate role-based security and navigation – code sample. Page 13 The RT Library Phase 3 project implements role-based access security and navigation. The controls which
implement these functions are placed on a master page which is used as the template for all other pages on the
website.
Access security is enforced by strategically placing the web pages in a subfolder of the website. All of the
functions which are accessible to users who have the role of Librarian are in the Librarian subfolder. The web
server enforces access to the pages in the subfolder by following the instructions in the web.config file which is
also located in that subfolder. This is the web.config code:
The menus which are displayed for the navigation controls (SiteMapPath, Menu and TreeView) are described in
the sitemap file. Not all users will see the same menu. Only users who belong to the Librarian role will see the
menu items that are meant to be used by Librarians. These rules are defined in the sitemap file listed here:
<?xml version="1.0" encoding="utf-8" ?>
<siteMap xmlns="http://schemas.microsoft.com/AspNet/SiteMap-File-1.0" >
<siteMapNode url="RTLibrary.aspx" title="RT Library Home" description="RT
Library Home Page" roles="*">
<!-- set roles="*" so it shows up in the expanded menu -->
<siteMapNode url="login.aspx" title="Login" description="Login for " />
<siteMapNode title="Librarians" roles="Librarian">
<!-- roles="Librarian" is set in web.config for access security
but it also needs to be set here for menu trimming -->
<siteMapNode title="Member Admin" description="Administer member
information" roles="Librarian">
<siteMapNode url="~/Librarians/AddAdultMember.aspx" title="Add New Adult
Member" description="Add a new adult member"/>
<siteMapNode url="~/Librarians/AddJuvenileMember.aspx" title="Add New
Juvenile Member" description="Add a new juvenile member"/>
<siteMapNode url="~/Librarians/DisplayMember.aspx" title="Display Member
Info" description="Display member information"/>
</siteMapNode>
<siteMapNode title="Item Admin" description="Administer library items"
roles="Librarian">
<siteMapNode url="~/Librarians/AddNewItem.aspx" title="Add New Item"
description="Add a new item to the catalogue"/>
<siteMapNode url="~/Librarians/CheckInItem.aspx" title="Check In Item"
description="Check in an item"/>
<siteMapNode url="~/Librarians/CheckOutItem.aspx" title="Check Out Item"
description="Check out an item"/>
</siteMapNode>
</siteMapNode>
</siteMapNode>
</siteMap>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
<authorization>
<allow roles="Librarian" />
<deny users="*" />
</authorization>
</system.web> </configuration>
Proper use of n-tier layer architecture – screenshot. Page 14
In this screen the status message, “Juvenile was successfully upgraded to adult”, is displayed to the user.
The action was implemented in the Business Layer with no changes required of the User Interface Layer.
Website user interface Windows forms user
interface
Business Layer – encapsulates implementation of business
rules. For example, juveniles are automatically converted to
adult status upon reaching age 18.
Data Access Layer – encapsulates all database calls. If there
is a change to the SQL stored procedure, only this layer is
affected.
SQL database – contains stored procedures and data tables.
Lib
rary
Da
ta E
nti
tie
s li
ke
Me
mb
er
or
Ite
m
ob
ject
s w
hic
h a
re c
om
mo
n t
o t
he
Da
ta A
cce
ss,
Bu
sin
ess
an
d U
ser
Inte
rfa
ce l
aye
rs.
User Interface Layer
Proper use of n-tier layer architecture – code sample. Page 15
The RT Library projects in all their phases implemented an n-tier architecture as laid out in the diagram above.
Phase 3 implemented a change to the business rules governing the display of juvenile members. The
requirements state that when the application displays a juvenile member, it should check to see if the member
is now at least 18 years-old and automatically convert the juvenile to adult status. This must happen
automatically without requiring any user interaction.
Therefore, this enhancement was implemented in the business layer without any changes to the user interfaces.
Here is the code from the business layer. The new code is highlighted in yellow.
public Member GetMember(short memberId, out string errString)
{
// Create a library data access object
Member myMember = null;
LibDataAccess lda = new LibDataAccess();
// Call the GetMember() method and pass in a member id.
try
{
myMember = lda.GetMember(memberId);
}
catch (LibraryException e)
{
formatErrorMessage(e.LibraryErrorCode, out errString);
return null;
}
catch (ArgumentOutOfRangeException)
{
errString = "Invalid member id.";
return null;
}
// Check here to see if this is a juvenile who should be promoted
if (myMember is JuvenileMember)
{
DateTime birth_date = ((JuvenileMember)myMember).BirthDate;
if (birth_date.AddYears(18) <= DateTime.Today)
{
// This juvenile had 18th birthday today or earlier
// Upgrade this juvenile to an adult
DateTime expr_date = DateTime.MinValue;
if (UpgradeJuvenileToAdult(memberId, out expr_date, out errString) < 0)
{
errString = "Failed to upgrade juvenile to adult";
return null;
}
// Now let's be really tricky and call ourselves
myMember = GetMember(memberId, out errString);
if (myMember == null)
{
return null;
}
// Successful upgrade and retrieval of new member
errString = "Juvenile was successfully upgraded to adult";
return myMember;
}
}
// Return the retrieved member
errString = "Success";
return myMember;
}
Using an AJAX client-side control – screenshot. Page 16
In the screenshot shown above, the item circled in yellow is implemented by an AJAX client-side control and
show activity while the server is processing the database request.
Using an AJAX client-side control – code sample. Page 16
Here is the code which implements the AJAX client-side control.
<!-- AJAX UpdateProgress Control should be outside of the UpdatePanel/!-->
<asp:UpdateProgress ID="CheckInUpdateProgress" runat="server">
<ProgressTemplate>
Item is being checked in ...
<asp:Image ID="CheckInProgressImage" runat="server"
ImageUrl="~/images/UpdateProgressActivity.gif" />
</ProgressTemplate>
</asp:UpdateProgress> <!-- End AJAX UpdateProgress Control /!-->
Use of CSS classes to unify a color scheme – screenshot. Page 17
The RT Library website uses a 3-color scheme on all of its pages to represent the status of library members:
Adult, Juvenile or Expired. The colors make it easy for the librarians to recognize a member’s status immediately
upon display. Instead of hard-coding these colors on every page they are defined in a single CSS file which is
referenced by the master page which is the template for all the other pages.
Use of CSS classes to unify a color scheme – code sample. Page 17
Here is how those colors are defined in the CSS file.
Here is how the CSS file is included in the master page.
And here is how the color is dynamically set when the page is running.
lblAddJuvenileStatus.Text = "NEW MEMBER<br />" + juvenileMemberString;
lblAddJuvenileStatus.CssClass = "juvenile";
<head runat="server">
<link href="~\RTLibrary.css" rel="Stylesheet" type="text/css" />
<title>RT Library Phase 3</title>
</head>
/* member background colors */
.expired { background-color: #FF99FF}
.adult { background-color: #66FF66}
.juvenile { background-color: #66CCFF}