Practical ASP.NET
Build RESTful ASP.NET Apps
Create a virtual page handler that lets you deliver data directly from your database to your Web users. The handler also lets you move your app into the world of REST Web Services.
Technology Toolbox: Visual Basic, ASP.NET, XML
Many sites retrieve their content from tables in databases.
The Web pages for these sites are just wrappers for the data, holding the code that pulls the data out of a table and wraps it in some HTML eye-candy to make it presentable. If any part of your site draws its content primarily from a database (or any other data source), you can improve your site's performance by creating your own virtual path handler. The best part of this strategy is that it's not hard to do; creating and implementing a basic path handler takes less than 20 lines of code.
Using your own path handler does more than improve the performance of data-heavy pages; it also positions you to convert those pages to a Web Service. Wrapper pages are good candidates for Web Services because all you need to do is remove the HTML that surrounds the data.
In the world of Web Services, there are two standards: SOAP (Simple Object Access Protocol) and REST (REpresentional State Transfer). SOAP is the dominant protocol and is widely supported by most toolkits including .NET. The REST protocol, while less widespread, has a major advantage over SOAP: REST services are much simpler to call than SOAP services. Both Yahoo and eBay implement REST interfaces, while Amazon provides both SOAP and REST interfaces.
The REST protocol is defined by four rules. The fundamental rule is that every resource can be retrieved from a unique URL without a query string or additional information. For example, you might use a URL such as http://MySite/Inventory/A123to access information about an item in inventory with the part number A123.
This doesn't mean that you must have a separate page for an inventory item. The REST rules only specify that a unique URL returns the data. REST doesn't specify where that data is stored or how you retrieve it. Creating your own virtual path handler lets you create RESTful Web Services in ASP.NET. Also, a RESTful Web Service outperforms the equivalent service implemented with SOAP.
Implementing your own path handler lets you store all of your site content in a database (or any other repository), deliver it to your users when requested, and implement RESTful Web Services. You need to replace the VirtualPathManager used by ASP.NET to retrieve pages with your own class and create VirtualFile classes to be called by the path manager to process requests. A site can have a single active VirtualPathManager, and multiple VirtualFiles (see Figure 1).
One caveat before you begin: You can't use virtual-path handlers with precompiled sites. Other than that, you're free to incorporate this technology into any site where you want to provide clients with access to data.
Create a VirtualPathManager
Creating a minimal implementation of a VirtualPathManager requires that you create a class that inherits from System.Web.Hosting.VirtualPathProvider and implement two methods: FileExists and the GetFile.
The FileExists property is called by ASP.NET whenever a page is first requested and is passed the virtual path of the page. In other words, it is returned the requested URL minus the http protocol prefix and any querystring. If you can handle the virtual path required, you should return True from the method; return False if you can't. However, if you do fail to handle the request, your site simply returns a 404 error, so a minimal implementation of the method can return True every time.
ASP.NET calls the GetFile method after the FileExists method to retrieve the file to be processed. This method is also passed the virtual path and must return a VirtualFile object. You must create a VirutalFile object in this method, passing a string to the VirtualFile's constructor. Typically, you pass the VirtualFile the requested virtual path, but you can pass any data that you wish. The code in your VirtualFile objects prepares and returns the data specified by the URL.
There is a wrinkle here: Your site probably consists of a combination of static Web pages, and pages that wrap data from your database. Your VirtualFile objects handle the data requests, but you don't want to replace ASP.NET's default processing for static pages. The simplest way to handle requests for static Web pages is to pass those requests to the GetFile method of the VirtualPathProvider that you replace. You can access the provider that you replace through the Previous property of the VirtualPathProvider base class.
This sample VirtualPathProvider parses the URL to determine if the request's URL specifies the Inventory virtual directory of a site called RESTFulASP. Note that .NET's VirtualPathUtility provides methods that parse the URL for you. If the URL does specify the Inventory directory, the code creates an Inventory object (which inherits from VirtualFile). You pass all other requests to the previous provider's GetFile method. The code passes the request's URL to the object's constructor:
Public Class SiteProvider
Inherits System.Web.Hosting.VirtualPathProvider
Public Overrides Function FileExists( _
ByVal virtualPath As String) As Boolean
Return True
End Function
Public Overrides Function GetFile( _
ByVal virtualPath As String) As _
System.Web.Hosting.VirtualFile
Dim strDirectory As String
strDirectory = VirtualPathUtility.GetDirectory( _
virtualPath)
If strDirectory.ToUpper.IndexOf( _
"/RESTFULASP/INVENTORY") > -1 Then
Dim sf As New InventoryData(virtualPath)
Return sf
Else
Return Previous.GetFile(virtualPath)
End If
End Function
End Class
The next step is to implement the Inventory VirtualFile class. You can retrieve your data any way that you want to in the Inventory object, but you must turn your data into a Stream (with its position set to 0) before handing it over to ASP.NET to return to the client.
Implement a Minimal VirtualFile
Your class must inherit from System.Web.Hosting.VirtualFile and implement two methods: a constructor that accepts a single string and the Open method that returns a Stream object. In the constructor, you must call the constructor for the underlying object. You should save the string that your page manager passes to the method to a module level variable, so you can use it in the Open method to retrieve the requested data.
ASP.NET calls the class's Open method when it's ready to retrieve the page. In this method, you generate the Stream of data to send to the client. Typically, the data passed to the constructor determines what the contents of the Stream, but you can also access data from the client through the collections on the HttpContext.Current.Request object.
For example, this simple implementation returns the file name from the virtual path passed to the constructor, wrapped in some HTML:
Public Class Inventory
Inherits System.Web.Hosting.VirtualFile
Private _VirtualPath As String
Sub New(ByVal virtualPath As String)
MyBase.New(virtualPath)
_VirtualPath = virtualPath
End Sub
Public Overrides Function Open() As System.IO.Stream
Dim strResource As String
strResource = "<HTML><BODY><b>" & _
VirtualPathUtility.GetFileName( _
_VirtualPath) & _
"</b></BODY></HTML>"
Dim st As Byte() = _
System.Text.Encoding.ASCII.GetBytes( _
strResource)
Dim ms As New System.IO.MemoryStream(st)
Return ms
End Function
End Class
To modify this VirtualFile to act as a Web Service, you could wrap the data in a set of XML tags. This code wraps the file name in an XML document:
strResource = "<?xml version='1.0'?><pathName>" & _
VirtualPathUtility.GetFileName( _
_VirtualPath) & _
"</pathName>"
This more complex example retrieves employee data from a database into a DataSet using the URL's file name as the employee ID. Once the data is retrieved, the code adds the XML declaration to a memory stream and then pours the DataSet's XML representation into the Stream. After setting the Stream's position to 0, the code passes the Stream back to ASP.NET, which returns it to the client:
Dim st As Byte()
Dim ms As New System.IO.MemoryStream
Dim con As New System.Data.SqlClient.SqlConnection( _
"?connection string?")
Dim cm As System.Data.SqlClient.SqlCommand = _
con.CreateCommand
cm.CommandText = "Select LastName, FirstName, " & _
"Title From Employees Where EmployeeId = " & _
VirtualPathUtility.GetFileName(_VirtualPath) & ";"
Dim da As New System.Data.SqlClient.SqlDataAdapter(cm)
Dim ds As New System.Data.DataSet
da.Fill(ds, "Employees")
st = System.Text.Encoding.ASCII.GetBytes( _
"<?xml version='1.0'?>")
ms.Write(st, 0, st.Length)
ds.WriteXml(ms)
ms.Position = 0
Return ms
The application now provides both Inventory and Employee data, but you need to enhance the VirtualPathProvider's GetFile method to handle URLs that reference different virtual folders. Using a Select Case statement provides an extendable structure for your VirtualPathProvider that supports multiples VirtualFiles:
Dim strDirectory As String
strDirectory = VirtualPathUtility.GetDirectory( _
virtualPath)
Select Case strDirectory.ToUpper
Case "/RESTFULASP/INVENTORY/"
Dim iv As New Inventory(virtualPath)
Return iv
Case "/RESTFULASP/EMPLOYEE/"
Dim em As New Employee(virtualPath)
Return em
Case Else
Return Previous.GetFile(virtualPath)
End Select
Retrieve Inventory
A client can now retrieve either inventory or employee data by requesting a URL that the VirtualPathHandler and VirtualFiles can process. In .NET, you can use the WebClient object to request the URL. The code a client uses to retrieve the data for the employee with an ID of 1 might look like this:
Dim wc As New System.Net.WebClient
Me.TextBox1.Text = wc.DownloadString( _
"http://RESTfulASP/Employees/1")
In COM environments, you could use the XmlHttp object to request the page. You could also embed the URL in your HTML. This HTML requests inventory data:
<iframe src=" /RESTfulASP/Inventory/A124" />
This code is all you need to create a minimal implementation of a virtual page and virtual file, but there are some other methods and properties that you might want to implement in these classes. For example, ASP.NET calls the VirtualPathProvider's Intialize method when your VirtualPathProvider is registered. You can use this method to perform any set up tasks that you require.
You can support virtual directories from your VirtualFile class, in addition to supporting virtual files. This implementation supports only virtual files, so you should override the IsDirectory property and return False:
Public Overrides ReadOnly Property IsDirectory() As Boolean
Get
Return False
End Get
End Property
You can also give your VirtualFile class a display name by overriding the Name property and returning a string:
Public Overrides ReadOnly Property Name() As String
Get
Return "Inventory.aspx"
End Get
End Property
You need to get your site to start using your path handler before a client can request the URLs you've implemented through the path handler. The RegisterVirtualPathProvider method replaces the existing path handler with an instance of your path handler. However it's essential that you call this method before the client requests any of the URLs that your handler processes. ASP.NET compiles and caches a page the first time a client requests it. If a client requests one of your virtual pages and ASP.NET can't handle the request, ASP.NET assumes that it can't handle any subsequent requests for the same page.
Your goal is to register your handler as soon as possible. Two mechanisms give you the earliest possible chance to do so. The first opportunity is familiar to ASP and ASP.NET programmers: The Application_Start event in the Global.asax file. The second option is to put the code in a shared method named AppInitialize in any class file in the App_Code folder. You can centralize the code for your virtual path if you place the AppInitialize method in your VirtualPathProvider class.
Regardless of where you decide to register your path handler, all you need to do to use it is to create an instance of your provider and pass it to the RegisterVirtualPathProvider method:
Public Class SiteProvider
Inherits System.Web.Hosting.VirtualPathProvider
Shared Sub AppInitialize()
Dim sp As New SiteProvider
System.Web.Hosting.HostingEnvironment. _
RegisterVirtualPathProvider(sp)
End Sub
Your code is complete; now you're ready to start debugging your application. However, the pages that you want to test don't exist, so you can't simply set the start page in Visual Studio and press F5. So, the easiest way to set up debugging is to specify a URL to use when starting. Do this by right clicking on your Web site in Solution Explorer and selecting Property Pages. Next, select Start Options from the tree view on the left in the Property Pages dialog. Finally, go to the Start Options page and select the Specific Page option, then enter a URL that your virtual path handler understands.
That's all there is to creating your own virtual path handler and/or RESTful Web Service. In addition to mimicking a single Web page (as this article demonstrates), you might also mimic a virtual directory by overriding the VirtualFile's IsDirectory property and returning False. You must then implement an enumerator that lets clients browse through a set of resources as if they were Web pages held in a virtual directory. In addition to exploring the additional functionality available in with VirtualPathProviders, you should also take a deeper look at REST's principles. The Wikipedia entry at http://en.wikipedia.org/wiki/Representational_State_Transfer is a good place to start.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.