SUMMARY
In-line script blocks and external code components can be
used to implement custom routines that are invoked during the course of an XSL
transformation (XSLT) to perform calculations and process data. Microsoft
recommends that you avoid the usage of in-line script blocks, and instead use
external code components to implement such custom routines when you design
portable XSLT style sheets. External code components that are used by XSLT are
referred to as XSLT
Extension objects.
This step-by-step article describes how to
set up and test sample ASP.NET applications based on a possible real world
scenario to demonstrate how to use
Extension objects while you execute XSLT in Microsoft .NET
applications.
back to the top
Overview of the Sample Scenario
This section provides a description of the scenario that drives
the design of the sample application components that you will create.
ABC Corp. is a retailer that stocks and sells food supplies that are
manufactured by a few select suppliers. ABC Corp. uses an ASP.NET Intranet
application to record and track information that pertains to its inventory,
customer orders, and external supplier purchase orders. One limitation in the
current inventory management and purchase order modules is the lack of a
feature to dynamically view the current stock levels for a product at a
supplier site before placing a purchase order. The information technology team
at ABC Corp. has informed the management that this feature can be implemented
if the information systems that are used by the suppliers have provisions to
dynamically exchange the required data. The management team at ABC Corp.
discussed this functionality with each of the suppliers and found that none of
the suppliers had the provision to handle this requirement.
One of
the suppliers, XYZ Corp., expressed interest in pursuing this option because it
felt that such a feature can also benefit other retailers that XYZ Corp. does
business with. The information systems at XYZ Corp. are also built on the .NET
Framework by using .NET technologies. The management teams of both the
organizations decided to let their information technology personnel meet and
brainstorm the implementation architecture for this feature. The following is a
high-level listing of the components of the final agreed-upon implementation
model:
The rest of this article describes how to implement the
components that are described in this model. To keep things simple, you will
create and test all of the components on a single computer.
back to the top
Create the Sample Extension Object
In this section you create the
Extension object that the supplier (XYZ Corp.) uses in the sample scenario
to dynamically retrieve product stock information. For illustration purposes,
this sample uses the data that is contained in the Products table in the SQL
Server Northwind sample database. The
Extension object implements a single method named
GetCurrentStockLevel. The method takes a product ID as its input parameter and returns
the current units in stock for the specified product based on the data in the
Products table.
This
Extension object and method are invoked by the XSLT style sheet that is
used to transform the XML that is posted by the retailer (ABC Corp.) to
retrieve current stock information for the requested products.
To
create this component:
- In Visual Studio .NET, create a new Visual Basic .NET Class
Library project named InventoryExtensionObject.
- Delete the default Class1.vb module that is added to the
project.
- Add a new Class module named Products.vb to the project. Open this in the code editor, and then delete the
existing code.
- Paste the following code in the Products.vb class module to implement the Products class:
Note You must change the User ID <username>
value and the password =<strong password> value to the correct values before you run this
code. Make sure that User ID has the appropriate permissions to perform this
operation on the database.
Imports System.Data.SqlClient
Public Class Products
Private cn As SqlConnection
Public Function GetCurrentStockLevel(ByVal ProductId As Integer) As Integer
'Construct query to retrieve UnitsInStock for the specified ProductId.
Dim cmd As New SqlCommand("Select UnitsInStock from Products where ProductID = " & ProductId, cn)
Dim UnitsInStock As Integer
'Execute query to retrieve UnitsInStock.
UnitsInStock = CType(cmd.ExecuteScalar(), Integer)
Return UnitsInStock
End Function
Public Sub New()
'You will need to modify the connection string in the following
'statement to point to your instance of the Northwind SQL Server
'sample database.
cn = New SqlConnection("server=.;database=northwind;user id=<username>;password=<strong password>;")
cn.Open()
End Sub
Public Sub Dispose()
cn.Close()
End Sub
End Class
- Save the changes to Products.vb, and then build the
solution.
back to the top
Create the XSLT Style Sheet That Uses the Extension Object
In this section you create the XSLT style sheet that the supplier
(XYZ Corp.) uses in the sample scenario to generate the HTML table that
displays information about the current stock level for products that the
retailer (ABC Corp.) requests. This style sheet is used to transform the XML
that is posted by the retailer that contains the list of product IDs for which
current stock information is required. It uses the
Extension object that you created in the previous section to retrieve data
that pertains to the current units in stock for each product.
Use the
following code to create and save an XSLT style sheet named Products.xsl to
your hard disk:
<?xml version='1.0'?>
<!-- Notice how a namespace URI and prefix are declared to reference the extension object -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
xmlns:InventoryExtensionObj="urn:InventoryExtensionObj">
<xsl:template match="Products">
<HTML>
<BODY>
<H3>XYZ Corp. Inventory stock level</H3><BR/>
<TABLE BORDER="1">
<TR>
<TH>Product Id</TH>
<TH>Product Name</TH>
<TH>Units In Stock</TH>
</TR>
<xsl:for-each select="Product">
<TR>
<TD><xsl:value-of select="@id"/></TD>
<TD><xsl:value-of select="@name"/></TD>
<!--
Call extension object method to retrieve stock information.
Notice how the id attribute of the Product element is passed as the input
parameter. The actual instance of the Extension object will be supplied
by the code that executes the XSLT transformation.
-->
<TD><xsl:value-of select="InventoryExtensionObj:GetCurrentStockLevel(@id)"/></TD>
</TR>
</xsl:for-each>
</TABLE>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>
back to the top
Create the Sample ASP.NET Applications to Test the XSLT Transformation
In this section you create two sample ASP.NET applications. One
ASP.NET application is used to host an ASP.NET Web form that the retailer (ABC
Corp.) uses in the sample scenario to select the products for which updated
stock information is required. This Web form contains code in its code-behind
class module to generate and post the XML that contains the list of selected
products to a component on the site of the supplier (XYZ Corp.) and to display
the returned HTML response.
The other ASP.NET application is used to
implement a Web form in the system of the supplier to which the XML that the
retailer generates is posted. The code-behind class module of this Web form
contains code to load the XML that is posted to it and executes the XSLT
transformation by using the XSLT style sheet that you created in the previous
step to generate and return the required HTML. It also contains the code to
create and supply an instance of the
Extension object to the XSLT transformation process. First you create the
ASP.NET application for the supplier.
back to the top
Supplier ASP.NET Application to Execute XSLT Transformation
- In Visual Studio .NET, create a new Visual Basic .NET Web
Application project named SupplierSample.
- Delete the default Web form (WebForm1.aspx) that is added
to the project.
- Add the XSLT style sheet (Products.xsl) that you created in
the previous step to the Web project.
- Add a project reference to the Extension object DLL (InventoryExtensionObject.dll) that you created in the
"Create the Sample Extension Object" section of this article.
- Add a new Web form named GetProductStockLevels.aspx to the
project and view its code.
- Add the following Imports statements at the top of the of the class module before the Class
declaration:
Imports System.Xml
Imports System.Xml.XPath
Imports System.Xml.Xsl
- Paste the following code in the Page_Load event procedure to execute the XSLT transformation on the XML
that is posted to the Web form. Study the inline comments to see how the Extension object is instantiated and used by the transformation process:
Try
'Instantiate a new XPathDocument object and load the posted XML.
Dim xmldoc As New XPathDocument(Request.InputStream)
'Instantiate a new XslTransform object and load the style sheet.
Dim xslt As New XslTransform()
xslt.Load(Server.MapPath("products.xsl"))
'Instantiate an XsltArgumentList object.
'An XsltArgumentList object is used to supply extension object instances
'and values for XSLT paarmeters required for an XSLT transformation.
Dim xsltArgList As New System.Xml.Xsl.XsltArgumentList()
'Instantiate and add an instance of the extension object to the XsltArgumentList.
'The AddExtensionObject method is used to add the Extension object instance to the
'XsltArgumentList object. The namespace URI specified as the first parameter
'should match the namespace URI used to reference the Extension object in the
'XSLT style sheet.
Dim InventoryExtensionObj As New InventoryExtensionObject.Products()
xsltArgList.AddExtensionObject("urn:InventoryExtensionObj", InventoryExtensionObj)
'Set the ContentType of the ASP.NET Response object to text/html.
Response.ContentType = "text/html"
'Execute the transformation and generate the output to the Response object's
'output stream. Notice how the XsltArgumentList object to which the Extension
'object instance was added is supplied as a parameter when executing the
'Transform method of the XslTransform object.
xslt.Transform(xmlDoc, xsltArgList, Response.OutputStream)
InventoryExtensionObj.Dispose()
InventoryExtensionObj = Nothing
'Exception handling code.
Catch xsltExp As System.Xml.Xsl.XsltException
Response.Write(xsltExp.Message)
Catch xsltCompileExp As System.Xml.Xsl.XsltCompileException
Response.Write(xsltCompileExp.Message)
Catch XPathExp As System.Xml.XPath.XPathException
Response.Write(XPathExp.Message)
Catch XmlExp As XmlException
Response.Write(XmlExp.Message)
End Try
- Save, compile, and build the Web project
solution.
back to the top
Retailer ASP.NET Application to Query Supplier Stock Levels
- In Visual Studio .NET, create a new Visual Basic .NET Web
Application named RetailerSample.
- Delete the default Web form (WebForm1.aspx) that is added
to the project.
- Add a new Web form named GetSupplierStockLevels.aspx to the
project.
- Switch to the HTML pane of the Web form in the designer
window and replace the existing code with the following to generate a simple
test user interface: The user interface displays three check boxes that
represent three products that are supplied by the supplier (and for which data
exists in the Northwind sample database) in the sample scenario. The check
boxes are hard coded in this sample for simplicity. They can be generated
dynamically by using data binding and the ASP.NET CheckBoxList server control in a real world application.
<%@ Page Language="vb" AutoEventWireup="false" Codebehind="GetSupplierStockLevels.aspx.vb" Inherits="RetailerSample.GetSupplierStockLevels"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<title>TestPost</title>
<meta content="Microsoft Visual Studio.NET 7.0" name="GENERATOR">
<meta content="Visual Basic 7.0" name="CODE_LANGUAGE">
<meta content="JavaScript" name="vs_defaultClientScript">
<meta content="http://schemas.microsoft.com/intellisense/ie5" name="vs_targetSchema">
</HEAD>
<body MS_POSITIONING="GridLayout">
<form id="Form1" method="post" runat="server">
<asp:Label id="Label1" style="Z-INDEX: 101; LEFT: 39px;
POSITION: absolute; TOP: 87px" runat="server"
Width="575px" Height="43px" Font-Names="Arial"
Font-Size="Medium">Get Stock Levels from XYZ Corp. for selected Products
</asp:Label>
<asp:CheckBox id="Product3" style="Z-INDEX: 104; LEFT: 359px;
POSITION: absolute; TOP: 154px" runat="server" Width="170px"
Height="30px" Font-Names="Arial" Font-Size="X-Small" Text="Aniseed syrup">
</asp:CheckBox>
<asp:CheckBox id="Product1" style="Z-INDEX: 102; LEFT: 39px;
POSITION: absolute; TOP: 155px" runat="server" Width="122px"
Height="35px" Font-Names="Arial" Font-Size="X-Small" Text="Chai">
</asp:CheckBox>
<asp:CheckBox id="Product2" style="Z-INDEX: 103; LEFT: 175px;
POSITION: absolute; TOP: 155px" runat="server" Width="170px"
Height="30px" Font-Names="Arial" Font-Size="X-Small" Text="Chang">
</asp:CheckBox>
<asp:Button id="Button1" style="Z-INDEX: 105; LEFT: 40px;
POSITION: absolute; TOP: 237px" runat="server" Width="134px"
Height="33px" Text="Go">
</asp:Button>
<asp:Label id="Label2" style="Z-INDEX: 106; LEFT: 36px;
POSITION: absolute; TOP: 21px" runat="server" Width="579px"
Height="37px" Font-Names="Arial" Font-Size="Medium"
ForeColor="#0000C0">ABC Corp : Verify product stock levels with Partner company
</asp:Label></form>
</body>
</HTML>
- Open the code-behind class module of the Web form in the
Visual Studio .NET code editor.
- Add the following Imports statements before the GetSupplierStockLevels class declaration:
Imports System.Net
Imports System.IO
- Paste the following code in the Page_Load event procedure: This code generates the XML listing the selected
products, posts the XML to the supplier application, and displays the returned
HTML to generate the table that lists the current supplier stock levels. It
uses the HttpWebRequest and HttpWebResponse classes in the System.Net namespace to post the XML to the supplier site and access the
returned response. For more information about the methods of these objects used
in this code, see the .NET Framework SDK documentation about these classes.
'Check to see if a post back occurred. If yes, then execute code
'to generate the XML and post it to the Supplier application.
If Page.IsPostBack Then
'Use a StringBuilder object to generate the XML string to post to the
'Supplier application. Strings in .NET are non-mutable. Using a String
'variable to generate the following XML string would result in multiple
'String objects being created behind the scenes each time the variable is
'modified. This can be prevented by using a StringBuilder object.
Dim strXML As New System.Text.StringBuilder()
'Append a string for the root <Products> node.
strXML.Append("<Products>")
'Generate a <Product> node for each selected product. To do this,
'examine the Checked property of each product check box on the web form.
If Product1.Checked = True Then
strXML.Append("<Product id='1' name='" & Product1.Text & "'/>")
End If
If Product2.Checked = True Then
strXML.Append("<Product id='2' name='" & Product2.Text & "'/>")
End If
If Product3.Checked = True Then
strXML.Append("<Product id='3' name='" & Product3.Text & "'/>")
End If
'Append the closing </Products> node.
strXML.Append("</Products>")
'Instantiate a System.Net.HttpWebRequest object to post the generated XML to the
'the GetProductStockLevels.aspx web form in the Supplier application.
Dim PostURL as String = "http://localhost/SupplierSample/GetProductStockLevels.aspx"
Dim HttpWReq As HttpWebRequest = CType(WebRequest.Create(PostURL), HttpWebRequest)
'Intantiate a Stream object. The generated XML will be written to this object.
Dim HttpStream As Stream
'Instantiate a Byte Array and use the GetBytes Static method of
'of the System.Text.Encoding.UTF8 class to write the UTF-8 encoded
'byte representation of the generated XML string to the Byte Array.
Dim buf As Byte()
buf = System.Text.Encoding.UTF8.GetBytes(strXML.ToString)
'Set the Method property of the HttpWebRequest to POST to indicate
'that it will be used to execute an HTTP POST request.
HttpWReq.Method = "POST"
'Write the contents of the Byte Array (the UTF-8 encoded byte representation
'of the XML string) to the HttpWebRequest's RequestStream. This is the data that
'will be posted to the target URL. The GetRequestStream method is used to obtain
'a reference to the HttpWebRequest's request stream.
HttpStream = HttpWReq.GetRequestStream()
HttpStream.Write(buf, 0, buf.Length)
HttpStream.Close()
'Execute the GetResponse method of the HttpWebRequest object to execute the POST
'and use the generated Response to instantiate an HttpWebResponse object.
Dim HttpWResp As HttpWebResponse = CType(HttpWReq.GetResponse(), HttpWebResponse)
'Use the GetResponseStream method of the HttpWebResponse object
'to obtain a reference to its response stream.
Dim receiveStream As Stream = HttpWResp.GetResponseStream()
'Use the GetEncoding Static method of the System.Text.Encoding object to
'instantiate a UTF-8 Encoding object.
Dim encode As System.Text.Encoding = System.Text.Encoding.GetEncoding("utf-8")
' Pipe the response stream to a higher level stream reader with the required
'encoding format. The StreamReader object will be used to access the contents
'of the ResponseStream.
Dim readStream As New StreamReader(receiveStream, encode)
'Loop through and write out the contents of the ResponseStream (contains
'the HTML generated and returned by the Supplier application) 256 characters
'at a time to the ASP.NET Response object to display the HTML table listing
'the Products and their current stock levels at the Supplier site.
Dim read(256) As Char
Dim count As Integer = readStream.Read(read, 0, 256)
Console.WriteLine("HTML..." + ControlChars.Lf + ControlChars.Cr)
While count > 0
'Write the 256 characters to a string.
Dim str As New String(read, 0, count)
Response.Write(str)
count = readStream.Read(read, 0, 256)
End While
'Close the StreamReader.
readStream.Close()
'Close the HttpWebResponse object.
HttpWResp.Close()
Response.End()
End If
- Save, compile, and build the Web project
solution.
back to the top
Test the Sample ASP.NET Applications
- Use the following URL to locate the
GetSupplierStockLevels.aspx Web form in the Retailer application:
http://localhost/RetailerSample/GetSupplierStockLevels.aspx
- Use the check boxes on the Web form to select the products
for which you want to obtain current stock information from the Supplier
application.
- Click the Go button to generate the XML to reflect the selected products, post
the XML to the GetProductStockLevels.aspx Web form in the Supplier application,
and display the returned HTML. You will see a HTML table that lists the
selected products together with their current stock levels at the Supplier
site.
back to the top
More Information
Implementing and accessing
Extension objects in XSLT style sheets is the recommended design
methodology when you must invoke custom routines in an XSL transformation. XSLT
Extension objects do not require inline script blocks in the XSLT style
sheet. Inline script blocks may make the style sheet dependent on
vendor-specific XSLT-extension script tags and scripting or programming
language interpreters or compilers. XSLT
Extension objects, on the other hand, do not specify or define any
implementation details that are related to the technologies and components that
are used to package the custom routines. All that is required in the XSLT style
sheet is a namespace URI and prefix that are associated with the
Extension object. The defined prefix is used to qualify calls that are made
to the methods of the
Extension object in the XSLT style sheet. You can use the technology of
your choice to implement the actual
Extension object component. The only requirement is that the XML or XSLT
processor that is used to execute the transformation must implement an
interface that permits you to supply instances of
Extension objects as input arguments to an XSLT transformation process.
Using
Extension objects to implement custom routines is also a recommended
solution for the problem that is described in the following Microsoft Knowledge
Base article:
316775 PRB: Cannot Unload Assemblies That You Create and Load by Using Script in XSLT
back to the top