Chapter 19 Custom Handlers 421
FIGURE 19-3 IIS has a handler mapping for Trace.axd.
If you look through the default web.confi g fi le a bit more, you’ll see some other critical
ASP.NET handlers. As you might expect, source code is banned explicitly from normal clients
by default. Notice that fi les such as *.cs, *.confi g, and *.vb are handled by the Forbidden han-
dler. If you try to look at source code via a Web browser, ASP.NET returns the page shown in
Figure 19-4 by default. FIGURE 19-4 What happens when you try to view forbidden content
422 Part IV Diagnostics and Plumbing
Remember that ASP.NET’s confi guration is very malleable and that you may choose to let cli-
ents see your source code by one of two means. You may remove the source code extension
to ASP.NET mappings within IIS. Alternatively, you may write your own source code viewer
handlers and declare them in your application’s web.confi g fi le.
These handlers plug into the pipeline by implementing IHttpHandler. Let’s take a look at this
key interface.
IHttpHandler
Here it is. Shield your eyes while you look at Listing 19-2 (just kidding—it’s not a very big
interface).
LISTING 19-2 The IHttpHandler Interface
public interface IHttpHandler
{
void ProcessRequest(HttpContext ctx);
bool IsReusable {get;}
}
There’s really not much to it, is there? The interface includes a method named ProcessRequest
and a property named IsReusable. If the handler instance can be used multiple times, then
IsReusable should return true. If the handler generally returns static content, it’s probably
reusable. If the content is dynamic, it’s probably not reusable. The heart of the handler is the
“Lookup,” and its value attribute should be “Lookup.” Next, look up the new value for
the “Feature” selection tag within the HttpContext’s Request.Params collection. If the
value is not null, then the end user selected something. Write the value provided by the
“Feature” selection tag. Finally, write out closing tags. That is, </form>, </body>, and </
html> tags.
Have the ProcessRequest method call the ManageForm method like so:
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
public class CustomFormHandler : IHttpHandler
{
public void ProcessRequest(HttpContext ctx)
{
ManageForm(ctx);
}
public void ManageForm(HttpContext context)
{
context.Response.Write("<html><body><form>");
context.Response.Write(
"<h2>Hello there. What's cool about .NET?</h2>");
context.Response.Write(
"<select name='Feature'>");
context.Response.Write(
"<option> Strong typing</option>");
context.Response.Write(
"<option> Managed code</option>");
context.Response.Write(
"<option> Language agnosticism</option>");
424 Part IV Diagnostics and Plumbing
The code within the ProcessRequest will render a form element and a select element
that renders a form that can be submitted by the browser. When the form is submitted
back to the server, the parameter collection will contain a Features element. The code
examines the parameter collection to see if it references a feature, and it displays the
feature if it’s been selected.
5. The class library you just created deposits its output in the project directory. In order for
ASP.NET to use the page, the resulting executable needs to live in the application direc-
tory’s bin subdirectory. You can do this by adding the CustomHandlerLib.dll as a project
reference to the Web site. Click the right mouse button on the Web site project within the
Solution Explorer and add a new project reference. Navigate to the CustomFormHandlerLib
project’s bin directory and choose the CustomFormHandlerLib.dll fi le.
6. Now update web.confi g so that it uses the handler when clients request the
CustomFormHandler resource. If you don’t already have a web.confi g in the proj-
ect, add one. Then insert an httpHandlers section that points requests for the
CustomFormHandler to the new CustomFormHandler class.
Chapter 19 Custom Handlers 425
<configuration >
<appSettings/>
<connectionStrings/>
<system.web>
<httpHandlers>
<! There will be some other entries here >
<add path="*.cstm" verb="*"
type="CustomFormHandlerLib.CustomFormHandler, CustomFormHandlerLib"
validate="true" />
</httpHandlers>
</system.web>
</configuration>
Note If this site were running under IIS, you would need to tell IIS about the new fi le types to
be handled by the CustomFormHandler. If you decide to run this application under IIS (instead of
For a handler to use session state, it must have the System.Web.SessionState.IRequiresSessionState
interface in its inheritance list. That way the runtime will know to load and store session state at
the beginning and end of each request.
Listing 19-3 shows a handler with session state enabled.
LISTING 19-3 Example HTTP Handler That Accesses Session State
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.SessionState;
public class HandlerWithSessionState : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext ctx)
{
string strData = (string)ctx.Session["SomeSessionData"];
if (String.IsNullOrEmpty(strData))
{
strData = "This goes in session state";
ctx.Session["SomeSessionData"] = strData;
}
ctx.Response.Write("This was in session state: " + strData);
}
428 Part IV Diagnostics and Plumbing
public bool IsReusable {
get
{
return true;
}
}
}
context.Response.Write("<option> Strong typing</option>");
context.Response.Write("<option> Managed code</option>");
context.Response.Write("<option> Language agnosticism</option>");
context.Response.Write("<option> Better security model</option>");
context.Response.Write(
"<option> Threading and async delegates</option>");
context.Response.Write("<option> XCOPY deployment</option>");
context.Response.Write(
"<option> Reasonable HTTP handling framework</option>");
context.Response.Write("</select>");
context.Response.Write("</br>");
context.Response.Write(
"<input type=submit name='Lookup' value='Lookup'></input>");
context.Response.Write("</br>");
if (context.Request.Params["Feature"] != null)
{
context.Response.Write("Hi, you picked: ");
context.Response.Write(context.Request.Params["Feature"]);
context.Response.Write(" as your favorite feature.</br>");
}
context.Response.Write("</form></body></html>");
}
public bool IsReusable
{
get
{
return false;
}
}
}
to create and deploy because you don’t need to modify the web.confi g fi le, nor do you need
to modify the IIS metabase.
Chapter 19 Quick Reference
To Do This
Create a custom handler assembly
Create a new class implementing IHttpHandler.
Implement the IsReusable property.
Implement ProcessRequest.
Assign a fi le mapping to the handler in
ASP.NET
Confi gure the handler in the httpHandler segment of the
application’s web.confi g fi le.
Assign a fi le mapping to the handler in IIS Click the right mouse button on the virtual directory.
Select Properties.
Click the Confi gure button.
Click the Add button.
Add a new extension and map it to aspnet_isapi.dll.
Create a simple handler
Select Web site, Add New Item.
Select Generic Handler from the templates.
Insert your own code for responding to the request.
T
o
Do Thi
s
class="bi x0 y0 w1 h1"
433
Part V
Services, AJAX, Deployment,
and Silverlight
with Distributed Component Object Model (DCOM) before .NET came along.
The next step in connecting computers is happening over the Internet. There’s already a
ubiquitous connection available (computers connected via HTTP, the HyperText Transfer
Protocol) and a well-understood wire format (XML). Together, these two elements make up
XML Web Services.
Remoting
The desire to call software methods “over there” from “over here” has been around ever since
the advent of distributed computing networks. Beginning in the days of Remote Procedure
Calls all the way through the latest version of DCOM, the promise of remoting has been to
435
436 Part V Services, AJAX, Deployment, and Silverlight
exercise a network of computers to solve computing problems rather than pinning the whole
problem on a single computer.
Remoting involves several fundamental steps:
1. The caller fl attens the local method call stack into a stream that may be sent over the
wire. This process is known as serialization.
2. The caller sends the serialized call stack across the wire.
3. The endpoint receives the serialized call stack and turns it into a usable call stack on the
server. This is known as deserialization.
4. The endpoint processes the method call.
5. The endpoint transmits the results back to the caller.
Figure 20-1 illustrates the basic connection underlying any remoting activity.
Client
Transmitter
(proxy layer)
Receiver
(stub/sink layer)
Real object
Server
schema applied to them, which in effect may be thought of as a transportable type sys-
tem. Web services are also responsible for providing metadata (Web Service Description
Language) describing the messages they consume and produce.
SOAP
Although it seems obvious that the Web is an excellent medium for distributing a user
interface–oriented application to the masses, it may not seem so obvious that the same tech-
nology might be used to make method calls. One of the main reasons Web services may exist
now is because different enterprises can agree on what a method call looks like, and they can
all access it over already existing HTTP connections.
XML Web Service method calls are encoded using XML. The format that callers and ser-
vices agree on was originally named Simple Object Access Protocol. The full name has been
dropped, but the moniker “SOAP” remains. The SOAP protocol is an XML formalization for
message-based communication. SOAP defi nes how to format messages, how to bind mes-
sages over HTTP, and a standard error representation.
Transporting the Type System
The primary interoperability focus of XML Web Services is to widen the audience of an ap-
plication so that as many clients as possible can invoke methods of the service. Because the
connective medium involved is the Internet, any computer that can invoke HTTP requests
becomes a potential client. Paired with the ability to connect over HTTP and to format calls
as SOAP messages, a client can make calls to any of your Web service’s methods.
438 Part V Services, AJAX, Deployment, and Silverlight
With the focus on interoperability among as many platforms as possible, it becomes very im-
portant that the caller and the service agree on the data types being passed back and forth.
When a client calls a method containing parameters, the two endpoints might each have their
own way of interpreting the parameter types. For example, passing a character string between
two .NET endpoints does not pose a major problem. However, passing a string between a
client running a non NET platform and a service written using .NET does pose a problem be-
cause a character string type is almost certainly represented differently on each platform.
When calling methods between two computers using HTTP and XML, it’s very important
that a schema is provided on each end so that the parameter types are interpreted correctly.
with the ASMX extension to ASP.NET, where they’re handled like any other request.
ASP.NET includes an attribute named WebMethod that maps a SOAP request and its response
to a real method in a class. To make the service work, you simply derive a class from System
.Web.Services.WebService and expose methods using WebMethod. When the request comes
through, the target class will be “bound” to the .asmx endpoint. As with normal page execu-
tion, the current HttpContext is always available. In addition, ASP.NET automates WSDL gen-
eration, and Microsoft provides tools to automate generating client-side proxies given WSDL
input from an XML Web Service.
The following example illustrates an XML Web Service that retrieves quotes from the quotes
collection we saw in Chapters 15 and 18. This example will expose the quotes collection via a
set of methods expressed as an XML Web Service.
Write an ASP.NET Web service
1. Create a new Web site project. Name the project QuoteService. Make it a fi le system–
based ASP.NET Web Service.
2. Rename the code fi le in App_Code from Service.cs to QuoteService.cs. Rename the
ASMX fi le from Service.asmx to QuoteService.asmx. Use the Visual Studio refactoring
facilities to change the name of the service class from Service to QuoteService. Open the
QuoteService.cs fi le. Highlight the name of the Service class and click the right mouse
button on it. From the local menu, select Rename from the Refactor menu. When
prompted, type QuoteService for the new name of the class. This will change the
name of the class in the C# code fi les. Then you’ll need to change the reference to the
service class in the .asmx fi le manually. This is the line at the top of the .asmx fi le:
<%@ WebService Language="C#" CodeBehind="~/App_Code/QuoteService.cs" Class="Service" %>
It should become
<%@ WebService Language="C#" CodeBehind="~/App_Code/QuoteService.cs" Class="QuoteService" %>
3. After all this is done, your stubbed-out XML Web Service should look like this:
using System;
using System.Linq;
using System.Web;
using System.Web.Services;
Chapter 20 ASP.NET Web Services 441
By default, ASP.NET renders the names of the available methods when you just GET the
ASMX fi le. Notice that the HelloWorld method (provided by Visual Studio) is exposed.
If you want to try running the HelloWorld method, click the HelloWorld link, which
renders a new page with a button you can click to invoke the method. The site will re-
spond with this page:
5. Examine the WSDL. Before adding any code, click the Service Description link on the
fi rst page displayed after surfi ng to the XML Web Service. The Web service will send
back the WSDL for the site. You can page through it to see what WSDL looks like. The
information contained in the WSDL is not meant for human consumption but, rather,
for client proxy generators (which we’ll examine soon).
442 Part V Services, AJAX, Deployment, and Silverlight
6. Add code to manage quotes. To have some quotes to expose as Web methods, import
the QuotesCollection from Chapter 15. The project name is UseDataCaching. Highlight
the App_Code node within the solution explorer. Select Web Site, Add Existing Item
from the main menu and fi nd the fi le QuotesCollection.cs. In addition to importing the
QuotesCollection.cs fi le, grab the QuotesCollection.xml and QuotesCollection.xsd fi les
from the UseDataCaching\App_Data directory and place them in the App_Data direc-
tory for this project.
7. Write a method to load the QuotesCollection. Put the code in the QuoteService.cs fi le.
Check fi rst to see if the QuotesCollection is in the cache. If not, create a QuotesCollection
object and load it using the quotescollection.xml and quotescollection.xsd fi les. Load
the quotes into the application cache during the construction of the QuoteService class.
When you add the data to the cache, build a dependency on the quotescollection.xml
fi le. One of the Web methods we’ll add will modify the XML fi le, so we’ll want to fl ush it
from the cache when it’s updated. The code from the data caching chapter loaded the
XML data in the Page_Load handler. In this case, there’s no page, so you need a sepa-
rate method to load the quotes. Here’s the code that does the trick. Also, notice the
new CacheDependency(strFilePathXml);
ctx.Cache.Insert("quotesCollection",
quotesCollection,
cacheDependency,
Cache.NoAbsoluteExpiration,
Cache.NoSlidingExpiration,
CacheItemPriority.Default,
null);
}
return quotesCollection;
}
public QuoteService () {
}
[WebMethod]
public string HelloWorld() {
return "Hello World";
}
}
8. Write a method that retrieves a random quote from the table and sends it back to the
client. The QuotesCollection class derives from the DataTable class, which is a collection
of DataRows. Unfortunately, returning a DataRow from a Web method doesn’t work
444 Part V Services, AJAX, Deployment, and Silverlight
because DataRow doesn’t have a default constructor. So instead, add a new class to the
Web service that wraps the quote data. That is, add a new class that contains strings for
the quote, the originator’s fi rst name, and the originator’s last name.
Delete the HelloWorld Web method and add a new Web method. Name the new
Web method for fetching a quote GetAQuote. Have GetAQuote load the quotes using
LoadQuotes. The GetAQuote method should generate a number between zero and the
number of rows in the QuotesCollection, fetch that row from the table, wrap the data in
a Quote class, and return it to the client. Be sure to adorn the GetAQuote method with
}
public Quote(String strQuote,
String strOriginatorLastName,
String strOriginatorFirstName)
{
_strQuote = strQuote;
_strOriginatorLastName = strOriginatorLastName;
_strOriginatorFirstName = strOriginatorFirstName;
Chapter 20 ASP.NET Web Services 445
}
}
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class QuoteService : System.Web.Services.WebService
{
// Other code here
// LoadQuotes goes here
[WebMethod]
public Quote GetAQuote()
{
QuotesCollection quotesCollection = this.LoadQuotes();
int nNumQuotes = quotesCollection.Rows.Count;
Random random = new Random();
int nQuote = random.Next(nNumQuotes);
DataRow dataRow = quotesCollection.Rows[nQuote];
Quote quote = new Quote((String)dataRow["Quote"],
(String)dataRow["OriginatorLastName"],
(String)dataRow["OriginatorFirstName"]);
return quote;
}