This is the problem set that started it all. Before devhood was created, 4 out of the 6 creators didn't even know HTML. Here is the problem set that they completed which taught them all they needed to know to create devhood. If you're looking for a way to learn about database backed web applications and .NET technologies this is the problem set for you. Once you've completed this problem set you'll have a good understanding of everything from databases to ASP.NET web applications to web services.
Devhood Boot Camp: Problem Set 1
Objectives:
- Developing a Microsoft SQLServer backed web service.
- Maintaining database consistancy.
- Introduction to ASP.NET, C#, and Web Forms Processing.
- Developing and invoking remote web services using the .NET Framework.
Helpful Links:
- - A .NET student community site featuring tutorials and message forums. The site is also source code viewable which provides a great resource for example code.
- - A great .NET site written by part of the ASP.NET team. Also source code viewable. Well organized code and samples.
- - A Microsoft .NET community site.
- - Philip Greenspun's book on web publishing. Although focused on Oracle backed web applications, provides good design principles.
- - Also focused on Oracle's SQLPlus but still provides a great reference for using RDBMS's for the web.
Your job is to develop a Web-based room reservation system for MIT. To first
get an idea of the requirements for the system, you sit down and talk with your
client. From this dialog, you (the application programmer) can form a big
picture of what needs to be done.
|
Secretary: |
MIT has the best computer-science department in the world. We have a
building full of top researchers, advanced computer systems, and nice
lecture halls. We would like a web-based interface for scheduling lecture
halls. |
|
You: |
How are the lecture hall reservations being done now? |
|
Secretary: |
People email me with the time they need a lecture hall, and how many
people they need to hold. I then look up this information in my
appointment book, and email them back with open rooms for that time slot
that will hold the desired amount of people. |
|
You: |
Wow, that's a big headache. So how do you envision the web application
working? |
|
Secretary: |
A professor comes to the server and requests a lecture hall from Time
x to Time y, capable of holding n people. The server
will then offer the user a choice of available lecture halls, each one
with a description of the AV facilities. The user should then be able to
pick the lecture hall she likes best. |
|
You: |
Any other features you would like in the system? |
|
Secretary: |
Yes, professors should be able to boot only students (i.e., professors
can't boot other professors) out of a reserved room if they can't find a
room that suits them. The system should then send an email alert to the
booted student informing them that they were booted and asking them to
come back and select another room. |
|
You: |
So professors and students are the only 2 types of users that the
system needs to support? Can any user reserve any lecture hall? |
|
Secretary: |
No. Most of the lecture halls are reservable by any MIT student or
professor, however, some lecture halls require administrative approval
from myself or any other designated administrator of the system, unless
the lecture hall was reserved by a professor. Professors shouldn't need
approval for reserving any lecture hall. So the system should also support
students, professors, and a group of administrators. However, any
professor or student can be designated a room administrator. |
|
You: |
So how exactly do these administrators approve rooms? |
|
Secretary: |
Whenever a reservation is made for a approval-required lecture hall,
an email should be sent out to the administrators of that room. The email
should contain an encoded URL where the administrator can click to approve
or deny the request. |
You've been given a basic idea of how the client wants the system to work. It
is now your job to figure out the implementation details as well as layout the
system. We are building a program that keeps track of users, groups of users,
rooms, and room reservations. Because we are building this system complete from
scratch we have to carfeully plan out each piece step by step.
In this problem set, you'll be creating SQLServer tables to keep track of
users, rooms, and room reservations. You'll also be learning ASP.NET and C#
techniques for processing transactions on the RDBMS (Relational Database
Management System) through the use of ASP.NET's Web Forms.
Still working on this part. Are they gonna install .NET and SQL on their own
comps or work off a central server?
First we'll need a table to hold users of the system. Our data model will
look something like this:
| create table users ( |
| |
user_id |
|
integer primary key, |
| |
first_names |
|
varchar(100) not null, |
| |
last_name |
|
varchar(100) not null, |
| |
email |
|
varchar(100) not null, |
| |
password |
|
varchar(8) not null constraint length_check check (LEN(password)
>= 4 and LEN(password) <= 8) -- constrains password to be 4-8
characters long |
| ) |
Remember that in our
conversation with the secretary, she stated that professors should be able to boot
students out of their reserved room. However by now you might be saying to
yourself, "Hey that data model doesn't support different user groups though." A
sharp-minded individual quickly realizes that right now we have no way of
differentiating between students and professors.
Cleary we need a way of assigning each user to be either a professor or a
student. One way to do this is by adding a column to our users table which shows
which group each user belongs. This can be done by adding a single line to the
data model:
| create table users ( |
| |
user_id |
|
integer primary key, |
| |
first_names |
|
varchar(100) not null, |
| |
last_name |
|
varchar(100) not null, |
| |
email |
|
varchar(100) not null, |
| |
password |
|
varchar(8) not null constraint length_check check (LEN(password)
>= 4 and LEN(password) <= 8) -- constrains password to be 4-8
characters long, |
| |
group |
|
constraint group_check_constraint check (group = 'student' or group
= 'professor') -- constrains the value of group to be student or
professor. |
| ) |
What's wrong with this
approach?
- What if a user was a professor and a student? Not likely but possible.
This approach doesn't allow users to be members of more than one group.
- What if I wanted to add another group, tenured professors, who could boot
regular professors? This approach is not very friendly to new groups. For
every new type of group that's created we have to alter our table to update
the check constraint on the group column to account for the new group.
- Anything else wrong with it?
Generally speaking, when designing your data model, you want tables to be in
at least . What this means is that you want all columns of your table to
be functionally dependent on the whole key. Let's try it again:
| create table users ( |
| |
user_id |
|
integer primary key, |
| |
first_names |
|
varchar(100) not null, |
| |
last_name |
|
varchar(100) not null, |
| |
email |
|
varchar(100) not null, |
| |
password |
|
varchar(8) not null constraint length_check check (LEN(password)
>= 4 and LEN(password) <= 8) -- constrains password to be 4-8
characters long, |
| |
group_id |
|
integer not null constraint users_to_groups references groups |
| ) |
| create table groups ( |
| |
group_id |
|
integer primary key, |
| |
group_name |
|
varchar(100) not null |
| ) |
Let's examine what's
changed here. We've added a column to the users table to keep track of which
group they belong by referencing a groups table. We've also created a simple
groups table that holds group names, and ids. By referencing the groups table
from the users table we've eliminated the problem of adding group types in the
future. Now for every new group type, all we need is to add another row into the
groups table, and the users table never has to be altered.
However, what about the problem of having users belong to more than one
group? Our data model still doesn't support that. A common approach for matching
together multiple tables (such as matching users to groups) is to create a table
whose only job is to hold mappings between tables. So our final data model would
look like this:
| create table users ( |
| |
user_id |
|
integer primary key, |
| |
first_names |
|
varchar(100) not null, |
| |
last_name |
|
varchar(100) not null, |
| |
email |
|
varchar(100) not null, |
| |
password |
|
varchar(8) not null constraint length_check check (LEN(password)
>= 4 and LEN(password) <= 8) -- constrains password to be 4-8
characters long |
| ) |
| create table groups ( |
| |
group_id |
|
integer primary key, |
| |
group_name |
|
varchar(100) not null |
| ) |
| |
| create table user_group_map ( |
| |
user_id |
|
integer constraint user_group_map_to_users references users, |
| |
group_id |
|
integer constraint user_group_map_to_groups references groups |
| ) |
Now with this data
model, for each group that a user is a member of we simply add a row into the
user_group_map table. Note that this table does not need a explicit column set
as the primary key, rather the combination of user_id and group_id will be the
unique key on the table.
What are the advantages of this data model? Well let's see:
- We can easily add new types of groups to the system without risking the
chance of breaking the users table with an alter command.
- The system is flexible. Users are not limited to being a member of only
one group.
- What if we wanted to know how many members group_id "1" had? Or if we
wanted to know which groups user_id "2" belonged to? A simple count(*) SQL
query on the mapping table would provide this information quickly.
Now we've gone through the basics of data modeling, and hopefully you now see
the importance of starting off with a good data model.
Create the users, group, and user_group_map table as above. The tables as
they are shown above should suffice to get you through this problem set. If you
want, you can add any columns to the table that you feel would be important
information to store. Fields such as registration_date, registration_ip, or
birthdate may be appropriate.
You can test that your tables were successfully created by using the
SQLServer "sp_help" command. For example "sp_help users" will list all columns
of the table users. Make sure that your tables work before moving on.
Now that you have your tables in place, we need to fill them with some
information. The next step will be to build a user registration page so that
users can sign up for access to the system. Now we'll begin building the Web
Frontend to our system.
ASP.NET technology brings with it Web Forms that can be used to create
programmable webpages. Web Forms also ease the task of connecting and
transacting with a database. First create a registration form for users to
register for an account on the system. You'll want to use ASP.NET's server
controls along with the runat=server attribute to make sure that these controls
are processed on the server. For example, to create a text box for the user to
submit his email address, you would do the following:
<asp:TextBox id="email" runat=server />
The
server (MS IIS Web Server) will process this control and output appropriate html
to the client (the user's browser). The output will typically look like regular
HTML source code since this is a simple control. However for more complicated
server controls, it has the ability to output specific html based on the device
or browser the client is using.
By giving it an id="email" we can get or set the contents of the server
control in our C# script. For instance if we wanted to set the value of the text
during a Page Load event, our code with look something like this:
<script language="C#" runat="server">
void Page_Load(Object o, EventArgs e) {
email.Text = "";
...
}
</script>
Notice that because the script is told to runat="server" and
because the email textbox control is also told to be processed on the server,
the Page_Load event inside the script block to set the
contents of the email textbox control. In the same way, we could have used
email.Text as an accessor to get the value of the email textbox.
Create a page using ASP.NET's server controls for the elements of the page
that will require server-side processing, i.e., textboxes and other form
elements. Don't worry about the submit button or processing the form yet. We'll
get to that next. Refer to the .NET Framework Documentation and API to help you
build this page. Name this file registration.aspx.
The .aspx extension tells the server that there are ASP.NET elements in the
page.
For our user registration page, all we are concerned about is inserting rows
into our users table. ASP.NET contains a lot of rich controls for viewing
extracted information from a database which we will be exploring later on.
However for now all we want to is to insert new users into our users table.
Use the SQLConnection class to open a connection to the database. Write a
method that will handle the processing of the registration form. Your code
should insert a row into the users table when the user submits the registration
form, as well as send an email confirmation to the user letting them know the
registration was successful (use C#'s MailMessage class to send email). Place
this method within <script language="C#" runat="server"> blocks at the top
of your registration.aspx page. Name this method "SubmitBtn_Click". In general,
we'll want to name our methods using the following convention,
description_eventtype.
Now add a submit button to the bottom of the page using ASP.NET's Button
Control. Connect the Click Event handler of this control to your
"SubmitBtn_Click" method. The Button Click event in ASP.NET causes the form to
be posted back to the server. What this means is that the form values make a
roundtrip to the server where they are processed, then sent back to the client.
For this to happen, first you must tell the server to process the form
server-side. If you haven't done so already, place <form runat="server">
tags around your registration form elements in registration.aspx. Your registration page should now be
able to handle adding new user registrations. Sign yourself up for an account
and check the database to see if your account was successfully added to the
users table.
Now create a login page and creatively name it login.aspx. This should be a simple page, containing an
email textbox, a password textbox, and a login button. Use the SQLDataReader
Class to validate the email and password against the database. Redirect the user
to a page, login_success.aspx to let the user know
that they have logged in successfully. This is a temporary page so keep it
simple. Later we'll be redirecting the user straight to our rooms reservation
system once they've logged in. A page displaying the text "Hello, First
Names" will suffice. In order for you to display the users first name in
login_success.aspx you'll want to pass it as a URL
variable on the redirection after log in. To pass variables inside a URL, use
the following format:
http://www.domain.com/target_page?variable_name1=variable_value1&variable_name2=variable_value2...
or in our case something
like
http://www.devhood.com/login_success.aspx?first_names=Peter&last_name=Weng
ASP.NET's Page Object contains a
Request Property gets the HttpRequest used to access data from incoming http requests.
The HttpRequest object, in turn, has a property Params that provides a name, value collection of URL
variables. We can use this collection as follows:
Outside of the script tags, you can call methods and properties
by placing them within <% %> tags.
Hello, <%
Page.Request.Params["first_names"] %> <%
Page.Request.Params["last_name"] %>, you were successfully logged in.
or you can place the code within the Page_Load
event in your script block
| <script language="C#" runat="server"> |
| void Page_Load (Object o, EventArgs e) { |
| |
//get the name from the URL and concatenate the string |
| |
string first_names = Page.Request.Params["first_names"]; |
| |
string last_name = Page.Request.Params["last_name"]; |
| |
| |
//set the TEXT property of the LABEL control to display it on the
page |
| |
lbl_first_names.Text = first_names; |
| |
lbl_last_name.Text = last_name; |
| |
... |
| |
| } |
| </script> |
| |
| Hello, |
| <asp:Label id="lbl_first_names" runat="server"
/> |
| <asp:Label id="lbl_last_name" runat="server"
/> |
| , You were sucessfully logged
in. |
Other ways of passing variables
between pages include using hidden form variables, or storing them in the State bag or Session bag. The
State and Session bags are
temporary storage areas for variables and/or Control
properties. Anything inside these "bags" will be persisted across roundtrips to
the server.
Remember to also display an error message on the login page (login.aspx) if the email and password are incorrect.
You'll also want to give the user the option of remembering their login
information at the login page. Add a CheckBox server control allowing the user
to "Remember email and password". (Hint: Use ASP.NET's RedirectFromLoginPage
method in the CookieAuthentication Class to accomplish this.)
The web.config file is an xml-based file that is used to configure settings
on the server. Create a web.config file and place it in the root directory of
your application. You'll want to set the authentication mode to "Cookie" within
the security section of this file. A sample web.config file is below:
|
<configuration> |
| |
<security> |
| |
|
<authentication mode="Cookie"> |
| |
|
|
<cookie
cookie=".ASPXAUTH" loginurl="login.aspx" />
|
| |
|
</authentication> |
| |
|
<authorization> |
| |
|
|
<deny
users="?"
/> |
| |
|
</authorization> |
| |
</security> |
|
</configuration>
|
Finally place a link on your login.aspx page that points to your registration.aspx page so that new users can register. If
you are noticing that your link continues to bounce back to the login.aspx page, what may be happening is that your
web.config file is denying anonymous users from accessing registration.aspx. Use the <location> section in web.config to allow all users access to
the registration page. Refer to the .NET Framework Reference Documentation for
more information about web.config.
Now that we've got the basic functionality in place, let's concern ourselves
with the details to make this page more robust.
In order for your system to be robust, you should always perform validation
wherever user input is requested. Explore ASP.NET's Validation Controls by using
them to add input validation to your registration form. Fields such as email
should be validated for correct format (). Also, add
RequiredFieldValidator controls on any field that corresponds to a not null
column in the users table.
Finally, we need to maintain consistency in our database. Why is this so
important? Well let us consider this scenario. Peter Weng ()
comes to the site and registers for an account. His account information is
inserted into the users table and he is able to log in with his email and
password and use the system. 3 months later he comes back to the site, which has
now been redesigned. He forgets that he has ever registered on this site before,
and therefore registers for another account with the same email, but a different
password. Our system inserts another row into the database for the same user.
Now Peter tries to log in with his newly created (or so he thinks) account.
So we have a duplicate row in the database. So what? What's the big deal?
Well let's think about what the server has to do to authenticate a user. It must
check that the password the user has entered matches with the email. It does
this getting the password from the database for that particular email using a
select query with a where clause, and checking it against the password that the
user has provided. However, in this case the select query would return 2 rows
for and we wouldn't know which password to authenticate
against. In most cases, the web server would throw an error, since it was not
expecting more than 1 row.
An effective strategy in maintaining a consistent database and avoiding
duplicate rows is to use keys. Let's define duplicate row as 2 or
more rows that contain the same datum on any unique column. Since each of our
users should have a unique email address, we don't want to have 2 or more rows
with the same email address.
Using SQL's alter command, add a unique key constraint to the email column on
the user's table.
Fill this in later with some usability stuff.
I've walked you through the process of building a data model above (users and
user groups). Now you should be able to build your own data model. First read
through chapter on . Note that
Greenspun's book,
assumes that the reader is using an Oracle RDBMS, thus the examples are in
SQLPlus. SQL, in fact is not a standardized language and Microsoft SQLServer's
syntax will be slightly different from Oracle's. However, his data modeling
chapter teaches good data model design principles, so the Oracle syntax should
not keep you from learning something just because you're using SQLServer.
Next read through this entire problem set to get an idea of what your data
model must be able to support. Otherwise, you'll find yourself altering your
tables and rewriting code half through the problem set.
Here are some things you should keep in mind when designing your data model.
Remember that this is not a complete listing of all necessary steps.
- Remember to be consistent with your table naming and variable naming
conventions. This will be important for other users who may code-review your
work.
- Boolean data should be prefixed with a "_p", and should all be of type
bit.
- When recording a designated administrator in the rooms table, you should
do so by referencing the users table. Your column name should also be the same
name as the primary key of the table you are referencing (user_id in this
case). We used this same idea in our data model above. Remeber how, in our
users table, our group_id column referenced the groups table?
- Don't use room numbers as the primary key for your rooms table. These
numbers, although unique, can sometimes change. Changing the primary key of a
table is always a bad idea. Instead use generated keys for your table so that
you can gracefully handle room number changes.
- Use
start_time and end_time columns of type
datetime in your table. You can then always find the length of time of
the reservation from the difference of the 2 columns.
- Rather than using a boolean column to hold whether a room has been made or
approved, use a column of type
datetime to hold the time the
reservation was made or approved. This way you can get more information out of
the column than if you were to use a boolean column. (You can still tell
whether a reservation has been approved or not by checking for null in the
approval_time column).
- You'll probably need at least 2 tables, one to hold the rooms, and the
other to hold the reservations.
Remember to save your data model in a file somewhere safe. As you go through
this problem set, you may find yourself dropping tables and thus will need to
refer back to your data model to recreate them.
Let's start off simple. We need to first build some pages so that
administrators can create rooms. Don't worry about being able to edit room
names, or designate room administrators yet.
We'll need at least the following pages to start out:
- Create the page index.aspx and place it in a
directory named admin. This page should contain
the names of all rooms, and also an "Add a Room" LinkButton. Your rooms can be
displayed as unordered lists, tables, or drop-down lists. As an engineer, you
should choose the most appropriate design for the information being presented,
i.e., the design that will make your page the most usable. I strongly suggest
you explore ASP.NET's DataTable, DataView, and Repeaters for more choices.
- The LinkButton "Add a Room" should redirect to room-add.aspx. This page should contain a form that
allows the administrator to add new rooms to the system. Make this page
redirect to index.aspx after successfully creating
the new room. The new room should show up in index.aspx which is confirmation to the user that the
rooms has been successfully created.
- These pages are named admin pages because only administrators should be
able to access them. In fact all pages within the admin directory should be secure. You can do this by
adding a web.config file in your admin directory
to only allow users of certain roles to access these pages. Refer to the .NET Reference Documentation for more
information about role access and the ASP.NET authorization process.
Test out your pages by adding a few new rooms to the system.
Again, let's keep it simple for now. Don't worry about room approvals, or
professor's booting students, etc. Let's just get our basic system up for now:
- Build index.aspx, the gateway to the room
reservation system. This page should contain a form that contains elements
such as start and end date and times, and number of people occupying the room.
You may even wish to have a field, room name, for users who want to reserve a
specific room. Below the form, display an organized view of all rooms the user
currently has reserved for the future. By showing this list, you free yourself
from having to write special pages to confirm reservations or check
reservation history. When finished, go back and modify login.aspx to redirect to index.aspx instead of login_success.aspx upon successful login.
- After the server has processed the form on index.aspx, it should redirect the user to new.aspx. This page should show a list of rooms that fit
the users needs. It should refrain from showing any rooms that have already
been booked for the requested time period. This SQL query can get complicated,
so here are some hints:
- You can select from your
rooms table with a WHERE clause
that subqueries the room_reservations table.
select ... from rooms where room_id = (select ... from
room_reservations ...)
- You may choose to use the BETWEEN operator to catch cases of overlapping
room reservations although it may not be the best way. A more professional
way would probably be something in the form
select ... from room where not exists (select 1 from
room_reservations ...)
This page, new.aspx, will ultimately be the page that allows the
user to reserve the room. One way you might do this is by adding radio buttons
beside each detailed room listing on new.aspx,
which lets them choose which room they wish to reserve. Another way may be to
provide a brief listing of available rooms on new.aspx, and link each room to a page that shows more
information about the room. The "Reserve Room" button may then reside on your
detailed room view page. However you decide to build the system, just remember
to choose the layout that provides the best end-user experience.
- When processing the page new.aspx (or
whichever page you chose to let the user reserve the room), you should check
there isn't a time conflict, then insert the reservation into the table. After
successfully inserting, the server should redirect the user to index.aspx where they can see that their reservation was
properly inserted. However, if there is a time conflict, redirect the user to
an error page that shows a message to that effect. This error page should also
provide a link back to new.aspx with the original
room request parameters (presumably this time new.aspx will not offer the already booked room).
Now you might be asking yourself why there is a need to check for a time
conflict when processing new.aspx when new.aspx only showed free rooms in the first place. How
could the user have possible chosen a room with a conflict? Here's an example
of a concurrency situation we're concerned about.
| Jonathan logs into our system and request a room for 40
people at 1:00pm tomorrow for a 1.5 hour presentation. |
|
| Our system quickly and intelligently serves up all
available rooms for Jonathan to choose. The Bush Room is among the list
of available rooms. |
Eugene logs in and requests a room for 50 people at
2:00pm tomorrow. |
| Jonathan was about to confirm the room when he receives
an instant message from his friend requesting him to play . Jonathan quickly
responds aknowledging that he will play but only for a short time. He
then minimizes his browser window and begins playing.
Three hours later Jonathan is still playing...
|
Since Jonathan has yet to book any rooms, our system
shows Eugene that the Bush Room is among the rooms available tomorrow at
2:00pm for 50 people.
Eugene confirms the Bush Room, and the reservation is inserted into
the database.
|
| Jonathan finally finishes playing but then gets
preoccupied with his girlfriend. Sometime later Jonathan finally
remembers that he has yet to reserve a room for tomorrow. He restores
his browser window, but does not reload the page (i.e., he is looking at
the page that was generated for him hours ago), and then chooses to
reserve the Bush Room for 1:00pm tomorrow. |
|
What is the outcome of this situation? Double booked rooms. To make our
system robust (and to prove your worth as a web programmer), you must check
for reservation conflicts twice, once when serving up the list of available
rooms, and again before inserting into the database.
- There also arises a rare situation where two users submit room requests at
precisely the same second. Set yourself apart from mediocre web programmers by
accounting for this situation. Lock the room reservations table before
querying to check for conflicts. This will prevent anyone else from updating
the table while you are querying for room availability. For more information
about transactions and locking tables for Oracle, refer to the of
SQL for Web Nerds (). Note again, the
example code in this book is for Oracle, but the concepts are consistent
across all Relational Database Management Systems.
Our basic functionality is in place. Administrators can create rooms, and
users can reserve rooms. Our system prevents double booking rooms and
reservation conflicts. Now it's time to add the features and other little things
that make the thing real.
Here's what we need to do for the admin pages:
- Link each room in the /admin/index.aspx
listing to room-edit.aspx where the administrator
can change the name of the room, mark a room as requiring approval, etc.
- When a room is marked as requiring approval, you'll need to designate a
room administrator for the room. Create a page user-search.aspx that will search through the
users table so that a user can be chosen as the room
administrator.
- Create a directory /admin/reports/ and build a
page index.aspx within the reports directory. This page shows usage reports for
each room, and should contain a list of rooms, and for each room the total
nmber of hours that the room has been reserved. Also show the average number
of people in a reservation for that room. Make this list sortable by name,
number of hours booked, or average number of people. All statistics should be
limited to reservations in the preceding 12 months. Be careful not to leave
out of your report rooms which haven't had any reservations. These rooms
should show up in the report with total hours being 0.
Hints:
- ASP.NET's DataGrid Control allows you to specify sortable columns.
- Let SQLServer do the any date arithmetic, rounding, summing, or
averaging for you.
- You can do this report with a 4-line SQL query by using the GROUP BY
clause.
- You might find SQLServer's NVL helpful for rooms without any
reservations.
- The room names in your /admin/reports/index.aspx should be links to details.aspx, where all reservations are shown, most
recent at the top. Make this list sortable as well.
- Edit new.aspx so that professors see a list of
both unreserved and reserved-but-bumpable (i.e., rooms not reserved by another
professor) rooms. Also, students or any non-professors should see a flag by
rooms that require approval. Be sure professors can differentiate between
unreserved and reserved-but-bumpable rooms.
- Edit new.aspx to handle a few more situations:
- Professors bumping students. This involves deleting (or if you'd prefer,
just changing the status) of the original reservation, sending email to the
student, then inserting the professor's reservation.
- Students requesting approval-required rooms. This involves inserting the
reservation, emailing the room administrator, and serving a confirmation
page to the user saying something along the following lines, "your request
has been submitted to ... We'll let you know if it's approved".
- Room administrators should be able to approve or deny room requests
directly from their email client. So the email notice to the room
administrator should contain encrypted URLs for "one-click approval." You'll
probably want one URL for approval, and one URL for denial. These URLs should
allow the room administrator to approve/deny the request without having to log
in. You'll want to encrypt these URL's in some way so that a user cannot
approval his own request by performing URL surgery.
One suggestion is to use a random number generator to assign a secret key
to an approval-required reservation. By inserting this key into the
room_reservations table at the time of the reservation, you can
always match it back to the specific reservation. You may then choose to pass
this key as a variable in the URL for approval/denial.
Congratulations, you now have a working room reservation system.
A can be thought of as an entry point into your application. Web
Services are methods within your application that have been programmatically
exposed on the internet. Microsoft's version of web services uses SOAP, which
supports Remote Procedure Calls (RPC), so that methods can be invoked across
HTTP. What does this mean? Any internet device (browser on a PC, , WAP
Phone, etc) will be able to see, invoke, and use your methods.
The idea and importance of Web Methods will become clearer once you start
creating some yourself.
Let's start off with a real simple example. By now you should be familiar
enough with C# to recognize the following syntax. If you need to, refer to the
.NET Framework Reference Documentation. I also recommend Eric Gunnerson's as a good reference for C#.
We have the following method that returns the factorial value of the given
argument:
class Factorial {
public int GetFactorial (int n) {
int answer = 1;
int counter = n;
while (counter > 0) {
answer *= counter;
counter--;
}
return answer;
}
}
As you can see, GetFactorial is a
public method on the class Factorial. To make this
method remotely available, and invokable across HTTP, we'll need to make the
following additions:
<%@ WebService Language="C#" Class="Factorial"%>
using System.Web.Services;
class Factorial {
[WebMethod]
public int GetFactorial (int n) {
int answer = 1;
int counter = n;
while (counter > 0) {
answer *= counter;
counter--;
}
return answer;
}
}
Notice that we've added a WebService directive at the top of the page. This directive
has an attribute Language, which lets the compiler
know which language should be used to compile the web method. You'll also have
to set the Class attribute within the directive which
specifies the name of the web service class. In our example, the web service
class happened to be in the same file as the directive. Alternatively, you could
have specified a web service class which is wrapped within a dll file inside the
\bin directory.
Now let's look at the [WebMethod] portion of the code. This is an attribute
which simply specifies that the method should be enabled for Web Services.
Notice that we had to import the System.Web.Services
namespace to use this attribute.
That's it!! That's the bare minimum you have to do to expose your method as a
"Web Method". However you should always practice good documentation techniques
to make your method even more accessible. You can take advantage of properties
on the WebMethod attribute to help you accomplish
this. Most programmers don't document their code, and thus makes it harder for
others to decide whether to use it, or impossible to figure out what it does.
The whole purpose for setting the WebMethod attribute
is to make your method distributable across the internet, so good documentation
is even more important. Hold yourself to a higher standard by taking advantage
of the Description property to help you document your
code. A professional programmer's code would probably look something like this:
<%@ WebService Language="C#" Class="Factorial"%>
using System.Web.Services;
[WebService(Description="The Factorial Class")]
class Factorial {
[WebMethod(Description="A method that returns the factorial value of a given number")]
public int GetFactorial(int n) {
int answer = 1;
int counter = n;
while (counter > 0) {
answer *= counter;
counter--;
}
return answer;
}
}
Now let's test it out. Cut and paste the code above into a
temporary file named service_tmp.asmx (note the
.asmx extension used for Web Service files). Save it
to your web server and load the page in your web browser. Note there it is also possible
to create webservices out of csharp files. For more information on how to do this, refer to the
section Precompiled Web Services in . If you're using your
local machine as your web server, remember to load the page using
localhost in the address field of the browser rather than the direct path
(i.e., http://localhost/service_tmp.asmx, rather than
c:\inetpub\wwwroot\service_tmp.asmx). You should see a generated web interface for your web service.
Use the generated HTML interface for your web method by calling the method
with some parameters. If you've provided a valid parameter (n > 0)
then you should see an XML document as the return result. By returning the value
as an XML data structure, SOAP can easily send this data across the wire. Any
platform can use this information, provided that they can understand the XML
language. This xml document should be in the form:
<?xml version="1.0" ?>
<[data type] xmlns="http://tempuri.org/">[value]</[data type]>
We'll also need to be able to describe our methods so that other programs can
access them. Microsoft's solution for this is the Service Description
Language (SDL). The SDL serves as a contract for users or programs on the
public internet to be able to use our services and web methods. The SDL is a
structured contract that is machine-readable, so that programs will know how to
use your services, i.e. what arguments to pass, the return data type, etc. Click
on the SDL Contract link on your service_tmp.asmx page. The link should bring you to an
another XML document. I've cut out a portion of this document below:
<httpget xmlns="urn:schemas-xmlsoap-org:get-sdl-2000-01-25">
<service>
<requestResponse name="GetFactorial" href="http://localhost/test/service.asmx/GetFactorial">
<request>
<param name="n"/>
</request>
<response>
<mimeXml ref="s0:int"/>
</response>
<info>A method that returns the factorial value of a given number</info>
</requestResponse>
</service>
</httpget>
Remember we didn't have to do anything fancy to describe our
web service. In fact, the server generated this contract for us. Thus to the
programmer, they don't have to do anything special to make sure that their web
method or web service will be accessible be other programs. However, it's still
important to be familiar with the SDL. Let's briefly examine what the SDL
contract is all about.
The portion I've cut out above is for invoking the method over a HTTP-Get
Request. You can see this because the code snippet is wrapped around
<httpget> tags. Being able to invoke the method via a HTTP-Get means that
the method can be called by sending the data to the server via a URL (an example
would be http://localhost/test/service_tmp.asmx/GetFactorial?n=3). You'll
also notice that the SDL describes things such as what arguments should be
passed in (<request>...</request>), what is the return data type
(<response>...</response>), and more information about the method
(<info>...</info>). If you look over the entire SDL, you'll see that
are analogous sections in the SDL for HTTP-Post and SOAP requests.
Look over the SOAP section of the SDL. This can be found at the beginning of
contract enclosed within <soap>...</soap> tags. Familiarize yourself
with the structure of the document by answering the following questions:
Obviously you can answer
these questions by looking at the source code of the web method created above,
but don't cheat. Look over the SDL and verify your findings with the source
code. Remember, this exercise is to familiarize yourself with the structure of
the SDL contract.
Invoking Web Services through .NET
.NET includes a wsdl tool, which creates a proxy class so we don't have
to worry about network or marshalling code. Although Visual Studio 7 will
eventually do this, we would use wsdl on the Factorial example in
the following fashion:
wsdl /language:CS /namespace:Factorial http://localhost/service_tmp.asmx?sdl
This tells the utility to make a proxy class for service_tmp.asmx, which is
written in C#, and make a namespace, Factorial, that contains the proxy class.
The utility will create a C# source file for us. The filename will be the
namespace we specified followed by the extension cs for C#.
To make our Factorial service programmatically accessible to other .NET
users, we compile the resulting source code which creates a dll for our proxy
class. Then, we give a copy of the dll to anyone who wants to use our service.
When others place our dll in their bin directories, They can use our service in
their ASP.NET pages just like they would if they wrote the service themselves. In
our Factorial example, what would actually happen is the proxy class would tell
our server to instantiate the Factorial class on our server and communicate with
the remote sites accessing our service through XML, but all of those details are
hidden from the remote sites for simplicity.
What are the advantages of Web Methods?
The Factorial example above is a really simple example. In fact it would
probably never be used if it were a real Web Service available on the internet.
However, for arguments sake, let's assume that calculating the factorial of a
number is an extremely CPU intensive task. If you have an extremely powerful
computer running a web service, now exposing this method on the internet becomes
important to other users. Now they can "borrow" your CPU for calculating a
factorial, and just wait for the return value.
Now lets forget about the Factorial example. Instead, lets think about a
method that actually extracts some information out of our database. If we
exposed such a method, we are now allowing programmatic access to our database
for other programs or programmers to take advantage of. If we were a supplier of
some product and a client needed "real-time" inventory to be coupled with one of
their scripts to fill or place orders, now their programs can interact with our
database to get the latest inventory at any moment.
Your room reservation system has become very popular. Professors and students
are constantly booking rooms. However you have heard complaints that users are
always forgetting which rooms they have booked or on which days. Email reminders
are annoying and increases the chances of your web server crashing, so you don't
want to take that approach.
Your buddy, Peter, has a built a popular web calendar service, such as or
that is widely being used
on campus. You talk to Peter and have both decided that a great service would be
to somehow automatically insert the room reservation information into his
calendar service.
Write a new method, or expose an existing method so that Peter can have
access to users' room reservation information. Assume that Peter is from MIT,
and is competent enough to handle an XML data structure. Here are some things to
consider:
- Name your method
- Peter is not going to know anything about your user_id's. Instead have
your method take in an email address as a parameter to identify a specific
user's reservation information.
- Just have your method take in a start date and end date, and return all
reservations between those 2 dates. Let Peter determine whether the
reservations have already been inserted into the calendar or not.
- Assume that your method will be invoked using the SOAP protocol. Some data
types supported by SOAP include Primitives, Enum, Structs, Classes, DataSets,
etc. For a complete set of DataTypes and Descriptions supported by SOAP and
HTTP Get/Post refer to page 162 of .
- You could stop when your method returns XML, but assume that Peter is also
running the .NET platform and extend your service to him using the
wsdl tool. That way all of the XML is hidden, and as far as Peter is
concerned calling your sevice is like calling one of his own methods.
Congratulations! You've just built a real web service that can be used by
people and programmers across the internet.
If you have friends who are also working on this pset, test out each other's
service by writing a page that shows the availability of one of your cohorts.
Alternatively, you can also write a page that uses your own web service to
show availability if you are doing this problem set alone.
This problem set was written in December 2000 by Peter Weng (). It is copyright
2000 and may be reused provided credit is given to it's original author with a
hyperlink to this document. Original inspiration for this problem set comes from
and Room
Reservations written for TCL and AOLServer.