-7-
This chapter concentrates on the basics
of the Internet. By the time you reach the end of this chapter, you will be
comprehensibly conversant with the role of a web server, a web browser and
ASP.Net. You will also be in a position to appreciate the indispensability of
ASP.Net, in writing powerful business applications.
Understanding the samples
provided by Microsoft, is akin to viewing ASP from a height of 10000 miles.
At the outset, you are required
to create a file called a.cs in the sub-directory c:\inetpub\scripts. We will
explain the relevance of this subdirectory in due course of time.
a.cs
public class zzz
{
public static void Main()
{
System.Console.WriteLine("<b>hi");
}
}
The above is a short and sweet C#
program! Now, compile this program using the csc command, as in ‘csc a.cs’.
Then, run the executable and your
dos screen will simply display 'hi' with the bold tag in front of it.
To run this exe file from our
browser, we key in the following URL in the browser.
http://localhost/scripts/a.exe
This URL seems logical, since we
have placed the file a.exe in the scripts sub-directory. But, we do not seem to
get anything right the first time. This is evident from the following errors
that have been generated:
Output
CGI Error
The specified CGI application misbehaved by not returning a complete set of HTTP headers. The headers it did return are:
hi
The first line indicates that we
have written a CGI application. CGI stands for Common Gateway Interface. And we
have not adhered to the rules pertaining to a CGI application. The web browser
expects a complete set of headers, which we have not provided. Subsequently, we
see 'hi' displayed in bold.
Before moving into the details
about the rules, headers etc. of CGI programming, we shall send a header
across.
a.cs
public class zzz
{
public static void Main()
{
System.Console.WriteLine("Content-Type:text/html");
System.Console.WriteLine("<b>hi");
}
}
We compiled the above program as
before and provided the same URL in the browser. It is preferable to load the
browser again and provide the URL. You could even click on the refresh icon
instead. Whichever method you follow, the same error is generated again.
In future, we will not repeat
these steps, but we do expect you to follow them, so that your output matches
with ours. Writing one more WriteLine function makes no difference at all.
a.cs
public class zzz
{
public static void Main()
{
System.Console.WriteLine("Content-Type:text/html\n");
System.Console.WriteLine("<b>hi");
}
}
Output
hi
The mere inclusion of an 'enter',
represented by '\n', results in elimination of all the errors. We now see the
word 'hi' displayed in bold in our window. Before we explain as to why everything suddenly starts working
fine, let us make a small modification in the above program.
a.cs
public class zzz
{
public static void Main()
{
System.Console.WriteLine("Content-Type:text/plain\n");
System.Console.WriteLine("<b>hi");
}
}
Output
<b>hi
Now, it is time for us to
demystify this mystery.
The web or the HTTP protocol is
very simple to understand if you follow certain rules. When the browser
requests for a file from the web server, depending upon the extension given to
the file, the Web Server does one of the following:
• It picks the file up from its local hard disk and sends it across
OR
• It executes the executable file which has been requested for, from the disk which generates this file and sends it over.
In the case of a file with a .exe
extension, the server executes the file or program on its machine. This program
is not restricted to producing an HTML file only. It can create a file of any
type. If it were restricted to generating only HTML files, it would have made
the web very restrictive.
The contents of every file vary
depending on the file type. For example, an HTML file will contain tags; a .jpg
file will contain images, and so on. The browser must display the files in the
right format, on receiving them.
To inform the browser about the
file coming across, the exe file or program creates a header that signifies the
file type or the content type. A header is nothing but a word that is given
some value and which ends with a colon. Thus, the phrase 'Content-Type' is a
header since it ends in a colon, and the value assigned to it is 'text/plain'.
The rules of CGI state that all
headers must end with an Enter or '\n' symbol. This is so because a file can
have multiple headers. An 'enter' symbol, when placed by itself on a line, signifies
the end of all the headers. It also marks the start of the content. The Web
server is free to add its own headers to the list of headers generated by the
program. This composite data is then transferred to the browser.
Since the headers were not specified
in the first program, an error was generated. The presence of a Content-Type
header in a sense is mandatory for any
file that has to be transferred from the server to the browser.
The second error was reported
because we had omitted the 'enter' symbol all by itself, on a separate line, to
mark the end of all headers. Thus, more headers were expected from the server.
When this did not happen, the browser generated an error. The WriteLine
function adds an 'enter' symbol by default, but an additional \n is required
all by itself, to indicate an end to the Header values.
The browser uses the header named
Content-Type, to determine the type of data sent by the web server.
Accordingly, it displays the file in the right format.
The type text/html refers to a
text file containing html tags. Thus, 'hi' was displayed in bold. However, the
header value of text/plain informs the browser that plain text content is being
sent over, and hence, the tags are not parsed.
a.cs
class zzz
{
public static void Main()
{
string s;
System.Console.WriteLine("Content-Type:text/html\n");
s=System.Environment.GetEnvironmentVariable("PATH");
System.Console.WriteLine(s);
}
}
Output
C:\Program
Files\Microsoft.Net\FrameworkSDK\Bin\;C:\WINNT\Microsoft.NET\Framewor
k\v1.0.2204\;C:\WINNT\system32;C:\WINNT;C:\WINNT\System32\Wbem;C:\Program
Files\Microsoft SQL Server\80\Tools\BINN
An environmental variable is a
word stored by the operating system. In the world of Windows 2000, if we give
the command >set in the dos box, a
list containing a number of words with their corresponding values is displayed.
At the command prompt, if we give
the command >set aa=hi, it creates an environmental variable named 'aa' and
assigns it a value of 'hi' for the current dos session. On issuing the same set
command again, this recently created variable and its value, get displayed
along with the other environmental values.
The namespace System.Environment
has a static function called GetEnvironmentVariable. This function accepts an
environmental variable name and returns its value. In our program, we ask the
function to display the value of the environmental variable named PATH.
These variables are created and
maintained by the operating system. The web server too can ask the operating
system to create some variables, before it executes a program.
a.cs
class zzz {
public static void Main()
{
System.Collections.IDictionary i;
i=System.Environment.GetEnvironmentVariables();
System.Collections.IDictionaryEnumerator d;
d=i.GetEnumerator ();
System.Console.WriteLine("Content-Type:text/html\n");
System.Console.WriteLine(i.Count + "<br>");
while (d.MoveNext())
{
System.Console.WriteLine("{0}={1}<br>",d.Key,d.Value);
}
}
}
The above program shows 46 environmental
variables in the browser. An object named i of datatype IDictionary is defined.
It is initialized to the return value of the static function called
GetEnvironmentVariables.
This function returns an object
that contains all the environmental variables. We need a constant way of
retrieving data that is similar, but which has multiple occurrences. To do so,
the designers of the C# programming language have given us an enumeration
object which enables us to list the multiples values.
We use the GetEnumerator function
from the object 'i', and store its return value in the enumeration object named
'd'. The object named MoveNext iterates through the list of values. The object
contains two members called Key and Value. Basically, the format is Key=Value.
Thus, Key stands for the variable name and Value is the value contained in the
Key.
In the WriteLine function, {0}
displays the first parameter, {1} displays the second variable, and so on. The
Count member of IDictonary returns the count of the environmental variables.
Some of the variables are created
by the Operating System while it starts execution. The others are created by
the IIS WebServer. We searched the world over, but could not find a variable
named Query_String.
This program will be used in the
future. So we want you to make a copy of it and name it as b.exe: >copy a.exe b.exe
a.html
<html>
<body>
<form action=http://localhost/scripts/a.exe>
<input type = text name = aa>
<input type = text name = bb>
<input type = submit value = click>
</form>
</body>
</html>
The above HTML file is placed in
the wwwroot sub-directory and loaded as http://localhost/a.html. This file
shows two textboxes and a button labeled 'Click'. After entering the words 'hi'
and 'bye' in these textboxes, click on the button. You will be surprised to see
the browser displaying the same output as shown in the earlier program. But
observe that the count now displays the number 48, and the address bar now
contains the new URL http://localhost/scripts/a.exe?aa=hi&bb=bye. One of
the newly added name-value pair is :
QUERY_STRING=aa=hi&bb=bye
On receiving the new URL from the
web browser, the server simply creates an environmental variable called
QUERY_STRING and initializes it with the values that are contained in the URL after
the ? symbol. Thereafter, it calls the program named a.exe given in the URL,
from the relevant directory.
The program on the server can
easily identify as to what the user has entered in the textboxes. It simply has
to use the Request class or parse the QUERY_STRING variable.
Cookies
Before taking a leap into this
section, we would like you to set a few options in your browser. First, go to
menu option 'Tools' and select the last option named 'Internet Options'. Select
the Security tab. Ensure that you have selected the ‘Local Intranet’ option and
click on Custom level. Scroll down the list box until you see the heading
'Cookies'. For both the sub options, select the 'Prompt' radio button. The
default is 'Enable'. Restart the browser after making the changes. Now, run the
program given below in your browser.
a.cs
class zzz
{
public static void Main()
{
System.Console.WriteLine("Content-Type:text/html\nSet-Cookie:aa=vijay\n");
System.Console.WriteLine("hi");
}
}
To our utter surprise, we see a
dialog box titled 'Security Alert', asking us whether we would be interested in
saving a temporary file sent by the WebServer on our hard disk. This file is
termed as cookie.
Firstly, we click on the button
labeled 'More Info'. This extends the dialog box to give us more information
about the cookie. Notice that the
cookie is named as aa, and along with other information, it reveals that the
data in this cookie is vijay.
Click on 'yes' and you will see
'hi' in the browser. Subsequently, if you run b.exe in the browser, it will
display all our environmental variables as before. But now, an additional
environmental variable called HTTP_COOKIE is created with the value of
aa=vijay.
Before we go further, let us
first run the following ASP program in the browser using:
http://localhost/a.aspx?aa=hi&bb=bye
a.aspx
<html>
<%@ language="C#" %>
<body>
<%
Request.SaveAs("c:\\z.txt",true);
%>
</body>
</html>
z.txt
GET /a.aspx?aa=hi&bb=bye HTTP/1.1
Connection: Keep-Alive
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Accept-Encoding: gzip, deflate
Accept-Language: en-us
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; COM+ 1.0.2204)
For the ones who came in late,
whenever we click on a button of type submit, a new URL is generated. The Web
browser calls on the WebServer again, and sends it a packet of data.
To view the contents of this
packet, the Request property is used. This property returns an HttpRequest
object. SaveAs is one of the functions in the object that takes two parameters:
• The filename in which it can save the request.
• A bool value of true or false. If the value is true, the header is also saved in the file.
Any packet sent by the browser
has two parts to it. It starts with the word GET, followed by the URL that is
to be fetched. The name of the computer, localhost, is removed from the URL, as
the browser connects to it. Following it is the data, as entered in the browser
window.
All this is part of the HTTP
protocol. The protocol states that the URL is to be followed by the http
version number.
When the web server sends data to
the browser, the packet starts with the headers, followed by a blank line, and
finally, followed by the rest of the HTML file. On the other hand, in the
packet sent from the browser, the URL is stated first, followed by the data and
finally, there are the headers. This is exactly the reverse of what happens in
the case of the web server!
At first, we run the executable
from the browser as http://localhost/scripts/a.exe and accept the cookie.
Thereafter, the file a.aspx is copied from c:\inetpub\wwwroot to
c:\inetpub\scripts and loaded as http://localhost/scripts/a.aspx. The file
named z.txt, is shown below.
z.txt
GET /scripts/a.aspx HTTP/1.1
Connection: Keep-Alive
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Accept-Encoding: gzip, deflate
Accept-Language: en-us
Cookie: aa=vijay
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; COM+ 1.0.2204)
A cookie has a header named Set-Cookie,
which is sent by the server to the client. A point to be noted here is that the
server initiates a cookie, and not a client.
There is a program that runs on
the server, which directs the server to send a cookie, which has a specific
name and value. When the client browser, which could be Netscape, IE or any
other browser, sees a header named Set-Cookie, it checks for the cookie-option
values. If the prompt is set on, the browser will display a message, thereby,
requesting for permission to accept or reject the cookie.
If the reply is 'yes', then
whenever the client connects to the server, it will contain a header named
Cookie, which it will send to the server. Thus, we see the header Cookie:
aa=vijay in the file z.txt. The text 'Set-' is removed from the Header.
Thus, a cookie is basically a
header-value that is sent by the server, and which is returned by the browser.
These cookies remain in existence
only until the browser is alive. If you close the current copy of the browser
and reload it again with the file a.aspx, the cookie header will not be seen.
System.Console.WriteLine("Content-Type:text/html\nSet-Cookie:aa=vijay; path=/aa\n");
A cookie header, along with the
name-value, has a path that decides on the sub-directories that the cookie can
be sent to. On running a.exe from the scripts sub-directory, the path parameter
in the cookie dialog box shows the path as /scripts/.
Therefore, if you load a.aspx
from the inetpub\wwwroot sub-directory, it will not show the Cookie: header in
the file z.txt. This is because the browser not only stores the domain name or
name of the computer, but also the URL or sub-directories that the cookie
should be sent to. As the path is /scripts, the browser will only send a
Cookie: header to the URLs that access files in the /scripts sub-directory.
Now, we shall see how our cookies
can be made eternal, so that they never say die.
a.cs
class zzz
{
public static void Main()
{
System.Console.WriteLine("Content-Type:text/html\nSet-Cookie:aa=Vijay; expires=Tuesday, 03-04-2002 12:12:23\n ");
System.Console.WriteLine("hi");
}
}
After leaving a space after the
semicolon, we have simply added the word 'expires'. This is initialized to a
date in a certain format, followed by time in hours, minutes and seconds. The
cookie dialog box( that had earlier displayed the Expires as 'end of session),
now displays a specific date.
Upto this date, each time we
connect to a certain path on the server called localhost, the browser will send
this cookie. The earlier cookies were termed as 'session cookies', since their
life span extended only till the end of the session. The cookies where
'expires' is mentioned, are called 'non session cookies'. If we disable the
cookies, the browser does not send the Cookie: header to the server.
Let us now understand the
Response and Request objects in ASP.Net, which are introduced when they are
derived from the Page class.
The Request Object
The property Request in the Page
class, is of type HttpRequest, and it contains code that handles the data sent
by the browser.
This makes it easier for our aspx
program to parse the output sent to us by the browser.
a.aspx
<html>
<%@ language="C#" %>
<body>
<%
String[] s = Request.AcceptTypes;
Response.Write(s.Length.ToString());
for (int i = 0; i < s.Length; i++)
{
Response.Write(s[i] + "<br>");
}
%>
</body>
</html>
Output
8image/gif
image/x-xbitmap
image/jpeg
image/pjpeg
application/vnd.ms-powerpoint
application/vnd.ms-excel
application/msword
*/*
The web server sends the header
Content-Type to signify the type of content that is to follow. The type given
after Content-Type is also called the 'MIME type'. The client, i.e. the browser
too sends across the types it supports, by using a header called Accept.
This header is sent as follows:
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
Thus, the MIME type starts with
the name of a family, such as image, text, application etc. This is followed by
a slash /, after which we specify the different types within the family. The
symbol */* indicates that the browser supports all MIME types.
The Request object has a property
called AccessTypes, which returns an array of strings. We simply display them
using a 'for' loop. The Length property gives us the number of members present
in the array.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.ApplicationPath);
%>
Output
/
The property ApplicationPath
displays the virtual path to the currently running server application. Even if
you copy the file in the scripts sub-directory and change the URL to
http://localhost/scripts/a.aspx, the result will still be a slash i.e. /.
a.aspx
<%@ language="C#" %>
<%
HttpBrowserCapabilities b = Request.Browser;
Response.Write("Type = " + b.Type + "<br>");
Response.Write("Name = " + b.Browser + "<br>");
Response.Write("Version = " + b.Version + "<br>");
Response.Write("Major Version = " + b.MajorVersion + "<br>");
Response.Write("Minor Version = " + b.MinorVersion + "<br>");
Response.Write("Platform = " + b.Platform + "<br>");
Response.Write("Is Beta = " + b.Beta + "<br>");
Response.Write("Is Crawler = " + b.Crawler + "<br>");
Response.Write("Is AOL = " + b.AOL + "<br>");
Response.Write("Is Win16 = " + b.Win16 + "<br>");
Response.Write("Is Win32 = " + b.Win32 + "<br>");
Response.Write("Supports Frames = " + b.Frames + "<br>");
Response.Write("Supports Tables = " + b.Tables + "<br>");
Response.Write("Supports Cookies = " + b.Cookies + "<br>");
Response.Write("Supports VB Script = " + b.VBScript + "<br>");
Response.Write("Supports Java Script = " + b.JavaScript + "<br>");
Response.Write("Supports Java Applets = " + b.JavaApplets + "<br>");
Response.Write("Supports ActiveX Controls = " + b.ActiveXControls + "<br>");
Response.Write("CDF = " + b.CDF + "<br>");
%>
Output
Type = IE5
Name = IE
Version = 5.5
Major Version = 5
Minor Version = 0.5
Platform = WinNT
Is Beta = False
Is Crawler = False
Is AOL = False
Is Win16 = False
Is Win32 = True
Supports Frames = True
Supports Tables = True
Supports Cookies = True
Supports VB Script = True
Supports Java Script = True
Supports Java Applets = True
Supports ActiveX Controls = True
CDF = False
The Browser property in the
Request Object returns an object of type HttpBrowserCapabilities. Thus, an
object b of this type is created and it stores the return value of this
property. We then display all the members of this class, which consist of the features
of the browser that just connected to the server.
Depending upon the values that
are supported and returned by the browser, the aspx file can be made generic,
to enable it to handle the differences among browsers.
The type member returns the name of
the browser along with its version number, whereas, the Browser property
returns only the name. The version number is displayed as 5.5. We can even
display the major and the minor version numbers separately. It is mandatory to
have the Version of the Explorer greater than 5.0, otherwise, the .Net
framework does not reveal the right values.
The property named 'Platform'
informs us about the Operating System that the browser is running on. If the
browser is currently running on Windows 2000, the platform property still
displays the value as WinNT. Our version of IE is the final copy, and not the
beta version, hence, the value of the property named beta is shown as false.
Search engines crawl all over,
looking for websites. Since ours is a simple browser, the property named
IsCrawler is shown as false. America Online is the largest on-line service in
the world and it has its own branded browser. Since we are using IE from
Microsoft, the property named AOL has a value of false.
Earlier, Microsoft had a 16 bit
operating system called Windows 3.1. As we are presently running their 32 bit
Operating system, the property Win16 is false, while the property Win32 is
true. An HTML page is divided into smaller parts by frames. Today, all browsers
support frames. Thus, the Frames property has a value of true.
Earlier some browsers could not
display tables. Thus, the tables property was introduced in the object. Today,
all the browsers fully support tables. However, not every browser can run Java
applets within the browser. In our case, since IE can do so, the JavaApplets
property is true.
All browsers support cookies,
since it is a standard created by Netscape. We have two main client side
scripting languages, VBScript from Microsoft and Javascript from Netscape. Since
our browser supports both, their properties are true. And since ActiveX was
invented by Microsoft, this property also has a value of true.
Finally, if we want to webcast
something, there is a new format called Channel Definition Format or CDF, which
has to be used. For some reason, IE does not support it.
a.aspx
<%@ language="C#" %>
<%
Encoding e = Request.ContentEncoding;
Response.Write(e.EncodingName); %>
Output
Unicode (UTF-8)
The Request.ContentEncoding
property returns an Encoding object. This object also has a large number of
properties and methods. One of them is called EncodingName, which reveals the
Character Set that the browser uses to transfer data to and fro.
a.cs
class zzz
{
public static void Main()
{
System.Console.WriteLine("Content-Type:text/html");
System.Console.WriteLine("Set-Cookie:aa=vijay0; expires=Tuesday, 09-09-2001 12:12:23");
System.Console.WriteLine("Set-Cookie:a1=vijay1; expires=Tuesday, 09-09-2001 12:12:23");
System.Console.WriteLine("Set-Cookie:a2=vijay2; expires=Wednesday, 10-10-2001 12:12:23\n");
System.Console.WriteLine("hi");
}
}
We first loaded the following C#
executable from the script sub-directory:
http://localhost/scripts/a.exe
This sets three cookies for the
browser session, which is currently active.
Then, we ran the aspx program (as
given below) from the scripts sub-directory, using:
http://localhost/scripts/a.aspx
a.aspx
<%@ language="C#" %>
<%
HttpCookieCollection cc;
cc = Request.Cookies;
Response.Write(cc.Count.ToString() + "<br>");
for (int i = 0; i < cc.Count; i++)
{
HttpCookie c = cc[i];
Response.Write(c.Name + "=" + c.Value + "<br>");
}
%>
Output
4
aa=vijay0
a1=vijay1
a2=vijay2
ASP.NET_SessionId=wnyumy5544u3tj55tdrp0u45
Request.Cookies returns an HttpCookieCollection
object that is stored in the object cc. This object named cc has a member named
count, which returns a count of the number of cookies present in the
collection. We have 4 cookies, and hence, the 'for' loop is repeated four
times.
HttpCookieCollection has an
indexer that allows access to the individual cookies. Thus, cc[0] refers to the
first HttpCookie object, and so on. The HttpCookie class in turn, has two
important members, i.e. Name and Value, which display the name of the cookie and
its value, respectively.
a.aspx
<%@ language="C#" %>
<%
HttpCookieCollection cc;
cc = Request.Cookies;
HttpCookie c = cc["a1"];
Response.Write(c.Name + "=" + c.Value + "<br>");
%>
Output
a1=vijay1
The indexer in the HttpCookieCollection
object can also accept a string, which is the name of the cookie. It returns a
cookie object that represents that cookie. The Value member will display the
value contained in the cookie. This will retrieve only a single cookie.
a.aspx
<%@ language="C#" %>
<%
HttpCookieCollection cc;
HttpCookie c;
cc = Request.Cookies;
String[] s = cc.AllKeys;
for (int i = 0; i < s.Length; i++)
{
Response.Write(s[i] + "<br>");
}
for (int i = 0; i < s.Length; i++)
{
c = cc[s[i]];
Response.Write("Cookie: " + c.Name + " ");
Response.Write("Expires: " +c.Expires + " ");
Response.Write ("Secure:" + c.Secure + " ");
String[] s1 = c.Values.AllKeys;
for (int j = 0; j < s1.Length; j++)
{
Response.Write("Value" + j + ": " + s1[j] + "<br>");
}
}
%>
Output
aa
a1
a2
langpref
ASP.NET_SessionId
Cookie: aa Expires: 1/1/0001
12:00:00 AM Secure:False Value0:
Cookie: a1 Expires: 1/1/0001
12:00:00 AM Secure:False Value0:
Cookie: a2 Expires: 1/1/0001
12:00:00 AM Secure:False Value0:
Cookie: langpref Expires: 1/1/0001
12:00:00 AM Secure:False Value0:
Cookie: ASP.NET_SessionId
Expires: 1/1/0001 12:00:00 AM Secure:False Value0:
Just as there are many ways to
skin a cat, there are also numerous ways of displaying a cookie. The
HttpCookiecollection class has a member called AllKeys that returns an array of
strings, which represent the names of the cookies or its keys. Thus, in one
stroke, we can figure out all the names of the cookies.
After displaying the individual
names of the Cookie in the 'for' loop, the same name is used in the indexer, to
access the individual cookie object. The 'expires' property inexplicably, does
not display the correct date and time. Further, the Values object must be used
in place of the Value property, because a cookie can have multiple values.
Thus, c.Values.AllKeys returns an array of strings. Since in the present case,
every cookie has only a single value, the 'for' loop executes only once.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.FilePath);
%>
Output
/a.aspx
The property named FilePath
returns the virtual path of the request. The output reflected is /aspx, because we are loading this file from
the wwwroot sub-directory. If you run the file from the scripts sub-directory,
the output will display /scripts/a.aspx
a.aspx
<%@ language="C#" %>
<%
NameValueCollection c;
c=Request.Headers;
String[] s = c.AllKeys;
for (int i = 0; i < s.Length; i++)
{
Response.Write("Key: " + s[i] + " ");
String[] s1=c.GetValues(s[i]);
for (int j = 0; j<s1.Length; j++)
{
Response.Write("Value " + j + ": " + s1[j] + "<br>");
}
}
%>
Output
Key: Connection Value 0: Keep-Alive
Key: Accept Value 0: */*
Key: Accept-Encoding Value 0: gzip, deflate
Key: Accept-Language Value 0: en-us
Key: Cookie Value 0: langpref=C#; ASP.NET_SessionId=abksbkrxdgjmmj45hasiffzx
Key: Host Value 0: localhost
Key: User-Agent Value 0: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914)
The Headers property returns a
NameValueCollection object that represents all the headers. The rules for
handling headers are common for all headers. The rules are all in the form of
name=value. The object c has an AllKeys property, which returns a list of keys.
As before, we use a loop and call the function GetValues, which returns an
array of strings when it is supplied with a key value. Most of the time, we
only have a single value. Hence, our array s1 has a length of one.
a.aspx
<%@ language="C#" %>
<%
NameValueCollection c;
c=Request.Headers;
String[] s = c.AllKeys;
for (int i = 0; i < s.Length; i++)
{
Response.Write("Key: " + c[i] + " ");
String s1=c.Get(s[i]);
String s2=c.Get(i);
Response.Write("Value " + s1 + " " + s2 + "<br>");
}
%>
Output
Key: Keep-Alive Value Keep-Alive Keep-Alive
Key: */* Value */* */*
Key: gzip, deflate Value gzip, deflate gzip, deflate
Key: en-us Value en-us en-us
Key: langpref=C#; ASP.NET_SessionId=abksbkrxdgjmmj45hasiffzx Value langpref=C#; ASP.NET_SessionId=abksbkrxdgjmmj45hasiffzx langpref=C#; ASP.NET_SessionId=abksbkrxdgjmmj45hasiffzx
Key: localhost Value localhost localhost
Key: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914) Value Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914) Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914)
The output is the same as before,
but it is easier to comprehend now, since a function called Get is used, which
returns a single string value. Every value is displayed twice, because we have
used both the forms of the Get function, i.e. passing it a string and then, passing
it a number. It is the browser that sends these headers. You can easily verify
this by inspecting the file z.txt, which has been created earlier.
Let us first create a simple aspx
file that merely writes out the value of a property called HttpMethod.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.HttpMethod);
%>
a.html
<html>
<body>
<form action=http://localhost/a.aspx METHOD=GET>
<input type = text name = aa>
<input type = text name = bb>
<input type = submit value = click>
</form>
</body>
</html>
In the above HTML file, we have
added an attribute called 'METHOD=GET' to the form tag. When we click on the
'click' button, browser screen loads on, with the address containing the action
value of http://localhost/a.aspx, followed by the ? symbol and the name value
pairs. The word GET is also displayed in the browser window.
Now, we make a small modification
in the HTML file. We replace the words GET with POST. When we load the file,
everything remains the same. When we click on the button, the browser now
displays POST. Further, the URL contained in the address bar does not contain
either the question mark or the name-value pairs.
The difference between a GET and
POST method is that, in a POST, the data is transmitted as a separate packet,
whereas, in a GET, it is sent as part of the URL. In Get, there is a limit to
the amount of data that can be sent as part of the URL. Passwords and other
important information must always be sent using the POST method and not the GET
method.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.IsAuthenticated + "<br>");
Response.Write(Request.IsSecureConnection + "<br>");
%>
Output
False
False
We have neither authenticated our
connection, nor have we been using a secure connection. A secure connection begins
with 'https', instead of 'http'.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.Path);
%>
Output
/a.aspx
This property displays the
virtual path of the current request.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.PhysicalApplicationPath);
%>
Output
c:\inetpub\wwwroot\
The Web Server can be installed
anywhere on the hard disk. The default directory selected by IIS is
C:\inetpub\wwwroot. As we have used the defaults, the property PhysicalApplicationPath
reveals the same path. The PhysicalApplicationPath is called the home directory
or the root directory of IIS. Whenever a file is to be referred to on the hard
disk, this value is added to the filename. Further, the slash symbol /, which
represents the virtual directory, finally gets converted into this physical
path, while it is locating or sending files to the browser.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.PhysicalPath);
%>
Output
c:\inetpub\wwwroot\a.aspx
In this program, we go a step
further and ask for the full path name or the physical filename of our aspx
file.
For the next program, we write
the following URL in the browser:
http://localhost/a.aspx?aa=hi&bb=bye&aa=no
a.aspx
<%@ language="C#" %>
<%
NameValueCollection c=Request.QueryString;
String[] s = c.AllKeys;
for (int i = 0; i < s.Length; i++)
{
Response.Write(s[i] + " ");
String[] s1 = c.GetValues(s[i]);
for (int j = 0; j < s1.Length; j++)
{
Response.Write("Value " + j + ": " + s1[j] + " ");
}
Response.Write("<br>");
}
%>
Output
aa Value 0: hi Value 1: no
bb Value 0: bye
The QueryString property returns a NameValueCollection. The
AllKeys property of this object returns only two keys, which is because we have
repeated the parameter name aa twice. We are permitted to repeat names in HTML.
The 'for' loop is repeated twice
for the two keys. The GetValues function returns an array consisting of two
members for aa, which is because it contains two values i.e. 'hi' and 'no'. The
second 'for' loop displays these values.
Working with ASP+ is a pleasure,
since there is an inbuilt code for handling multiple values.
If we run the earlier HTML file
with the method as Post, we shall not receive any output, because the environmental
variable QueryString holds values only with the Get method.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.RawUrl);
%>
Output
/a.aspx
The RawUrl displays the URL in
its most primitive form.
a.aspx
<%@ language="C#" %>
<%
NameValueCollection c;
c=Request.ServerVariables;
String [] s = c.AllKeys;
for (int i = 0; i < s.Length; i++)
{
Response.Write(s[i] + "=");
String [] s1 =c.GetValues(s[i]);
for (int j = 0; j < s1.Length; j++)
{
Response.Write(s1[j] + " ");
}
Response.Write("<br>");
}
%>
Output
ALL_HTTP=HTTP_CONNECTION:Keep-Alive HTTP_ACCEPT:*/* HTTP_ACCEPT_ENCODING:gzip, deflate HTTP_ACCEPT_LANGUAGE:en-us HTTP_COOKIE:langpref=C#; ASP.NET_SessionId=abksbkrxdgjmmj45hasiffzx HTTP_HOST:localhost HTTP_USER_AGENT:Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914)
ALL_RAW=Connection: Keep-Alive Accept: */* Accept-Encoding: gzip, deflate Accept-Language: en-us Cookie: langpref=C#; ASP.NET_SessionId=abksbkrxdgjmmj45hasiffzx Host: localhost User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914)
APPL_MD_PATH=/LM/W3SVC/1/ROOT
APPL_PHYSICAL_PATH=c:\inetpub\wwwroot\
CONTENT_LENGTH=0
CONTENT_TYPE=
GATEWAY_INTERFACE=CGI/1.1
HTTPS=off
INSTANCE_ID=1
INSTANCE_META_PATH=/LM/W3SVC/1
LOCAL_ADDR=127.0.0.1
PATH_INFO=/a.aspx
PATH_TRANSLATED=c:\inetpub\wwwroot\a.aspx
QUERY_STRING=
REMOTE_ADDR=127.0.0.1
REMOTE_HOST=127.0.0.1
REQUEST_METHOD=GET
SCRIPT_NAME=/a.aspx
SERVER_NAME=localhost
SERVER_PORT=80
SERVER_PORT_SECURE=0
SERVER_PROTOCOL=HTTP/1.1
SERVER_SOFTWARE=Microsoft-IIS/5.0
URL=/a.aspx
HTTP_CONNECTION=Keep-Alive
HTTP_ACCEPT=*/*
HTTP_ACCEPT_ENCODING=gzip, deflate
HTTP_ACCEPT_LANGUAGE=en-us
HTTP_COOKIE=langpref=C#; ASP.NET_SessionId=abksbkrxdgjmmj45hasiffzx
HTTP_HOST=localhost
HTTP_USER_AGENT=Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914)
The server creates a large number
of variables. They are too numerous to be displayed. Here, we are only
displaying the variables that have values.
a.aspx
<%@ language="C#" %>
<%
Uri o = Request.Url;
Response.Write("URL Port: " + o.Port + "<br>");
Response.Write("URL Protocol: " + o.Scheme + "<br>");
Response.Write("URL Host: " + o.Host + "<br>");
Response.Write("URL PathAndQuery: " + o.PathAndQuery + "<br>");
Response.Write("URL Query: " + o.Query + "<br>");
%>
Output
URL Port: 80
URL Protocol: http
URL Host: localhost
URL PathAndQuery: /a.aspx?aa=hi&bb=bye
URL Query: ?aa=hi&bb=bye
The Url property in the Request object,
returns an HttpUrl object. This object has many properties, which break up the
URL into different components. The port number is related to the protocol used.
Every packet on the Internet is
tagged with a number that signifies the protocol that carries it. For e.g., the
http protocol has the port no. 80, E-Mail read is 25, FTP is 21, etc. Thus, the Port shows a value of 80 because
the URL that has been entered, starts with the syntax http:. Similarly, the protocol
used is http.
Host is the name of our computer.
PathAndQuery contains the name of the requested file along with the
querystring.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.UserAgent);
%>
Output
Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0; .NET CLR 1.0.2914)
A user agent is another name for
the browser. The internal name for Netscape was Mozilla. So, IE initially
referred to itself by the same name. Many of the websites performed a check on
the browser that was requesting for the file. If it matched IE, the page was not
sent across. However, today it is IE that has eventually won the browser war.
We shall talk about it later, since it is too long an account to be related to
you right away.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Request.UserHostAddress);
%>
Output
127.0.0.1
Every computer on the Internet is
known by a number, which is technically called an IP address. It is of a long
data type. This implies that it consists 4 numbers, each ranging from 0 to 255.
These numbers are separated by dots. This format is known as the decimal dotted
notation.
As every machine is known as
localhost and it is given an IP address 127.0.0.1. Thus, when we write
localhost in the IE address bar, it gets converted to 127.0.0.1.
a.aspx
<%@ language="C#" %>
<%
String[] s = Request.UserLanguages;
for (int i = 0; i < s.Length; i++)
{
Response.Write(s[i] + "<br>");
}
%>
Output
en-us
The property UserLanguages
returns the languages that the browser supports. In our case, the browser
supports the English language, or more precisely, 'en-us', which stands
for American English and not for
British English.
a.aspx
<%@ language="C#" %>
<%
String s = Request.MapPath("/quickstart");
Response.Write(s);
%>
Output
C:\Program Files\Microsoft.Net\FrameworkSDK\Samples\QuickStart
When the .Net sdk installs
itself, it creates virtual directories in IIS. Thus, when we write
http://localhost/quickstart, it converts the virtual directory named quickstart
to the path displayed above. The MapPath function in the Request Object,
converts a virtual directory to an absolute path on your hard disk.
The Response Object
a.aspx
<%@ language="C#" %>
<%
Response.Write(Response.BufferOutput.ToString());
%>
Output
True
The first property we delve upon
in the HttpResponse class is BufferOutput. This property returns a logical
value of either a True or False. In doing so, it keeps us posted on whether the output sent to the browser
will be buffered or not.
While using the Write function
from the Response class, the data doesn't have to be sent to the browser at
once, as this will result in too many small packets being sent across.
Therefore, on grounds of efficiency, the text is collected and sent only when a
critical mass is reached. By default, the buffering option is on. Unless you
are equipped with a valid reason, you should not turn it off.
a.aspx
<%@ language="C#" %>
<%
Response.ContentType = "Text/plain";
Response.Write("<b>hi");
%>
Output
<b> hi
The web server sends a series of headers
to the browser. It then follows it up with the actual content. As explained to
you earlier, the most important header is Content-Type. If we avoid creating
this header in our file, IIS defaults to the type value as text/html.
Here, we have changed the
Content-Type property in Response to text/plain. Thus, the <b> tag is
treated as text and not a HTML tag.
a.aspx
<%@ language="C#" %>
<%
Response.Write(Response.IsClientConnected.ToString());
%>
Output
True
This function simply returns true
or false, depending on whether the client is connected or not. This is one
check that is to be performed before we send the file to the browser.
a.aspx
<%@ language="C#" %>
<%
System.IO.FileStream f;
long s;
f = new System.IO.FileStream("c:\\inetpub\\wwwroot\\a.html", System.IO.FileMode.Open);
byte[] b = new byte[(int)f.Length];
f.Read(b, 0, (int)f.Length);
Response.Write("<b>Start a.spx</b>");
Response.BinaryWrite(b);
Response.Write("<b>End a.spx</b>");
%>
Output
In the System.IO namespace, there
is a class called FileStream, which has members that can handle file activity.
While creating an object 'f' in the constructor, we state the full path of the
file and also the mode in which the file is to be opened. We then allocate a
byte array, depending upon the size of the file. The file size is acquired
using the property called Length in the FileStream object.
The read function is employed
next, to read the file into the byte buffer. Therefore, the first parameter
specified is 'b'. The second parameter is the starting position in the file,
the position from where the reading should begin. And the last parameter is the
number of bytes to be read, from thereon. As we want to read from the beginning
of the file, and we also want to read the entire file in one go, we specify
zero as the second parameter and the length of the file is the third parameter.
Now that the file is available in
the byte array, the BinaryWriter function is used to add these bytes into a
stream and subsequently, send them to the browser. As the browser receives an
HTML file, it parses through the file and displays the textboxes. The Write
functions before and after the BinaryWrite, work as normal.
a.aspx
<%@ language="C#" %>
<%
Response.Write("<b>Start");
Response.ContentType = "Text/plain";
Response.Clear();
Response.Write("<b>End");
%>
Output
<b>End
The Response.ContentType function
initially changes the Content-Type header after writing Start in bold.
Thereafter, the Clear function in Response, clears all HTML output, since its
job is to clear the Buffer. So, the output displayed in the browser is that of
the final Write. Even though the documentation states that headers are reset,
it does not happen in the case of our copy.
a.aspx
<%@ language="C#" %>
<%
Response.Write("<b>Start");
Response.ContentType = "Text/plain";
Response.ClearHeaders();
Response.Write("<b>End");
%>
Output
StartEnd
However, the function
ClearHeaders, resets all the headers created in the scriptlet. Thus, the
default Content-type header is sent across, before the contents in the buffer
are handed over to the browser. As a result, we see StartEnd displayed in bold.
a.aspx
<%@ language="C#" %>
<%
Response.Write("<b>Start");
Response.End();
Response.Write("<b>End");
%>
Output
Start
The End function states 'enough
is enough', and it sends across the HTML file and the headers immediately.
Thereafter, it closes the connection. Thus, all future output to be sent to the
browser, is conveniently ignored.
a.aspx
<%@ language="C#" %>
<%
Response.Write("<b>Start");
Response.Redirect("a1.aspx");
Response.Write("<b>End");
%>
a1.aspx
<%@ language="C#" %>
<%
Response.Write("<b>a1.aspx");
%>
Output
a1.aspx
The redirect function merely
stops executing the current file, a.aspx, and starts executing the file to
which it has been redirected. Thus, in case of a Redirect function, any Write
function that follows the Redirect command, is completely ignored. Also, if you
observe the address bar, the URL does not change in the browser. It remaines as
a.aspx.
a.aspx
<%@ language="C#" %>
<%
Response.Write("a.aspx <br>");
Response.WriteFile("a.html");
%>
The output is similar to one of
the earlier programs. The WriteFile function writes the file contents into the
http stream and sends it to the browser.
Cookies Revisited
a.aspx
<%@ language="C#" %>
<%
HttpCookie c = new HttpCookie("vijay","mukhi");
Response.AppendCookie(c);
%>
One of the simplest things to do
in ASP+, is to send a cookie across. Object 'c' of type HttpCookie is created
by calling the constructor with the name of the cookie vijay, having a value of
mukhi.
Since the cookie option has been
set to prompt in the browser, you will see Alert box for the cookie. Please
note that the constructor does not send the cookie across. It is the function
AppendCookie that does so.
a.aspx
<%@ language="C#" %>
<%
HttpCookie c = new HttpCookie("vijay","mukhi");
Response.AppendCookie(c);
HttpCookie c1 = new HttpCookie("vijay1","mukhi1");
Response.AppendCookie(c1);
c = new HttpCookie("vijay1","mukhi2");
Response.AppendCookie(c);
%>
By default, the browser sends a
cookie for the ASP+ session. In the above program, even though three cookies
have been sent, we see only two boxes, since the second and the third cookies
share the same name. Thus, the cookie with the names of vijay1 and value mukhi2
is not sent across separately. The point to be considered here is that you are
free to send as many cookies as you like.
a.aspx
<%@ language="C#" %>
<%
HttpCookie c = new HttpCookie("vijay");
c.Values.Add("sonal","wife");
c.Values.Add("zzz","yyy");
Response.AppendCookie(c);
%>
Output
Value in cookie dialog box
sonal=wife&zzz=yyy
a1.aspx
<%@ language="C#" %>
<%
HttpCookieCollection cc;
HttpCookie c;
cc = Request.Cookies;
int i = cc.Count;
for (int k = 0; k < i; k++)
{
c = cc[k];
Response.Write("Cookie: " + c.Name + "<br>");
String[] s1 = c.Values.AllKeys;
for (int j = 0; j < s1.Length; j++)
{
Response.Write("Value" + j + ": " + s1[j] + "<br>");
}
}
%>
Output
Cookie: vijay
Value0: sonal
Value1: zzz
Cookie: ASP.NET_SessionId
Value0:
Cookies can be made as complex as
we like. We create one cookie named vijay, and then use the Add function in the
Values property of the cookie, to initialize the subnames and values for the
cookie.
The cookie is transferred as one entity,
with the key-value pairs within it. The different pairs are separated by a
& sign. a1.aspx simply displays all the cookies. For a cookie named vijay,
the last 'for' loop gets executed twice as it holds two values.
a1.aspx
<%@ language="C#" %>
<%
HttpCookieCollection cc;
HttpCookie c;
cc = Request.Cookies;
c = cc["vijay"];
Response.Write("Cookie: " + c.Name + "<br>");
Response.Write(c.Values["sonal"] + "<br>");
Response.Write(c.Values["zzz"]);
%>
Output
Cookie: vijay
wife
yyy
Rewrite a1.aspx with the code
given above. This program first fetches the cookie named vijay and stores it in
'c'. The Values property, which returns a NameValueCollection, has an indexer
that is supplied with the name of the sub key. Consequently, this indexer
returns the value of the sub key.
a.aspx
<html>
Sonal Mukhi
<a href="a1.aspx">Click here</a>
</html>
a1.aspx
<html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs E)
{
if (!IsPostBack) {
Response.Write(Request.Headers["Referer"]);
ViewState["zzz"] = Request.Headers["Referer"];
}
}
void abc(Object sender, EventArgs E) {
Response.Redirect(ViewState["zzz"].ToString());
}
</script>
<form runat="server">
<input type="submit" OnServerClick="abc" Value="Click" runat="server"/>
</form>
</body>
</html>
Output
Sonal Mukhi Click here
http://localhost/a.aspx
The file a.aspx has an anchor tag
<a href…> that takes us to page a1.aspx, whenever we click on it.
The ASP+ program a1.aspx displays
a button with the label 'click'. Prior to this, the function Page_Load is
called. This function uses the Request object to access the indexer called
Header. The Header is passed a string called Referer, which informs it about
the file of its origin. As a.aspx was responsible for leading us to a1.aspx
from a.aspx, the URL displays the file name a.aspx.
The WebServer normally keeps a log
of the files that lead a user to its site. This value is then stored in a state
variable called zzz. As we are now aware of the site that brought us to the
current one, by clicking on the button, we can Redirect ourselves to the page
we came from.
Thus, the above code is generic
and goes into a circular loop.
We shall now consider a practical
example to demonstrate the utility of cookies.
a.aspx
<html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs E)
{
if (Request.Cookies["vijay"] == null)
{
HttpCookie c = new HttpCookie("vijay");
c.Values.Add("Size","8pt");
c.Values.Add("Name","Verdana");
Response.AppendCookie(c);
}
}
public String abc(String k)
{
HttpCookie c = Request.Cookies["vijay"];
if (c != null)
{
if ( k == "FontSize")
return c.Values["Size"];
else
return c.Values["Name"];
}
return "";
}
</script>
<style>
body
{
font: <%=abc("FontSize")%> <%=abc("FontName")%>
}
</style>
Sonal Mukhi
<a href="a1.aspx">Click here</a>
</body>
</html>
a1.aspx
<html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs E)
{
if (!IsPostBack)
ViewState["zzz"] = Request.Headers["Referer"];
}
void abc(Object sender, EventArgs E)
{
HttpCookie c = new HttpCookie("vijay");
c.Values.Add("Size",s1.Value);
c.Values.Add("Name",s2.Value);
Response.AppendCookie(c);
Response.Redirect(ViewState["zzz"].ToString());
}
</script>
<form runat="server">
<select id="s1" runat="server">
<option>8pt</option>
<option>10pt</option>
<option>12pt</option>
<option>44pt</option>
</select>
<select id="s2" runat="server">
<option>verdana</option>
<option>tahoma</option>
<option>arial</option>
<option>times</option>
</select>
<input type="submit" OnServerClick="abc" Value="Click" runat="server"/>
</form>
</body>
</html>
Output
Sonal Mukhi Click here
Sonal Mukhi
Click here
The above example displays the
text 'Sonal Mukhi' and a hyperlink labelled as 'Click Here'. If we click on the
hyper link, we are transported to a new page called a1.aspx. This page contains two dropdown listboxes
and a button. The first listbox displays the font point size and the second
offers the font face name. By default, the values in these text boxes are 8pt
for the font size and Verdana for the font face name, respectively. At this
stage, if you modify the font size to 12 and the font face name to Tahoma, and
then click on the button, you will be taken back to the original file named
a.aspx. In this file, the text and the Hyperlink will now be displayed in the
newly selected font and size.
Having unravelled the output, let
us now shift the spot-light to the internal working of this program.
The Page_Load function in a.aspx
verifies the existence of a cookie named 'vijay'. Since this page has just been
loaded afresh, the cookie is not available at the moment. This condition
results in true only when the client returns the cookie sent by the server,
while transmitting the user data.
Since the Cookies collection
returns a null, a cookie named 'vijay' is created with two sub keys of Size and
Name. These sub keys are given the values of 8 pt and Verdana, respectively,
which correspond to the font size and the font face name. This cookie is sent
over along with the page generated by a.aspx.
The code enclosed within the
style and /style tags calls the function abc, to adjust the font size and name
as specified by the cookie. Since 'c' has not been currently initialized, the
current text is not displayed in the font and the size specified in the cookie.
Depending upon the value of the parameter 'k', the value of the key Size or
Name, as specified in the Cookie 'Vijay', will be put into service. Thus, the
keys in the cookie named 'Vijay' will decide the format of the text displayed
on the page.
If we click on the hyper-link, it
will result in a call to a1.aspx. By using the Referer parameter in the
Header's Indexer, the name a.aspx of the aspx file is stored in the state
variable named zzz. When the user clicks on the button, the font name and font
size selected by the user, get stored in the listbox ids of s1 and s2
respectively.
This results in a call to the
code in the function abc, where a cookie called 'Vijay' is created. Its sub
keys are initialized to values obtained from the listbox. This cookie is sent
over. Thereafter, using the state variable zzz, we revert back to a.aspx.
In the file a.aspx, since the
cookie 'vijay' now contains specific values, we use these values to set the
font size and the name. As a consequence, the text is displayed in accordance
with the values selected by the user in the listbox, which are now accessible
from the cookie. Thus, we can conclude that the system takes into account the
choices that we make, and each time we load the file a.aspx, the text displayed
on the page is formatted as per the pre-selected font face and size.
This is the mechanism to display
a customized web page. Since by default the page does not retain these values
when the browser is closed, the 'expires' attribute has to be initialized to a
value specified in terms of time or a date. This indicates the duration for
which the cookie is to remain alive. Alternatively, we can store such a value
in DateTime.MaxValue, so that the cookie never expires.
You can verify the retention of
pre-set values between different browser sessions by closing the browser and
re-starting it. The browser would have
retained the settings specified by you earlier.
Cookies are employed to enable
data to persist between the client and the server. A cookie is usually stored
on the Client’s hard disk. In the case of Netscape Navigator, a file called
cookies.txt is created by the browser to store the cookies. The minimum size
per cookie is 4096 bytes.
State Management with Global.asax
a.aspx
<html>
<script language="C#" runat="server">
int p1=0;
void Page_Load(Object sender, EventArgs e)
{
p1++;
Response.Write("Page_Load() " + p1.ToString() + "<br>");
}
void abc(Object sender, EventArgs e)
{
Session.Abandon();
//Response.Redirect("a.aspx");
}
</script>
<body>
<form runat="server">
<input type="submit" Value="Refresh" runat="server"/>
<input type="submit" OnServerClick="abc" Value="End Session" runat="server"/>
</form>
</body>
</html>
global.asax
<script language="C#" runat="server">
int r1 =0, r2=0, s1=0,s2=0,a1=0,a2=0;
void Application_Start(Object sender, EventArgs E)
{
a1++;
}
void Application_End(Object sender, EventArgs E)
{
a2++;
}
void Session_Start(Object sender, EventArgs E)
{
s1++;
Response.Write("Session_Start " + s1.ToString()+ "<br>");
}
void Session_End(Object sender, EventArgs E)
{
s2++;
Response.Write("Session_End " + s2.ToString()+ "<br>");
}
void Application_BeginRequest (Object sender, EventArgs E)
{
r1++;
Response.Write("Request_Start " + r1.ToString()+ " Application start " + a1.ToString() + "<br>");
}
void Application_EndRequest (Object sender, EventArgs E)
{
r2++;
Response.Write("Request_End "+ r2.ToString()+ " Application end " + a2.ToString() + "<br>");
}
</script>
Output
Request_Start 1 Application start 0
Session_Start 1
Page_Load() 1
Request_End 1 Application end 0
In the above program, we have
created two files in the directory c:\inetpub\wwwroot. The file a.aspx can be
christened by any name of our choice, but the second file must be named as
global.asax. Prior to loading a.aspx in the browser, ASP.Net checks for the
presence of a file named global.asax. If it exists in the current directory, a
DLL is created with a class called global.asax. This class holds all the code
entered in the asax file. The class resembles the following:
public class global_asax : System.Web.HttpApplication , System.Web.SessionState.IRequiresSessionState {
We discovered this by consciously
committing an error in C#.
There are six functions with pre-defined
names that are added to global.asax. They are:
• Application_Start
• Application_End
• Session_Start
• Session_End
• Application_BeginRequest
• Application_EndRequest
We have also created six
different variables in this special file and initialized all of them to 0. Each
of these six functions increments one variable each by a value of 1. This
signifies the frequency with which the functions get called. A similar action
is repeated in the Page_Load function within the aspx file.
When we load the file a.aspx, it
calls some functions from the file global.asax. But, each of them gets called
only once. The order is as follows: Application_BeginRequest, Session_Start,
Page_Load.
Thereafter, the buttons in the
aspx file are displayed. Lastly, Application_EndRequest is displayed on the
screen.
Please note that there is no
Write function in Application_Start and Application_End, because the Response
object is neither available, nor has it been created so far. By adding a
function, an error is generated, that gives us information about the
global_asax class in the dll.
When we click on Refresh, no code
gets called, because it simply results in a post back to the server.
Output
Request_Start 2 Application start 0
Page_Load() 1
Request_End 2 Application end 0
The order of calling the various
functions is as follows:
• First, function Application_BeginRequest with variable r1 as 2,
• Then, function Page_Load with variable p1 as 1
• Finally, function Request_End with variable r2 as 2.
The output proves that the
Page_Load function is not called on clicking on the refresh button. Also, no
new session is created. Hence, the Session_Start function is not called.
Now, if you click repeatedly on
the 'End Session' button, you will see the following output:
Output
Request_Start 10 Application start 0
Session_Start 8
Page_Load() 1
Request_End 10 Application end 0
The Session_Start function is
called once and the Request_Start is called twice.
If we comment out the
Response.Redirect function in the file a.aspx, we obtain the same number of
functions as before, but the Request functions get called only once. What we
are trying to convey here is that, generous amount of code gets called before
and after Page_Load. We can take advantage of these functions and the frequency
of their occurrence to execute certain code at specified intervals.
For the moment, the code in
global.asax does not deal with User Interface calls. These calls handle a much
higher level of event handling, where it deals with application events. We can
create our own events in the form of Application_EventName(signature).
We would also like to clarify
that the file, global.asax, need not be restricted to the wwwroot sub-directory
alone. The only salient feature to be kept in mind is that it should be placed
in the same sub-directory as the aspx file.
The .Net framework creates a
class derived from HttpApplication and places our code in it. Any modifications
made to these files automatically calls for recompiling of the class. We cannot
use a direct URL request to fetch the file, since we are not permitted to view
the code written in it.
If you consider yourself to be a maverick,
you may attempt to do so. But beware, you will then have to deal with the error
number 404.
a.aspx
<html>
<script language="C#" runat="server">
void abc(Object sender, EventArgs e)
{
throw new Exception();
}
</script>
<body>
<form runat="server">
<input type="submit" OnServerClick="abc" Value="Exception" runat="server"/>
</form>
</body>
</html>
Output
In the above program, An
exception of type System.Exception was thrown. The above program is extremely
simple. It displays a button labelled 'Exception'. If we click on this button,
an exception is thrown.
An exception is a synonym for an
error. The class, when derived from exception, becomes an exception class.
Functions in today's world throw exceptions rather than errors. We get an ugly
yellow coloured screen containing the exception message.
Now, let us endeavour to execute
our own code, whenever an exception is thrown.
We first create a file called
web.config in c:\inetpub\wwwroot as follows:
web.config
<configuration>
<system.web>
<customErrors mode="On" defaultRedirect="a.htm" />
</system.web>
</configuration>
a.htm
hi
This file, which is used to
customize the default workings of ASP.Net, is read each time by the ASP+
framework. By giving the above tags in the file, we can direct the framework to
load a file named a.htm, whenever an error occurs. The URL, however, changes to
the following in the browser address bar:
http://localhost/a.htm?aspxerrorpath=/a.aspx
a.aspx
<%@ Import Namespace="System.Data" %>
<html>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E )
{
DataView s = (DataView)(Application["sss"]);
s1.InnerHtml = s.Table.TableName;
l.DataSource = s;
l.DataBind();
}
</script>
<body>
<span runat="server" id="s1"/></font>
<ASP:DataGrid id="l" runat="server"/>
</body>
</html>
global.asax
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.IO" %>
<script language="C#" runat="server">
void Application_Start(Object sender, EventArgs e)
{
DataSet d = new DataSet();
FileStream f = new FileStream(Server.MapPath("s.xml"),FileMode.Open,FileAccess.Read);
StreamReader r = new StreamReader(f);
d.ReadXml(r);
f.Close();
DataView v = new DataView(d.Tables[0]);
Application["sss"] = v;
}
</script>
s.xml
<root>
<schema id="DocumentElement" targetNamespace="" xmlns="http://www.w3.org/2001/XMLSchema" xmlns:xdo="urn:schemas-microsoft-com:xml-xdo" xdo:DataSetName="DocumentElement">
<element name="Products">
<complexType>
<all>
<element name="ProductID" type="int"></element>
<element name="CategoryID" minOccurs="0" type="int"></element>
</all>
</complexType>
</element>
</schema>
<DocumentElement>
<Products>
<ProductID>2002</ProductID>
<CategoryID>2</CategoryID>
</Products>
<Products>
<ProductID>2003</ProductID>
<CategoryID>2</CategoryID>
</Products>
</DocumentElement>
</root>
Output
Products
In the file a.aspx, we begin with
the assumption that a DataView object has already been created. Then, we
initialize a span member named InnerHtml to a Table name that is picked up from
the DataView.
This explains the display of
Products in the browser window. Then, the DataSource property of the DataGrid
named l, is initialized to the DataView object named 's'.
Finally, the function DataBind display the records in a Tabular format.
Now, let us cover up the
loopholes in the explanation of the above program.
In the global.asax file, we have
added adequate amount of code in the Application_Start function. A DataSet
object named 'd' is created to hold the data. Also, a FileStream object named
'f' is given a reference to an XML file named s.xml, which is opened for
reading purpose only. The file s.xml resides in the same directory. The
StreamReader object named 'r' is given the FileStream parameter in its
constructor, to enable it to read this file. d.Readxml will finally read the
contents of the xml file in its dataset. We are not going into the explanation
of s.xml, since it remains the same as
before.
After closing the file, a
DataView object 'v' is created. Application variables are similar to State
variables, in the sense that, they are valid only while the application is on.
The Application is an indexer. Hence, we provide a string named 'sss' to hold
the DataView object. The variable 'sss' is now an Application variable.
The Application variable sss,
containing the DataView object, is used in the Page_Load function. It supplies
the data contained in the DataSet. The functions Page_Load and
Application_Start are called only once, i.e. when the page is initially loaded.
Both perform one-time actions. Thus, all resource intensive actions, such as,
initializing the DataView, which is a one-time effort, are placed in this
function.
There can always be a situation
where multiple threads would like to access both, an application and its
objects, concurrently. So, the data that does not change very often, must be
stored with an Application scope. Data that needs to be initialized just once
and then it is required to be only read, is an ideal example of such data.
In the above example, only the
first request is resource intensive, and will incur a performance overhead
while creating the DataView. All the other requests will be executed at
lightning speed.
Sessions
a.aspx
<html>
<script language="C#" runat="server">
String abc(String k)
{
return Session[k].ToString();
}
</script>
<style>
body
{
font: <%=abc("Size")%> <%=abc("Name")%>
}
</style>
Sonal Mukhi
<a href="a1.aspx">Click here</a>
</body>
</html>
a1.aspx
<html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs E)
{
if (!Page.IsPostBack)
ViewState["zzz"] = Request.Headers["Referer"];
}
void abc(Object sender, EventArgs E)
{
Session["Size"] = s1.Value;
Session["Name"] = s2.Value;
Response.Redirect(ViewState["zzz"].ToString());
}
</script>
<body>
<form runat="server">
<select id="s1" runat="server">
<option>8pt</option>
<option>10pt</option>
<option>12pt</option>
<option>44pt</option>
</select>
<select id="s2" runat="server">
<option>verdana</option>
<option>tahoma</option>
<option>arial</option>
<option>times</option>
</select>
<input type="submit" OnServerClick="abc" Value="Click" runat="server"/>
</form>
</body>
</html>
global.asax
<script language="C#" runat="server">
void Session_Start(Object sender, EventArgs e)
{
Session["Size"] = "8pt";
Session["Name"] = "verdana";
}
</script>
The above example conducts itself
in the same manner as the earlier Cookies' example. However, this one is much
simpler. It is next to impossible for the user to detect the method used to
obtain the above output by merely looking at the end result.
Let us start with the file named
a.aspx.
The code in a.aspx remains the
same, except for modifications made in the function abc. The Session indexer is
used to retrieve the value of a variable that is created in the global.asax
file. In the global.asax file, we have created two variables named Size and
Name to store the font size and the font name respectively. You may have
noticed that this code is placed in the function named Session_Start.
When we click on the button in
the file a1.aspx, the Sessions variables get re-initialized with the values
selected in the listboxes. Thereafter, there is a redirection to the original
page named a.aspx.
If we replace Cookies with
Session, the entire picture would be more transparent and lucid.
We can configure the state of the
session object by using the sessionstate section in the file named web.config.
Adding the line <sessionState timeout="40" /> will increase the
default timeout from 20 minutes to 40 minutes. The timeout parameter ends the
session either in 40 minutes or when you close the browser, whichever occurs
first.
We now add the following line to
the file named web.config
web.config
<configuration>
<system.web>
<sessionState cookieless="true" />
</system.web>
</configuration>
We have selected the cookie
option named 'prompt', and in config.web, we have disallowed the use of a cookie
to keep track of sessions. Thus, the web server has no choice but to use the
URL to keep track of sessions. As a result, the new URL looks like the
following:
http://localhost/(ybp3byicimzjix3s0veyjb55)/a.aspx
This method is termed as URL
rewriting.
ViewState
The illustration given below may
be a large program, but it is deceptively simple to understand.
a.aspx
<html>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E )
{
if (!IsPostBack)
ViewState["zzz"] = 0;
}
void a1(Object Src, EventArgs E )
{
String pid = "P" + ViewState["zzz"].ToString();
ViewState["zzz"] = (int)ViewState["zzz"] + 1;
String Id = "P" + ViewState["zzz"].ToString();
Panel p = (Panel)FindControl(Id);
p.Visible=true;
p = (Panel)FindControl(pid);
p.Visible=false;
}
void a2(Object Src, EventArgs E )
{
String Id = "P" + ViewState["zzz"].ToString();
ViewState["zzz"] = (int)ViewState["zzz"] - 1;
String pid = "P" + ViewState["zzz"].ToString();
Panel p = (Panel)FindControl(Id);
p.Visible=false;
p = (Panel)FindControl(pid);
p.Visible=true;
}
void a3(Object Src, EventArgs E )
{
String s = "P" + ViewState["zzz"].ToString();
Panel p = (Panel)FindControl(s);
p.Visible=false;
l.Text += "Name: " + na.Value + "<br>";
l.Text += "Address: " + a.Value + "<br>";
l.Text += "Card : " + n.Value + "<br>";
}
</script>
<body">
<form runat="server">
<ASP:Panel id="P0" Visible="true" runat="server">
Name:
<input id="na" type="text" runat="server">
<br>
<input type="submit" Value=" Next >> " OnServerClick="a1" runat="server">
</ASP:Panel>
<ASP:Panel id="P1" Visible="false" runat="server">
Address:
<input id="a" type="text" runat="server">
<input type="submit" Value=" << Back " OnServerClick="a2" runat="server">
<input type="submit" Value=" Next >> " OnServerClick="a1" runat="server">
</ASP:Panel>
<ASP:Panel id="P2" Visible="false" runat="server">
Card Number:
<input id="n" type="text" runat="server"/>
<input type="submit" Value=" << Back " OnServerClick="a2" runat="server">
<input type="submit" Value=" Finish " OnServerClick="a3" runat="server">
</ASP:Panel>
</form>
<asp:Label id="l" runat="server"/>
</body> </html>
Output
We start our explanation with the
code following the script tag, where we have three predefined Panel controls
having the ids P0, P1 and P2. A Panel tag is simply a container for other HTML
controls.
The Visible property for the
first panel is set to true, whereas, for the other two, it is set to false.
Thus, only the first panel P0 is displayed, and not the others.
The first panel contains a
TextBox called 'na' and a button of type submit and label 'Next >>',
which calls the function a1 when it is clicked.
The Page_Load function creates a
ViewState variable named zzz and assigns it a value of 0. In a1, pid is
assigned a value after concatenating "P" to the string value returned
by ViewState["zzz"]. The statement ViewState["zzz"] =
(int)ViewState["zzz"] + 1; increases the value by 1, thereby, making
it 1.
A new string is then created with
this new value of zzz. Thus, the value in the variable id becomes P1. The
function FindControl, when supplied with the name of the control, returns a
control. We cast it to a Panel, because we want to store this value in a Panel
object named 'p'. The Panel object 'p' currently represents the hidden Panel
P1.
We change the visible property of
p, i.e. Panel P1, to true. As a result, the second panel is displayed on the
screen. Subsequently, we use the same function named FindControl to find the
Panel P0 and to change its Visible property to false. Thus, the only panel that
is displayed at this stage is P1.
The second panel contains one textbox
and two buttons. The button labeled 'Next >>' calls the function a1,
whereas, the one labeled '<< Back' calls the function a2. The function a1
increments the value of zzz by 1, thereby, taking us to the next panel. On the
other hand the function a2 decreases the value of zzz by 1, thereby, taking us
to the previous panel. Accordingly, the current panel is hidden and the new
panel is made visible.
In the last panel P3, the
function a3 gets called by the button labeled 'Finish'. This function simply displays
the values of the textboxes in the label field.
The user is given the impression
that he is moving from one page to the other, but in reality, a single aspx
file is executing certain sections of its code.
The property named ViewState is
an instance of the StateBag class, which gives us a dictionary like interface.
It stores objects associated with a key.
Create the b.cs file in the
c:\inetpub\wwwroot\bin sub directory.
b.cs
using System.Web;
namespace nnn
{
public class zzz : IHttpHandler
{
public void ProcessRequest(HttpContext c) {
c.Response.Write("Hell");
}
public bool IsReusable {
get {
return true;
}
}
}
}
Compile b.cs as
>csc /target:library /out:c.dll b.cs /R:system.web.dll
Create a file by the name of web.config
in c:\inetpub\wwwroot to appear as depicted below:
web.config
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="a1.aspx" type="nnn.zzz,c" />
</httpHandlers>
</system.web>
</configuration>
Now, load the following URL in
the browser http://localhost/a1.aspx
Output
Hell
We first create a simple program
named b.cs in the c:\inetpub\wwwroot\bin sub-directory. The class zzz in this
file is derived from the interface IhttpHandler. It contains only two functions
named ProcessRequest and IsReusable.
The IsReusable function is called
to find out whether the same instance of the class can be recycled and used for
another request, or if a fresh instance of the object needs to be created.
Since the function returns true, the same object is used.
The ProcessRequest function
creates the HTML output that is to be sent across to the browser. It accepts an
object 'c' of type HttpContext as a parameter. This object has a function
called Write, which helps the browser in writing output to the screen. Hence,
we see 'Hell' displayed in the browser window.
What if we want to call our own
function, instead of the ones supplied by IIS, when we are working on certain
files?
To synchronously process HTTP web
requests, the first requirement is that, the class must derive from
IHttpHandler. This interface must be implemented, because it defines a contract
and allows custom URL execution.
ASP.Net provides us with a
low-level request and response API (a set of rules), which uses classes in
order to service incoming requests. Thus, the above program is called a
handler. Handlers are used when we want to customize certain features if we are
not satisfied with the methods adopted by the framework that handles web
requests.
The interface IHttpHandlerFactory
decides on the class that will handle the http request. ASP.Net is shipped with
three factory classes that handle requests. These are:
• PageHandlerFactory
• RestrictedResourceFactory
• WebServiceHandlerFactory
We can either use any one of
these three classes or create our own set of classes. If we create our own
classes, we have to register them with the framework and handle the resolution
issues.
To change the default behaviour
in the .Net world, code has to be placed in the config.web file. This is a
plain and simple text file, which follows the rules of XML. This file has to be
placed in the inetpub\wwwroot sub-directory or the directory of your
application.
We can place our options within a
tag called <configuration>. Following this, we have to initially place a
tag called system.web which will
contain another tag called <httpHandlers>. This tag accepts the name of a
file or a file pattern. This pattern calls our handler when the value assigned
to the property called path, is invoked. HttpHandler also requires the type
attribute containing the namespace name and the class to handle the request.
The name of the dll is also to be specified with the type. Thus, we use a comma
as a separator, and then give 'c'. The c.dll must be present in the bin sub-directory.
These settings are inherited by sub-directories also, implying that the
sub-directories are also checked for controls, handlers etc.
Each time we request for a file
named a1.aspx, the request for this particular file is not processed by the web
server. Instead, it is handed over to our class named zzz. The class zzz then
decides on the data to be sent over, under the file name of a1.aspx
Thus, handlers and factories are
the backbone of the framework, because we are at liberty to decide on how the
HTTP request should be handled. The factory picks up the request and decides on
the handler that is to be called. These handlers are available in the
web.config file. The code for the handlers normally resides in the bin
sub-directory. Since the file is placed in the default root directory, it is
picked up by default, hence we request you to delete this file after having
tried the example.
Tracing
a.aspx
<%@ Page Trace="true" language="C#"%>
hi
<%
Response.Write("bye");
%>
Output
hi bye
Request Details |
|||||||||
Session Id: |
dufldc55vabqbd45xof13m55 |
Request Type: |
GET |
|
|
|
|
|
|
Time of Request: |
9/26/2001 6:15:55 PM |
Status Code: |
200 |
|
|
|
|
|
|
Request
Encoding: |
Unicode (UTF-8) |
Response
Encoding: |
Unicode (UTF-8) |
|
|
|
|
|
|
Trace Information |
|||||||||
Category |
Message |
From First(s) |
From Last(s) |
|
|
|
|
|
|
aspx.page |
Begin Init |
|
|
|
|
|
|
|
|
aspx.page |
End Init |
0.000184 |
0.000184 |
|
|
|
|
|
|
aspx.page |
Begin PreRender |
0.000928 |
0.000744 |
|
|
|
|
|
|
aspx.page |
End PreRender |
0.001095 |
0.000168 |
|
|
|
|
|
|
aspx.page |
Begin SaveViewState |
0.001462 |
0.000367 |
|
|
|
|
|
|
aspx.page |
End SaveViewState |
0.001617 |
0.000154 |
|
|
|
|
|
|
aspx.page |
Begin Render |
0.001757 |
0.000141 |
|
|
|
|
|
|
aspx.page |
End Render |
0.002250 |
0.000493 |
|
|
|
|
|
|
Control Tree |
|||
Control Id |
Type |
Render Size
Bytes (including children) |
Viewstate Size
Bytes (excluding children) |
__PAGE |
_ASP.a_aspx |
9 |
0 |
Cookies Collection |
|||||||||
Name |
Value |
Size |
|
|
|
|
|
|
|
ASP.NET_SessionId |
dufldc55vabqbd45xof13m55 |
42 |
|
|
|
|
|
|
|
Headers Collection |
|||||||||
Name |
Value |
|
|
|
|
|
|
|
|
Connection |
Keep-Alive |
|
|
|
|
|
|
|
|
Accept |
image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */* |
|
|
|
|
|
|
|
|
Accept-Encoding |
gzip, deflate |
|
|
|
|
|
|
|
|
Accept-Language |
en-us |
|
|
|
|
|
|
|
|
Host |
127.0.0.1 |
|
|
|
|
|
|
|
|
User-Agent |
Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914) |
|
|
|
|
|
|
|
|
Server Variables |
|||||||||
Name |
Value |
|
|
|
|
|
|
|
|
ALL_HTTP |
HTTP_CONNECTION:Keep-Alive HTTP_ACCEPT:image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */* HTTP_ACCEPT_ENCODING:gzip, deflate HTTP_ACCEPT_LANGUAGE:en-us HTTP_HOST:127.0.0.1 HTTP_USER_AGENT:Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914) |
|
|
|
|
|
|
|
|
ALL_RAW |
Connection: Keep-Alive Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */* Accept-Encoding: gzip, deflate Accept-Language: en-us Host: 127.0.0.1 User-Agent: Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914) |
|
|
|
|
|
|
|
|
APPL_MD_PATH |
/LM/W3SVC/1/ROOT |
|
|
|
|
|
|
|
|
APPL_PHYSICAL_PATH |
c:\inetpub\wwwroot\ |
|
|
|
|
|
|
|
|
AUTH_TYPE |
|
|
|
|
|
|
|
|
|
AUTH_USER |
|
|
|
|
|
|
|
|
|
AUTH_PASSWORD |
|
|
|
|
|
|
|
|
|
LOGON_USER |
|
|
|
|
|
|
|
|
|
REMOTE_USER |
|
|
|
|
|
|
|
|
|
CERT_COOKIE |
|
|
|
|
|
|
|
|
|
CERT_FLAGS |
|
|
|
|
|
|
|
|
|
CERT_ISSUER |
|
|
|
|
|
|
|
|
|
CERT_KEYSIZE |
|
|
|
|
|
|
|
|
|
CERT_SECRETKEYSIZE |
|
|
|
|
|
|
|
|
|
CERT_SERIALNUMBER |
|
|
|
|
|
|
|
|
|
CERT_SERVER_ISSUER |
|
|
|
|
|
|
|
|
|
CERT_SERVER_SUBJECT |
|
|
|
|
|
|
|
|
|
CERT_SUBJECT |
|
|
|
|
|
|
|
|
|
CONTENT_LENGTH |
0 |
|
|
|
|
|
|
|
|
CONTENT_TYPE |
|
|
|
|
|
|
|
|
|
GATEWAY_INTERFACE |
CGI/1.1 |
|
|
|
|
|
|
|
|
HTTPS |
off |
|
|
|
|
|
|
|
|
HTTPS_KEYSIZE |
|
|
|
|
|
|
|
|
|
HTTPS_SECRETKEYSIZE |
|
|
|
|
|
|
|
|
|
HTTPS_SERVER_ISSUER |
|
|
|
|
|
|
|
|
|
HTTPS_SERVER_SUBJECT |
|
|
|
|
|
|
|
|
|
INSTANCE_ID |
1 |
|
|
|
|
|
|
|
|
INSTANCE_META_PATH |
/LM/W3SVC/1 |
|
|
|
|
|
|
|
|
LOCAL_ADDR |
127.0.0.1 |
|
|
|
|
|
|
|
|
PATH_INFO |
/a.aspx |
|
|
|
|
|
|
|
|
PATH_TRANSLATED |
c:\inetpub\wwwroot\a.aspx |
|
|
|
|
|
|
|
|
QUERY_STRING |
|
|
|
|
|
|
|
|
|
REMOTE_ADDR |
127.0.0.1 |
|
|
|
|
|
|
|
|
REMOTE_HOST |
127.0.0.1 |
|
|
|
|
|
|
|
|
REQUEST_METHOD |
GET |
|
|
|
|
|
|
|
|
SCRIPT_NAME |
/a.aspx |
|
|
|
|
|
|
|
|
SERVER_NAME |
127.0.0.1 |
|
|
|
|
|
|
|
|
SERVER_PORT |
80 |
|
|
|
|
|
|
|
|
SERVER_PORT_SECURE |
0 |
|
|
|
|
|
|
|
|
SERVER_PROTOCOL |
HTTP/1.1 |
|
|
|
|
|
|
|
|
SERVER_SOFTWARE |
Microsoft-IIS/5.0 |
|
|
|
|
|
|
|
|
URL |
/a.aspx |
|
|
|
|
|
|
|
|
HTTP_CONNECTION |
Keep-Alive |
|
|
|
|
|
|
|
|
HTTP_ACCEPT |
image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */* |
|
|
|
|
|
|
|
|
HTTP_ACCEPT_ENCODING |
gzip, deflate |
|
|
|
|
|
|
|
|
HTTP_ACCEPT_LANGUAGE |
en-us |
|
|
|
|
|
|
|
|
HTTP_HOST |
127.0.0.1 |
|
|
|
|
|
|
|
|
HTTP_USER_AGENT |
Mozilla/4.0 (compatible; MSIE 6.0b; Windows NT 5.0; .NET CLR 1.0.2914) |
|
|
|
|
|
|
|
|
In a.aspx, we simply added an
attribute called Trace and assigned it a value of true. This resulted in a page
full of very useful information. First 'hi' and 'bye' were displayed, and then,
we saw a table where the information was categorized into different sections.
The first section is the Request
Information where the session id is displayed in the form of a very long
number. It changes with every new browser session. Then, the datetime of the
file request is displayed. The request type can either be GET or POST. Since we
have not explicitly specified the type, it has the default value of GET.
The last is the success/error
code. If all goes well, like it has in this case, the code sent to the browser should
be 200. In the case of an error, the error code or error number is reported.
For example, if a file is not found,
error number 404 is returned.
We are fully aware that a large
number of functions get called from our page. But it is challenging to figure
out as to which functions have been called, and the order in which they have
been executed. The trace gives us a ready rejoinder for this. At first, the
Init functions are called. They are followed by the functions PreRender,
SaveState and Render, in that order. A trace output augments our understanding
of ASP+.
In the current file, we have no
controls on our page, but if we did have them, they would all have been
displayed.
The next category encompasses the
Session State. All the session variables that we create, are listed here, along
with their values. A cookie that expires when the browser session is closed, is
called a session variable.
Following the Session State is
the Cookies Collection. Even though our program has not sent any cookies, ASP+
sends over a cookie to maintain state. All cookies are listed with their names
and values.
Following these categories are
the details about the Headers, followed by details about the Server variables.
The trace output assists in
debugging code. When you run the samples provided by Microsoft, you will
witness a large number of functions that are called by the framework, to handle
a database request. We could have added one more property called TraceMode,
which specifies the trace statements. The default behaviour of SortByTime can
also be changed to TraceMode="SortByCategory".
There is a Page-level tracing
that writes debugging statements directly to the output of a page. If tracing
is on, it also executes code.
a.aspx
<%@ Page Trace="true" TraceMode="SortByCategory" %>
<html>
<script language="C#" runat="server">
public void Page_Load(Object sender, EventArgs E)
{
Trace.Warn("aaa","bbb");
if (Trace.IsEnabled)
Trace.Write("ccc","vijay");
}
public void abc(Object sender, EventArgs E)
{
Trace.Write("ddd","mukhi");
Trace.Warn("eee","sonal");
}
</script>
<body>
<form runat="server">
<input type="Submit" OnServerClick="abc" Value="hi" runat="server"/>
</form>
</body>
</html>
We have a property called Trace of
type TraceContext, which executes the functions Warn and Write. Both these
functions display the parameters that are passed to them in red and normal
colour, respectively. This output is placed under the Trace Information. Here,
two parameters are required. The first parameter will get displayed in the
column Category and the second, under the heading Message.
The function Page_Load is called
once. It looks for the value contained in the Trace property. This value is of
type Boolean. Hence, it will return true when trace is on, and false, when
trace is off. We can execute trace code only if tracing is enabled.
When we click on the button, the
trace output shows plenty of code that has been called, and it also displays
the order in which the functions are being executed. Notice that the Trace
statements in the function Page_Load confirm the fact that Page_Load gets
called each time that we go back to the server.
public void Page_Load(Object sender, EventArgs E)
{
if (!IsPostBack)
{
Trace.Warn("aaa","bbb");
if (Trace.IsEnabled)
Trace.Write("ccc","vijay");
}
}
We have now added an 'if'
statement to the Page_Load function to verify whether IsPostBack has a value of
false for the first time or not.
Now, the trace statements will be
displayed only once, because when we click on the button, these statements are
not displayed again. This substantiates the point that we are trying to make.
Thus, we advise you to keep the trace on, if you want to figure out the order
of execution of functions.
Application level tracing is
turned-on by adding the following line in the web.config file:
web.config
<configuration>
<system.web>
<trace enabled="false" requestLimit="0" pageOutput="false" traceMode="SortByTime" />
</system.web>
</configuration>
In order to see the tracing
output, load a file called trace.asx using http://localhost/trace.asx . This
will first show us the individual requests. Then, we can drill down to the
actual tracing data.
Caching
Hard disks are always slower than
memory, since they are mechanical devices. The Internet is much slower compared
to your hard disk. So, it obviously takes much longer for a file to be
displayed in a browser, if it has to travel across the Internet. Retrieving the
same file from our hard disk is a relatively faster process. Caching is a
technique used in computers, which helps in increasing the speed of access.
This is done by storing that portion of the data in memory, which is required
to be accessed very often. Thereby, it can be accessed much more quickly.
In the web world, caching has a
different meaning. The performance of our web server would drastically improve,
if we held onto the web page between web requests, so that we do not have to
recreate them.
ASP.Net offers us two kinds of
caching, namely, Output Caching and Data Caching.
• In Output Caching, the html file created by the aspx application is cached or stored, instead of sending it across immediately.
• In Data Cache, an object is stored programmatically. ASP.Net provides a full-featured cache engine. This allows us to hold on to or store an object in such a way that we can re-use it across http requests.
We use Output Caching whenever
the entire page can be cached. A web site that has a large number of visitors
can experience substantial performance gains, if it can cache a frequently
accessed page even for a single minute. Thus, page caching implies that in the
case of an http request, the page is served from the cache. It is not recreated
by executing the code that it contains.
It is not always practical to
cache an entire page at a time. Hence, an alternative is to cache only a
portion or some objects on that page. Thus, those objects of a page that take a
long time to be created, should be cached for a longer period.
How does one decide as to when,
and for how long an object should be cached? This is not an easy decision to
make. The data might get refreshed at regular intervals, or it may be valid for
a specified time period only. We are allowed to associate an expiration period
with an item in the cache. On doing so, ASP.Net will remove the item from the
cache only when the specified expiration period has elapsed. Thus, a program
will first look for data in the cache. Only if it cannot find the item there,
will it re-create it.
In ASP+, the concept of caching
is quite advanced. A cache item can depend upon an external file or another
cache item. Thus, if one cache item expires, all the other cache items that are
dependent on it, also expire. Some items may expire when their data source is
modified.
a.aspx
<%@ OutputCache Duration="60" VaryByParam="none" %>
<html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs e)
{
l.Text = DateTime.Now.ToString("r");
}
</script>
<body>
<asp:label id="l" runat="server"/>
</body>
</html>
The above example displays the current data and time, since the
value is displayed off the DateTime object. Open the same file in a Browser,
and the time remains the same.
This happens because of the
directive OutputCache, where the duration property is set to 60 seconds. The
first time One access the aspx program, it is kept in the cache for Sixty
seconds. Thus, when we open the file in a
new browser, the value present in the cache is picked up. Then, after sixty
seconds have elapsed, the file gets re-executed. Thus, output caching is a very
powerful technique meant for increasing the performance of the server. Items
are placed in memory and not reloaded from the disk, over and over again.
Accessing the hard disk slows down the program.
The output is cached only when the above directive is employed.
In the first round, a valid GET or HEAD response is sent to the server. So, a
cache entry is created internally. Then, for future requests, the cache is
read, until it expires.
The cache also checks the entire
URL, including the parameters passed to it. If any of the parameters passed, or
the QueryString is different, the cache is ignored and the value is fetched
from the server. This is known as a Cache Miss.
a.aspx
<%@ OutputCache Duration="15" VaryByParam="none"%>
<html>
<script language="C#" runat="server">
void Page_Load(Object sender, EventArgs e)
{
l.Text = DateTime.Now.ToString("r") + "<br>";
}
</script>
<body>
<form action=a.aspx>
<asp:label id="l" runat="server"/>
<input type=text name=aa>
<input type=submit value="hi">
</body>
</html>
Output
To explain the above principle, we have a textbox and a button
that calls the same form again. You can enter any text in the textbox and click
on the button to submit the value. Since the page gets reloaded, we see a
different date time value. Open a new copy of the browser before 15 minutes are
elapsed and you’ll notice that the time does not get updated as the value stored
in the cache is reloaded.
However, if another copy of the
browser is loaded after 15 seconds have elaplsed, we see the current time on
the screen.
The output cache maintains the
sanctity of the duration specified for which you want the data to be cached.
Under no circumstance will the data be removed from the cache earlier than the
specified duration. This is because, the framework cannot, on its own,
comprehend the reason why you need to place something in the cache for the
specified duration. When a page is valid in the cache for a specified duration
of time, it is known as an absolute expiration policy. The server can also send
http headers to the client, which include the duration for which a page is to
remain valid on the browser's side.
VaryByParam can take only three
values, namely none, * or parameters.
DateTime.Now.ToString contains the alphabet r as a parameter. This refers to a certain format in which the date and
time are to be displayed. Change the value to G and the output will vary
accordingly.
Data Caching
a.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E)
{
DataView s;
s = (DataView)Cache["yyy"];
if (s == null)
{
SqlConnection c = new SqlConnection("server=localhost;uid=sa;pwd=;database=NorthWind");
SqlDataAdapter co = new SqlDataAdapter("select * from Products", c);
DataSet ds = new DataSet();
co.Fill(ds, "zzz");
s = new DataView(ds.Tables["zzz"]);
Cache["yyy"] = s;
i.Text = "Dataset created ";
}
else
{
i.Text = "Dataset retrieved from cache";
}
l.DataSource=s;
l.DataBind();
}
</script>
<body>
<form method="GET" runat="server">
<asp:label id="i" runat="server"/></i>
<ASP:DataGrid id="l" runat="server"/>
</form>
</body>
</html>
Output
Dataset created
ASP.Net offers us a caching
engine that has all the bells and whistles we will ever need. It lets us store
and retrieve any type of arbitrary objects in the cache, across http requests. These
objects are stored in the memory and not on the disk. They are private to each
application. Fortunately, we are provided with a simple dictionary interface,
like the ViewState and Session objects, that let us store some object and
equate it with a value.
In the above example, we are
using the cache indexer and checking for a variable called yyy. Since any
object can be stored in a cache, we cast it to a DataView object. s returns a
value of null, since we have not placed anything in the cache yet. We populate
the DataView with data from the database. We store the view object that
represents the SQL data, into the Cache property, using the name yyy.
Henceforth, any access made to
this page, will result in a non-null value for 's'. Thus, data is read from the
cache without executing the SQL statements. This also works across sessions or
browser instances. Imagine the boost in the performance that results, from not
having to go back to the SQL server.
a.aspx
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Data" %>
<html>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E )
{
if(!IsPostBack)
{
xyz();
}
}
void abc(Object sender, EventArgs E) {
DataSet ds = new DataSet();
FileStream fs = new FileStream(Server.MapPath("c.xml"),FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
StreamReader r = new StreamReader(fs);
ds.ReadXml(r);
fs.Close();
DataRow n = ds.Tables[0].NewRow();
n["auid"] = i.Text;
n["name"] = name.Text;
ds.Tables[0].Rows.Add(n);
fs = new FileStream(Server.MapPath("c.xml"), FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
TextWriter w = new StreamWriter(fs);
w = TextWriter.Synchronized(w);
ds.WriteXml(w);
w.Close();
xyz();
}
void pqr(Object sender, EventArgs e)
{
xyz();
}
void xyz()
{
DataView s = (DataView)Cache["yyy"];
if(s == null)
{
DataSet ds = new DataSet();
FileStream fs = new FileStream(Server.MapPath("c.xml"), FileMode.Open,FileAccess.Read);
StreamReader r = new StreamReader(fs);
ds.ReadXml(r);
fs.Close();
s = new DataView(ds.Tables[0]);
Cache.Insert("yyy", s, new CacheDependency(Server.MapPath("c.xml")));
c.Text = "Dataset created explicitly";
}
else
{
c.Text = "Dataset retrieved from cache";
}
l.DataSource = s;
l.DataBind();
}
</script>
<body>
<form runat="server">
<ASP:DataGrid id="l" runat="server"/>
Id:
<ASP:TextBox id=i Text="1" runat=server/>
Last Name:
<ASP:TextBox id=name Text="Mukhi" runat=server/>
<asp:button Text="Add " OnClick="abc" runat=server/>
<asp:button Text="Refresh " OnClick="pqr" runat=server/>
<asp:label id="c" runat="server"/></i></p>
</form>
</body>
</html>
c.xml
<DocumentElement>
<xsd:schema id="DocumentElement" targetNamespace="" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="Table">
<xsd:complexType>
<xsd:all>
<xsd:element name="auid" type="xsd:string"/>
<xsd:element name="name" type="xsd:string"/>
</xsd:all>
</xsd:complexType>
</xsd:element>
<xsd:element name="DocumentElement" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element ref="Table"/>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<Table>
<auid>1</auid>
<name>vijay</name>
</Table>
</DocumentElement>
Output when clicked on Refresh
This program shows us how we can
update the data in the cache, if a file has been updated.
Loading a.aspx in the browser,
results in a call to the function xyz. This function can be called from multiple
places. It simply populates the DataGrid control, which has the id of l, with data from the file c.xml. The xml file must
reside in the same directory as a.aspx.
Initially, verification is done
to confirm the existence of yyy in the Cache object. The first time around, it
will obviously not be present, and hence, the 'if' statement gets called.
Within the 'if' block, a new
DataSet object is created. Using the static function MapPath from the Server
class, the full physical path name of c.xml is retrieved. The xml file is then
read, and a new DataView object named 's' is created.
We not only use the Insert
function to associate the words yyy with the DataView object 's', but also to
create a CacheDependency object, whose constructor is passed a full path or a
file named c.xml. Thus, whenever this file is modified, the Cache object will
remove yyy from the cache, and its return value will be null. All this happens
internally, without any interference from our side.
Each time we click on the refresh button, the function pqr is
called. This function in turn calls the function xyz. The function xyz notices
that the cache object has a valid DataView object, and hence, it does not read
the xml file. The label displays whether we have picked up anything from the
cache or not.
When we click on the add button,
a record gets added to the xml file through the function abc.
To add a record, a new DataSet is
created, followed by a FileStream. Then the XML data is read. The file is
closed thereafter. In addition to this, a DataRow object having two fields
named auid and name, is created and filled up, using the indexer n. The
TextWriter class writes this newly created row to the file. The function xyz,
when called, observes that the xml file has changed. Hence, it reloads the
file. Thus, the new data gets displayed.
Any change to the XML file
invalidates the yyy object, and hence, it is removed from the cache.
ASP.Net actually supports three
types of caching:
• The Scavenging Method: Scavenging is a process that vultures are adept at. Items that are not needed must be removed. We cannot let the cache grow too big, since on doing so, we would use up memory, which is a valuable resource. Thus, items that are not accessed very often, are removed from the cache. We can associate a cost with each item, thus, providing the framework with a criterion to decide as to which items are to be removed.
• The Expiration Method: This method has already been explained earlier. In this method, the lifetime of an item is explicitly stated. We can either state an explicit value, such as midnight today, or a number that is relative to the time when the item was last accessed, such as 10 minutes after the item was last accessed. There are innumerable choices available.
• The File and Key Dependencies Method: If a dependency changes, the item in the cache is invalidated and removed. The above example invalidates the cache item yyy, when the xml file changes. Thus, the moment the file changes, its data is retrieved again, and the Dataview is updated with the new changes. The Insert function can take many more parameters that specify the time duration for which the item is to remain in the cache. This can be specified as an absolute time or a time period relative to the instance when it was last accessed.
Web.Config
Let us go deeper into the world
of configuration. As mentioned a couple of times in the past, the XML file
config.web stores all the details of how the web server will behave.
a.aspx
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<html>
<script language="C#" runat="server">
void Page_Load(Object Src, EventArgs E )
{
String d = (String) ((NameValueCollection) Context.GetConfig("system.web/dsnstore"))["yyy"];
SqlConnection c = new SqlConnection(d);
SqlDataAdapter ma = new SqlDataAdapter();
ma.SelectCommand = new SqlCommand();
ma.SelectCommand.Connection = new SqlConnection(d);
ma.SelectCommand.CommandText = "select * from Products";
DataSet ds = new DataSet();
ma.Fill(ds, "zzz");
l.DataSource=new DataView(ds.Tables[0]);
l.DataBind();
}
</script>
<body>
<ASP:DataGrid id="l" runat="server"/>
</body>
</html>
web.config
<configuration>
<configSections>
<sectionGroup name="system.web">
<section name="dsnstore" type="System.Configuration.NameValueSectionHandler,System" />
</sectionGroup>
</configSections>
<system.web>
<compilation debug="true" />
<dsnstore>
<add key="yyy" value="server=(local)\NetSDK;database=northwind;Trusted_Connection=yes" />
</dsnstore>
</system.web>
</configuration>
In the above example, we are
simply retrieving data from a database and putting it into a DataGrid named l. The difference here is that we have used a string
's' as the connection string and have not hard coded the values, unlike what we
had done earlier.
This string 's' gets its value
from a function named GetConfig, which in turn, uses the ‘system.web/dsnstore’.
If you look at web.config, it has a section name initialized to dsnstore within
the configSections tags. Also, the name of the class that will handle this
section, System.Configuration.NameValueSectionHandler is specified.
The same tag called dsnstore is
used to add a key named yyy. The value assigned to this key is our connection
string that refers to the northwind database on SQLServer. The indexer is then
used to access the value of this key.
The web.config file stores
strings that can be retrieved by the programmer later. Those items that are
used by everyone, can be stored in a configuration file. If there is a change
in any of these items, it has to be incorporated in just one place i.e. the
configuration file, and thereafter, the change is displayed to everyone who
accesses the page thereafter.
Security
We had considered explaining the security
concepts to you with a slightly different approach. There are a large number of
samples available from Microsoft called the Quickstart tutorials. We have
borrowed a considerably large number of these samples from there, and have also
added a sizeable number of our own. By default, all the samples that run, are
written in VBscript and not in C#.
To be able to execute the first
security example, we need to write the following URL:
http://localhost/quickstart/aspplus/samples/security/WindowsAuth/CS/windowsauth.aspx
Using Windows Authentication
If you search for a directory
called quickstart, you will not be able to trace it. This is because quickstart
is a virtual directory that begins at
C:\Program Files\Microsoft.Net\FrameworkSDK\Samples\QuickStart.
You can verify this by clicking
on Start, Programs, Administrative Tools, Internet Services Manager and then,
on the Default web site. If you click on the plus sign for this option, you
will see the word 'quickstart' listed there. Thus, the file windowsauth.aspx
starts at the following location:
C:\PROGRA~1\Microsoft.NET\FrameworkSDK\Samples\quickstart
\aspplus\samples\security\windowsauth\cs
Now that we are aware of where
our code resides, we will move into this sub-directory and copy the file
windowsauth.aspx as a1.aspx. This will keep the original code intact, and at
the same time, allow us to add our own file. Enter the program given below and
provide the URL as follows:
http://localhost/quickstart/aspplus/samples/security/WindowsAuth/CS/a.aspx
a.apsx
<html>
<script language="C#" runat=server>
void Page_Load(Object Src, EventArgs E )
{
Response.Write(User.Identity.Name + " " + User.Identity.AuthenticationType);
}
</script>
<body>
</body>
</html>
Output
VMCI\Administrator Negotiate
ASP+ needs to identify who we
are. This is the first level of security in ASP.Net. We logged into Windows
2000 as a user called Administrator. Our computer was named as VMCI. Hence, the
above output was displayed.
Next, we go one sub-directory
higher and then move down into the sub-directory cookieauth\cs. Here, we copy
the file default.aspx as a.aspx, and write the URL in the browser window as
follows:
http://localhost/quickstart/aspplus/samples/security/CookieAuth/CS/a.aspx
a.aspx
<%@ Import Namespace="System.Web.Security " %>
<html>
<script language="C#" runat=server>
void Page_Load(Object Src, EventArgs E )
{
Response.Write("Hell, " + User.Identity.Name);
}
void abc(Object sender, EventArgs E)
{
FormsAuthentication.SignOut();
Response.Redirect("login.aspx");
}
</script>
<body>
<form runat=server>
<asp:button text="Signout" OnClick="abc" runat=server/>
</form>
</body>
</html>
Output
Instead of the file a.aspx being
displayed, we are redirected to a file called login.aspx. How did this happen?
The URL that gets generated in
the browser's address bar, is as follows:
http://localhost/quickstart/aspplus/samples/security/CookieAuth/CS/login.aspx?ReturnUrl=http%3a%2f%2flocalhost%2fquickstart%2faspplus
%2fsamples%2fsecurity%2fCookieAuth%2fCS%2fa.aspx
All characters such as a /
symbol, get converted to the ASCII codes displayed in hex. Here, %2f stands for
a / symbol. The parameter ReturnUrl has a value of a.aspx.
The URL changes due to the
settings present in the web.config file in the same Directory. These are
definitely worth taking a look at.
web.config
<configuration>
<system.web>
<authentication mode="Forms">
<forms name=".ASPXUSERDEMO" loginUrl="login.aspx" protection="All" timeout="60" />
</authentication>
<authorization>
<deny users="?" />
</authorization>
<globalization requestEncoding="UTF-8" responseEncoding="UTF-8" />
</system.web> </configuration>
In this file, the authentication
tag has a mode that involves the use of Forms. The Form tag specifies a file
called login.aspx. We have not been authenticated by the framework to load any
file, unless we login. Hence, the file login.aspx is displayed. Thus, any aspx
file in this sub-directory will not get loaded, unless the user logs in through
the login.aspx file. We shall now explore the contents of this file.
Login.aspx
<%@ Import Namespace="System.Web.Security " %>
<html>
<script language="C#" runat=server>
void abc(Object sender, EventArgs E)
{
if ((name.Value == "vijay") && (pwd.Value == "mukhi"))
{
FormsAuthentication.RedirectFromLoginPage (name.Value,Cookie.Checked);
}
else
{
l.Text = "Invalid password mutt Please try again";
}
}
</script>
<body>
<form runat=server>
User Name:
<input id="name" type="text" runat=server/>
<p>
Password:
<input id="pwd" type=password runat=server/>
<p>
<ASP:RequiredFieldValidator ControlToValidate="pwd" Display="Static" ErrorMessage="*" runat=server/></td>
Persistent Cookie:
<ASP:CheckBox id=Cookie runat="server" />
<p>
<asp:button text="Login" OnClick="abc" runat=server/>
<asp:Label id="l" ForeColor="red" Font-Name="Verdana" Font-Size="10" runat=server />
</form>
</body> </html>
In login.aspx, we have a textbox
called name, where the user is expected to type in his name. Then, an input box
called pwd, of the type password, is provided. This ensures that the password
keyed in the textbox, is displayed as a series of asterix. If the user does not
enter anything, the required field validator gets activated and displays an
asterix.
If you click on the button
labeled 'Login', the function abc gets called, which determines whether the
name is 'vijay' and password is 'mukhi', or otherwise. If it is, a static
function called RedirectFromLoginPage from the FormsAuthentication.class is
called, which loads the original file of a.aspx. Moreover, a cookie with the
funny name ..ASPXUSERDEMO, is also sent across. This name can be seen in the
file config.web. You can verify this by changing the cookie options to the
'prompt' mode.
The second parameter to the
RedirectFromLoginPage function decides whether the cookie should be made
persistent, i.e., saved on the user's hard disk, so that it can be sent across
each time. The default type of cookie is a session cookie.
Thereafter, we reach the original
file named a.aspx. When we click on the button in this file, the function abc
gets called again. This function in turn, calls the SignOut function from the
class FormsAuthentication which kills the cookie. Hence, we are redirected back
to the login page.
Thus, implementation of security
issues is much more straightforward and clear-cut in ASP.NET. If we have not
been authenticated, a default file gets called. On valid authentication, we are
directed back to the original file that we had requested for. Once the user is
authenticated, a set of rules can be applied to restrict accessibility to
certain features. This also helps in customization of the interface.
In the web.config file, the
authorization tag can be used with the word 'deny', initialized to those users
who are to be permanently denied access. The ? value signifies anonymous users,
and it forbids all anonymous users from logging into the site.
web.config
<configuration>
<system.web>
<authentication mode="Forms">
<forms name=".ASPXUSERDEMO" loginUrl="login.aspx" protection="All" timeout="60" />
</authentication>
<authorization>
<deny users="vijay" />
</authorization>
<globalization requestEncoding="UTF-8" responseEncoding="UTF-8" />
</system.web>
</configuration>
In the authorization tag, if the 'deny users' is initialized to vijay, then any user who logs in as 'vijay'is denied access. Thus, 'vijay' is not permitted to view a.aspx. There is no way to bypass the login page, even though we have keyed in the right password. Thus, we first need to get authenticated and thereafter, we need to have the relevant authorization to put our intentions into practice.