Tải bản đầy đủ (.pdf) (88 trang)

C# 3.0 Cookbook phần 8 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (423.51 KB, 88 trang )

Validating Modified XML Documents Without Reloading
|
591
One item to be aware of when dealing with multiple XmlDocuments is that when you
take a node from one
XmlDocument, you cannot just append it as a child to a node in a
different
XmlDocument because the node has the context of the original XmlDocument.If
you try to do this, you will get the following exception message:
The node to be inserted is from a different document context.
To fix this, use the XmlDocument.ImportNode method, which will make a copy (deep
when the second parameter is true, or shallow when the second parameter is false) of
the node you are bringing over to the new
XmlDocument. For instance, when you add
the shipping information like so:
invoice.DocumentElement.AppendChild(invoice.ImportNode(shipInfo,true));
this line takes the shipInfo node, clones it deeply, then appends it to the main
invoice node.
See Also
The “XElement Class,” “The Three Parts of a LINQ Query,” “XmlDocument Class,”
“XmlElement Class,” and “XmlAttribute Class” topics in the MSDN documentation.
15.11 Validating Modified XML Documents Without
Reloading
Problem
You are using the XDocument or the XmlDocument to modify an XML document loaded
in memory. Once the document has been modified, the modifications need to be ver-
ified, and schema defaults need to be enforced.
Solution
Use the XDocument.Validate method to perform the validation and apply schema
defaults and type information.
Create an


XmlSchemaSet with the XML Schema document (book.xsd) and an
XmlReader and then load the book.xml file using XDocument.Load:
// Create the schema set
XmlSchemaSet xmlSchemaSet = new XmlSchemaSet( );
// add the new schema with the target namespace
// (could add all the schema at once here if there are multiple)
xmlSchemaSet.Add(" /> XmlReader.Create(@" \ \Book.xsd"));
XDocument book = XDocument.Load(@" \ \Book.xml");
592
|
Chapter 15: XML
Set up a ValidationEventHandler to catch any errors and then call XDocument.
Validate
with the schema set and the event handler to validate book.xml against the
book.xsd schema:
ValidationHandler validationHandler = new ValidationHandler( );
ValidationEventHandler validationEventHandler = validationHandler.HandleValidation;
// validate after load
book.Validate(xmlSchemaSet, validationEventHandler);
The ValidationHandler class holds the current validation state in a ValidXml property
and the code for the
ValidationEventHandler implementation method
HandleValidation:
public class ValidationHandler
{
private object _syncRoot = new object( );
public ValidationHandler( )
{
lock(_syncRoot)
{

// set the initial check for validity to true
this.ValidXml = true;
}
}
public bool ValidXml { get; private set; }
public void HandleValidation(object sender, ValidationEventArgs e)
{
lock(_syncRoot)
{
// we got called, so this isn't valid
ValidXml = false;
Console.WriteLine("Validation Error Message: {0}", e.Message);
Console.WriteLine("Validation Error Severity: {0}", e.Severity);
if (e.Exception != null)
{
Console.WriteLine("Validation Error Line Number: {0}",
e.Exception.LineNumber);
Console.WriteLine("Validation Error Line Position: {0}",
e.Exception.LinePosition);
Console.WriteLine("Validation Error Source: {0}",
e.Exception.Source);
Console.WriteLine("Validation Error Source Schema: {0}",
e.Exception.SourceSchemaObject);
Console.WriteLine("Validation Error Source Uri: {0}",
e.Exception.SourceUri);
Console.WriteLine("Validation Error thrown from: {0}",
e.Exception.TargetSite);
Console.WriteLine("Validation Error callstack: {0}",
e.Exception.StackTrace);
}

}
Validating Modified XML Documents Without Reloading
|
593
}
}
Add a new element node that is not in the schema into the XDocument and then call
Validate again with the schema set and event handler to revalidate the changed
XDocument. If the document triggers any validation events, then the
ValidationHandler.ValidXml property is set to false in the ValidationHandler
instance:
// add in a new node that is not in the schema
// since we have already validated, no callbacks fire during the add
book.Root.Add(new XElement("BogusElement","Totally"));
// now we will do validation of the new stuff we added
book.Validate(xmlSchemaSet, validationEventHandler);
if (validationHandler.ValidXml)
Console.WriteLine("Successfully validated modified LINQ XML");
else
Console.WriteLine("Modified LINQ XML did not validate successfully");
Console.WriteLine( );
You could also use the XmlDocument.Validate method to perform the validation in a
similar fashion to
XDocument:
string xmlFile = @" \ \Book.xml";
string xsdFile = @" \ \Book.xsd";
// Create the schema set.
XmlSchemaSet schemaSet = new XmlSchemaSet( );
// Add the new schema with the target namespace
// (could add all the schema at once here if there are multiple).

schemaSet.Add(" XmlReader.Create(xsdFile));
// Load up the XML file.
XmlDocument xmlDoc = new XmlDocument( );
// Add the schema.
xmlDoc.Schemas = schemaSet;
Load the book.xml file into the XmlDocument, set up a ValidationEventHandler to
catch any errors, and then call
Validate with the event handler to validate book.xml
against the book.xsd schema:
// validate after load
xmlDoc.Load(xmlFile);
ValidationHandler handler = new ValidationHandler( );
ValidationEventHandler eventHandler = handler.HandleValidation;
xmlDoc.Validate(eventHandler);
Add a new element node that is not in the schema into the XmlDocument and then call
Validate again with the event handler to revalidate the changed XmlDocument. If the
document triggers any validation events, then the
ValidationHandler.ValidXml prop-
erty is set to
false:
594
|
Chapter 15: XML
// Add in a new node that is not in the schema.
// Since we have already validated, no callbacks fire during the add
XmlNode newNode = xmlDoc.CreateElement("BogusElement");
newNode.InnerText = "Totally";
// Add the new element.
xmlDoc.DocumentElement.AppendChild(newNode);
// Now we will do validation of the new stuff we added.

xmlDoc.Validate(eventHandler);
if (handler.ValidXml)
Console.WriteLine("Successfully validated modified XML");
else
Console.WriteLine("Modified XML did not validate successfully");
Discussion
One advantage to using XmlDocument over XDocument is that there is an override to the
XmlDocument.Validate method that allows you to pass a specific XmlNode to validate.
This fine grain of control is not present on
XDocument. If the XmlDocument is large, this
override to
Validate should be used:
public void Validate(
ValidationEventHandler validationEventHandler,
XmlNode nodeToValidate
);
One other approach to this problem is to instantiate an instance of the XmlNodeReader
with the XmlDocument and then create an XmlReader with validation settings as shown
in Recipe 15.4. This would allow for continual validation while the reader navigated
through the underlying XML.
The output from running the code is listed here:
Validation Error Message: The element 'Book' in namespace ' />xsd' has invalid child element 'BogusElement'. List of possible elements expected:
'Chapter' in namespace ' />Validation Error Severity: Error
Validation Error Line Number: 0
Validation Error Line Position: 0
Validation Error Source:
Validation Error Source Schema:
Validation Error Source Uri: file:///C:/PRJ32/Book_2_0/C%23Cookbook2/Code/
CSharpRecipes/Book.xml
Validation Error thrown from:

Validation Error callstack:
Modified XML did not validate successfully
Notice that the BogusElement element that you added was not part of the schema for
the
Book element, so you got a validation error along with the information about
where the error occurred. Finally, you got a report that the modified XML did not
validate correctly.
Extending Transformations
|
595
See Also
Recipe 15.3; the “XDocument Class,” and the “XmlDocument.Validate” topics in
the MSDN documentation.
15.12 Extending Transformations
Problem
You want to perform operations that are outside the scope of the transformation
technology to include data in the transformed result.
Solution
If you are using LINQ to XML, you can call out to a function directly when trans-
forming the result set, as shown here by the call to
GetErrata:
XElement publications = XElement.Load(@" \ \publications.xml");
XElement transformedPublications =
new XElement("PublishedWorks",
from b in publications.Elements("Book")
select new XElement(b.Name,
new XAttribute(b.Attribute("name")),
from c in b.Elements("Chapter")
select new XElement("Chapter", GetErrata(c))));
Console.WriteLine(transformedPublications.ToString( ));

Console.WriteLine( );
The GetErrata method used in the above sample is listed here:
private static XElement GetErrata(XElement chapter)
{
// In here, we could go do other lookup calls (XML, database, web service)
// to get information to add back in to the transformation result
string errata = string.Format("{0} has {1} errata",
chapter.Value, chapter.Value.Length);
return new XElement("Errata", errata);
}
If you are using XSLT, you can add an extension object to the transformation that
can perform the operations necessary based on the node it is passed. This can be
accomplished by using the
XsltArgumentList.AddExtensionObject method. This
object you’ve created (
XslExtensionObject) can then be accessed in the XSLT and a
method called on it to return the data you want included in the final transformed
result:
string xmlFile = @" \ \publications.xml";
string xslt = @" \ \publications.xsl";
//Create the XslTransform and load the stylesheet.
// This is not XslCompiledTransform because it gives a different empty node.
596
|
Chapter 15: XML
//Create the XslCompiledTransform and load the stylesheet.
XslCompiledTransform transform = new XslCompiledTransform( );
transform.Load(xslt);
// Load the XML.
XPathDocument xPathDoc = new XPathDocument(xmlFile);

// Make up the args for the stylesheet with the extension object.
XsltArgumentList xslArg = new XsltArgumentList( );
// Create our custom extension object.
XSLExtensionObject xslExt = new XSLExtensionObject( );
xslArg.AddExtensionObject("urn:xslext", xslExt);
// Send output to the console and do the transformation.
using (XmlWriter writer = XmlWriter.Create(Console.Out))
{
transform.Transform(xPathDoc, xslArg, writer);
}
Note that when the extension object is added to the XsltArgumentList, it supplies a
namespace of
urn:xslext. This namespace is used in the XSLT stylesheet to refer-
ence the object. The
XSLExtensionObject is defined here:
// Our extension object to help with functionality
public class XslExtensionObject
{
public XPathNodeIterator GetErrata(XPathNodeIterator nodeChapter)
{
// In here, we could go do other lookup calls
// (XML, database, web service) to get information to
// add back in to the transformation result.
string errata =
string.Format("<Errata>{0} has {1} errata</Errata>",
nodeChapter.Current.Value, nodeChapter.Current.Value.Length);
XmlDocument xDoc = new XmlDocument( );
xDoc.LoadXml(errata);
XPathNavigator xPathNav = xDoc.CreateNavigator( );
xPathNav.MoveToChild(XPathNodeType.Element);

XPathNodeIterator iter = xPathNav.Select(".");
return iter;
}
}
The GetErrata method is called during the execution of the XSLT stylesheet to pro-
vide data in
XPathNodeIterator format to the transformation. The xmlns:xslext
namespace is declared as urn:xslext, which matches the namespace value you
passed as an argument to the transformation. In the processing of the
Book template
for each
Chapter,anxsl:value-of is called with the select criteria containing a call to
the
xslext:GetErrata method. The stylesheet makes the call, as shown here:
<xsl:stylesheet version="1.0" xmlns:xsl=" /> xmlns:xslext="urn:xslext">
<xsl:template match="/">
Extending Transformations
|
597
<xsl:element name="PublishedWorks">
<xsl:apply-templates/>
</xsl:element>
</xsl:template>
<xsl:template match="Book">
<Book>
<xsl:attribute name ="name">
<xsl:value-of select="@name"/>
</xsl:attribute>
<xsl:for-each select="Chapter">
<Chapter>

<xsl:value-of select="xslext:GetErrata(/)"/>
</Chapter>
</xsl:for-each>
</Book>
</xsl:template>
</xsl:stylesheet>
Discussion
Using LINQ to XML, you can extend your transformation code to include addi-
tional logic simply by adding method calls that know how to operate and return
XElements. This is simply adding another method call to the query that contributes to
the result set, and no additional performance penalty is assessed just by the call. Cer-
tainly if the operation is expensive it could slow down the transformation, but this is
now easily located when your code is profiled.
The ability to call custom code from inside of an
XSLT stylesheet is a very powerful
one, but one that should be used cautiously. Adding code like this into stylesheets
usually renders them less useful in other environments. If the stylesheet never has to
be used to transform XML in another parser, this can be a good way to offload work
that is either difficult or impossible to accomplish in regular
XSLT syntax.
The sample data used in the Solution is presented here:
<?xml version="1.0" encoding="utf-8"?>
<Publications>
<Book name="Subclassing and Hooking with Visual Basic">
<Chapter>Introduction</Chapter>
<Chapter>Windows System-Specific Information</Chapter>
<Chapter>The Basics of Subclassing and Hooks</Chapter>
<Chapter>Subclassing and Superclassing</Chapter>
<Chapter>Subclassing the Windows Common Dialog Boxes</Chapter>
<Chapter>ActiveX Controls and Subclassing</Chapter>

<Chapter>Superclassing</Chapter>
<Chapter>Debugging Techniques for Subclassing</Chapter>
<Chapter>WH_CALLWNDPROC</Chapter>
<Chapter>WH_CALLWNDPROCRET</Chapter>
<Chapter>WH_GETMESSAGE</Chapter>
<Chapter>WH_KEYBOARD and WH_KEYBOARD_LL</Chapter>
<Chapter>WH_MOUSE and WH_MOUSE_LL</Chapter>
<Chapter>WH_FOREGROUNDIDLE</Chapter>
598
|
Chapter 15: XML
<Chapter>WH_MSGFILTER</Chapter>
<Chapter>WH_SYSMSGFILTER</Chapter>
<Chapter>WH_SHELL</Chapter>
<Chapter>WH_CBT</Chapter>
<Chapter>WH_JOURNALRECORD</Chapter>
<Chapter>WH_JOURNALPLAYBACK</Chapter>
<Chapter>WH_DEBUG</Chapter>
<Chapter>Subclassing .NET WinForms</Chapter>
<Chapter>Implementing Hooks in VB.NET</Chapter>
</Book>
<Book name="C# Cookbook">
<Chapter>Numbers</Chapter>
<Chapter>Strings and Characters</Chapter>
<Chapter>Classes And Structures</Chapter>
<Chapter>Enums</Chapter>
<Chapter>Exception Handling</Chapter>
<Chapter>Diagnostics</Chapter>
<Chapter>Delegates and Events</Chapter>
<Chapter>Regular Expressions</Chapter>

<Chapter>Collections</Chapter>
<Chapter>Data Structures and Algorithms</Chapter>
<Chapter>File System IO</Chapter>
<Chapter>Reflection</Chapter>
<Chapter>Networking</Chapter>
<Chapter>Security</Chapter>
<Chapter>Threading</Chapter>
<Chapter>Unsafe Code</Chapter>
<Chapter>XML</Chapter>
</Book>
<Book name="C# Cookbook 2.0">
<Chapter>Numbers and Enumerations</Chapter>
<Chapter>Strings and Characters</Chapter>
<Chapter>Classes And Structures</Chapter>
<Chapter>Generics</Chapter>
<Chapter>Collections</Chapter>
<Chapter>Iterators and Partial Types</Chapter>
<Chapter>Exception Handling</Chapter>
<Chapter>Diagnostics</Chapter>
<Chapter>Delegates, Events, and Anonymous Methods</Chapter>
<Chapter>Regular Expressions</Chapter>
<Chapter>Data Structures and Algorithms</Chapter>
<Chapter>File System IO</Chapter>
<Chapter>Reflection</Chapter>
<Chapter>Web</Chapter>
<Chapter>XML</Chapter>
<Chapter>Networking</Chapter>
<Chapter>Security</Chapter>
<Chapter>Threading and Synchronization</Chapter>
<Chapter>Unsafe Code</Chapter>

<Chapter>Toolbox</Chapter>
</Book>
</Publications>
Getting Your Schemas in Bulk from Existing XML Files
|
599
See Also
The “LINQ, transforming data” and “XsltArgumentList Class” topics in the MSDN
documentation.
15.13 Getting Your Schemas in Bulk from Existing XML
Files
Problem
You have come on to a new project in which XML was used for data transmission,
but the programmers who came before you didn’t use an XSD for one reason or
another. You need to generate beginning schema files for each of the XML examples.
Solution
Use the XmlSchemaInference class to infer schema from the XML samples. The
GenerateSchemasForDirectory function in Example 15-12 enumerates all of the XML
files in a given directory and processes each of them using the
GenerateSchemasForFile
method. GenerateSchemasForFile uses the XmlSchemaInference.InferSchema method to
get the schemas for the given XML file. Once the schemas have been determined,
GenerateSchemasForFile rolls over the collection and saves out each schema to an XSD
file using a
FileStream.
Example 15-12. Generating an XML Schema
public static void GenerateSchemasForFile(string file)
{
// set up a reader for the file
using (XmlReader reader = XmlReader.Create(file))

{
XmlSchemaSet schemaSet = new XmlSchemaSet( );
XmlSchemaInference schemaInference =
new XmlSchemaInference( );
// get the schema
schemaSet = schemaInference.InferSchema(reader);
string schemaPath = string.Empty;
foreach (XmlSchema schema in schemaSet.Schemas( ))
{
// make schema file path and write it out
schemaPath = Path.GetDirectoryName(file) + @"\" +
Path.GetFileNameWithoutExtension(file) + ".xsd";
using (FileStream fs =
new FileStream(schemaPath, FileMode.OpenOrCreate))
{
schema.Write(fs);
600
|
Chapter 15: XML
The GenerateSchemasForDirectory method can be called like this:
// Get the directory two levels up from where we are running.
DirectoryInfo di = new DirectoryInfo(@" \ ");
string dir = di.FullName;
// Generate the schema.
GenerateSchemasForDirectory(dir);
Discussion
Having an XSD for the XML files in an application allows for a number of things:
1. Validation of XML presented to the system
2. Documentation of the semantics of the data
3. Programmatic discovery of the data structure through XML reading methods

Using the
GenerateSchemasForFile method can jump-start the process of developing
schema for your XML, but each schema should be reviewed by the team member
responsible for producing the XML. This will help to ensure that the rules as stated
in the schema are correct and also to make sure that additional items such as schema
default values and other relationships are added. Any relationships that were not
present in the example XML files would be missed by the schema generator.
See Also
The “XmlSchemaInference Class” and “XML Schemas (XSD) Reference” topics in
the MSDN documentation.
}
}
}
}
public static void GenerateSchemasForDirectory(string dir)
{
// make sure the directory exists
if (Directory.Exists(dir))
{
// get the files in the directory
string[] files = Directory.GetFiles(dir, "*.xml");
foreach (string file in files)
{
GenerateSchemasForFile(file);
}
}
}
Example 15-12. Generating an XML Schema (continued)
Passing Parameters to Transformations
|

601
15.14 Passing Parameters to Transformations
Problem
You need to transform some data using a mostly common pattern. For the few data
items that could change between transformations, you don’t want to have a separate
mechanism for each variation.
Solution
If you are using LINQ to XML, simply build a method to encapsulate the transfor-
mation code and pass parameters to the method just as you normally would for
other code:
// transform using LINQ instead of XSLT
string storeTitle = "Hero Comics Inventory";
string pageDate = DateTime.Now.ToString("F");
XElement parameterExample = XElement.Load(@" \ \ParameterExample.xml");
string htmlPath = @" \ \ParameterExample_LINQ.htm";
TransformWithParameters(storeTitle, pageDate, parameterExample, htmlPath);
// now change the parameters
storeTitle = "Fabulous Adventures Inventory";
pageDate = DateTime.Now.ToString("D");
htmlPath = @" \ \ParameterExample2_LINQ.htm";
TransformWithParameters(storeTitle, pageDate, parameterExample, htmlPath);
The TransformWithParameters method looks like this:
private static void TransformWithParameters(string storeTitle, string pageDate,
XElement parameterExample, string htmlPath)
{
XElement transformedParameterExample =
new XElement("html",
new XElement("head"),
new XElement("body",
new XElement("h3", string.Format("Brought to you by {0} on {1}{2}",

storeTitle,pageDate,Environment.NewLine)),
new XElement("br"),
new XElement("table",
new XAttribute("border","2"),
new XElement("thead",
new XElement("tr",
new XElement("td",
new XElement("b","Heroes")),
new XElement("td",
new XElement("b","Edition")))),
new XElement("tbody",
from cb in parameterExample.Elements("ComicBook")
orderby cb.Attribute("name").Value descending
select new XElement("tr",
new XElement("td",cb.Attribute("name").Value),
602
|
Chapter 15: XML
new XElement("td",cb.Attribute("edition").
Value))))));
transformedParameterExample.Save(htmlPath);
}
If you are using XSLT to perform transformations, use the XsltArgumentList class to
pass arguments to the XSLT transformation. This technique allows the program to
generate an object for the stylesheet to access (such as a dynamic string) and use
while it transforms the given XML file. The
storeTitle and pageDate arguments are
passed in to the transformation in the following example. The
storeTitle is for the
title of the comic store, and

pageDate is the date the report is run for. These are
added using the
AddParam method of the XsltArgumentList object instance args:
XsltArgumentList args = new XsltArgumentList( );
args.AddParam("storeTitle", "", "Hero Comics Inventory");
args.AddParam("pageDate", "", DateTime.Now.ToString("F"));
// Create a resolver with default credentials.
XmlUrlResolver resolver = new XmlUrlResolver( );
resolver.Credentials = System.Net.CredentialCache.DefaultCredentials;
The XsltSettings class allows changing the behavior of the transformation. If you
use the
XsltSettings.Default instance, the transformation will be done without
allowing scripting or the use of the
document( ) XSLT function, as they can be secu-
rity risks. If the stylesheet is from a trusted source, you can just create an
XsltSettings object and use it, but it is better to be safe. Further changes to the code
could open it up to use with untrusted XSLT stylesheets:
XslCompiledTransform transform = new XslCompiledTransform( );
// Load up the stylesheet.
transform.Load(@" \ \ParameterExample.xslt", XsltSettings.Default, resolver);
// Perform the transformation.
FileStream fs = null;
using (fs = new FileStream(@" \ \ParameterExample.htm",
FileMode.OpenOrCreate, FileAccess.Write))
{
transform.Transform(@" \ \ParameterExample.xml", args, fs);
}
To show the different parameters in action, now you change storeTitle and
pageDate again and run the transformation again:
// Now change the parameters and reprocess.

args = new XsltArgumentList( );
args.AddParam("storeTitle", "", "Fabulous Adventures Inventory");
args.AddParam("pageDate", "", DateTime.Now.ToString("D"));
using (fs = new FileStream(@" \ \ParameterExample2.htm",
FileMode.OpenOrCreate, FileAccess.Write))
{
transform.Transform(@" \ \ParameterExample.xml", args, fs);
}
Passing Parameters to Transformations
|
603
The ParameterExample.xml file contains the following:
<?xml version="1.0" encoding="utf-8" ?>
<ParameterExample>
<ComicBook name="The Amazing Spider-Man" edition="1"/>
<ComicBook name="The Uncanny X-Men" edition="2"/>
<ComicBook name="Superman" edition="3"/>
<ComicBook name="Batman" edition="4"/>
<ComicBook name="The Fantastic Four" edition="5"/>
</ParameterExample>
The ParameterExample.xslt file contains the following:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl=" /> <xsl:output method="html" indent="yes" />
<xsl:param name="storeTitle"/>
<xsl:param name="pageDate"/>
<xsl:template match="ParameterExample">
<html>
<head/>
<body>
<h3><xsl:text>Brought to you by </xsl:text>

<xsl:value-of select="$storeTitle"/><br/>
<xsl:text> on </xsl:text>
<xsl:value-of select="$pageDate"/>
<xsl:text> &#xd;&#xa;</xsl:text>
</h3>
<br/>
<table border="2">
<thead>
<tr>
<td>
<b>Heroes</b>
</td>
<td>
<b>Edition</b>
</td>
</tr>
</thead>
<tbody>
<xsl:apply-templates/>
</tbody>
</table>
</body>
</html>
</xsl:template>
<xsl:template match="ComicBook">
<tr>
<td>
<xsl:value-of select="@name"/>
</td>
<td>

604
|
Chapter 15: XML
<xsl:value-of select="@edition"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
The output from the first transformation using XSLT to ParameterExample.htm or
using LINQ to ParameterExample_LINQ.htm is shown in Figure 15-2.
Output from the second transformation using XSLT to ParameterExample2.htm or
using LINQ to ParameterExample2_LINQ.htm is shown in Figure 15-3.
Figure 15-2. Output from the first set of parameters
Figure 15-3. Output from the second set of parameters
Passing Parameters to Transformations
|
605
Discussion
Both approaches allow you to templatize your code and provide parameters to mod-
ify the output. With the LINQ to XML method, the code is all in .NET, and .NET
analysis tools can be used to measure the impact of the transformation. The declara-
tive style of the code conveys the intent more clearly than having to go to the exter-
nal XSLT file. If you don’t know XSLT, you don’t have to learn it as you can do it in
code now.
If you already know XSLT, you can continue to leverage it. The ability to pass infor-
mation to the XSLT stylesheet allows a much greater degree of flexibility when
designing reports or user interfaces via XSLT transformations. This capability can
help customize the output based on just about any criteria you can think of, as the
data being passed in is totally controlled by your program. Once you get the hang of
using parameters with XSLT, a whole new level of customization becomes possible.

As an added bonus, it is portable between environments (.NET, Xalan, etc.).
See Also
The “LINQ, transforming data,” “XsltArgumentList Class,” and “XsltSettings Class”
topics in the MSDN documentation.
606
Chapter 16
CHAPTER 16
Networking 16
16.0 Introduction
.NET provides many classes to help make network programming easier than many
environments that preceded it. There is a great deal of functionality to assist you
with tasks such as:
• Building network-aware applications.
• Downloading files via FTP.
• Sending and receiving HTTP requests.
• Getting a higher degree of control using TCP/IP and sockets directly.
In the areas in which Microsoft has not provided managed classes to access network-
ing functionality (such as some of the methods exposed by the
WinInet API for Inter-
net connection settings), there is always P/Invoke, so you can code to the Win32
API; you’ll explore this in this chapter. With all of the functionality at your disposal
in the
System.Net namespaces, you can write network utilities very quickly. Let’s take
a closer look at just a few of the things this section of .NET provides you access to.
16.1 Writing a TCP Server
Problem
You need to create a server that listens on a port for incoming requests from a TCP
client. These client requests can then be processed at the server, and any responses
can be sent back to the client. Recipe 16.2 shows how to write a TCP client to inter-
act with this server.

Solution
Use the MyTcpServer class created here to listen on a TCP-based endpoint for requests
arriving on a given port:
Writing a TCP Server
|
607
class MyTcpServer
{
#region Private Members
private TcpListener _listener;
private IPAddress _address;
private int _port;
private bool _listening;
private object _syncRoot = new object( );
#endregion
#region CTORs
public MyTcpServer(IPAddress address, int port)
{
_port = port;
_address = address;
}
#endregion // CTORs
The TCPServer class has two properties:

Address, an IPAddress
• Port, an int
These return the current address and port on which the server is listening and the lis-
tening state:
#region Properties
public IPAddress Address

{
get { return _address; }
}
public int Port
{
get { return _port; }
}
public bool Listening
{
get { return _listening; }
}
#endregion
The Listen method tells the MyTcpServer class to start listening on the specified
address and port combination. You create and start a
TcpListener, and then call its
AcceptTcpClient method to wait for a client request to arrive. Once the client con-
nects, a request is sent to the thread pool to service the client and that runs the
ProcessClient method.
The listener shuts down after serving the client:
#region Public Methods
public void Listen( )
608
|
Chapter 16: Networking
{
try
{
lock (_syncRoot)
{
_listener = new TcpListener(_address, _port);

// fire up the server
_listener.Start( );
// set listening bit
_listening = true;
}
// Enter the listening loop.
do
{
Trace.Write("Looking for someone to talk to ");
// Wait for connection
TcpClient newClient = _listener.AcceptTcpClient( );
Trace.WriteLine("Connected to new client");
// queue a request to take care of the client
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessClient),
newClient);
}
while (_listening);
}
catch (SocketException se)
{
Trace.WriteLine("SocketException: " + se.ToString( ));
}
finally
{
// shut it down
StopListening( );
}
}
The StopListening method is called to stop the TCPServer from listening for requests:
public void StopListening( )

{
if (_listening)
{
lock (_syncRoot)
{
// set listening bit
_listening = false;
// shut it down
_listener.Stop( );
}
}
}
#endregion
Writing a TCP Server
|
609
The ProcessClient method shown in Example 16-1 executes on a thread-pool thread
to serve a connected client. It gets the
NetworkStream from the client using the
TcpClient.GetStream method and then reads the whole request. After sending back a
response, it shuts down the client connection.
Example 16-1. ProcessClient method
#region Private Methods
private void ProcessClient(object client)
{
TcpClient newClient = (TcpClient)client;
try
{
// Buffer for reading data
byte[] bytes = new byte[1024];

StringBuilder clientData = new StringBuilder( );
// get the stream to talk to the client over
using (NetworkStream ns = newClient.GetStream( ))
{
// set initial read timeout to 1 minute to allow for connection
ns.ReadTimeout = 60000;
// Loop to receive all the data sent by the client.
int bytesRead = 0;
do
{
// read the data
try
{
bytesRead = ns.Read(bytes, 0, bytes.Length);
if (bytesRead > 0)
{
// Translate data bytes to an ASCII string and append
clientData.Append(
Encoding.ASCII.GetString(bytes, 0, bytesRead));
// decrease read timeout to 1 second now that data is
// coming in
ns.ReadTimeout = 1000;
}
}
catch (IOException ioe)
{
// read timed out, all data has been retrieved
Trace.WriteLine("Read timed out: {0}",ioe.ToString( ));
bytesRead = 0;
}

}
while (bytesRead > 0);
Trace.WriteLine("Client says: {0}", clientData.ToString( ));
// Thank them for their input
bytes = Encoding.ASCII.GetBytes("Thanks call again!");
610
|
Chapter 16: Networking
A simple server that listens for clients until the Escape key is pressed might look like
the following code:
class Program
{
static MyTcpServer server;
static void Main( )
{
// Run the server on a different thread
ThreadPool.QueueUserWorkItem(RunServer);
Console.WriteLine("Press Esc to stop the server ");
ConsoleKeyInfo cki;
while(true)
{
cki = Console.ReadKey( );
if (cki.Key == ConsoleKey.Escape)
break;
}
}
static void RunServer( object stateInfo )
{
// Fire it up
server = new MyTcpServer(IPAddress.Loopback,55555);

server.Listen( );
}
}
When talking to the MyTcpClient class in Recipe 16.2, the output for the server looks
like this:
Press Esc to stop the server
Looking for someone to talk to Connected to new client
Looking for someone to talk to Client says: Just wanted to say hi
Connected to new client
Looking for someone to talk to Client says: Just wanted to say hi again
Connected to new client
// Send back a response.
ns.Write(bytes, 0, bytes.Length);
}
}
finally
{
// stop talking to client
if(newClient != null)
newClient.Close( );
}
}
#endregion
}
Example 16-1. ProcessClient method (continued)
Writing a TCP Server
|
611
Looking for someone to talk to Client says: Are you ignoring me?
Connected to new client

Looking for someone to talk to Connected to new client
Looking for someone to talk to Client says: I'll not be ignored! (round 0)
Client says: I'll not be ignored! (round 1)
Connected to new client
Looking for someone to talk to Connected to new client
Looking for someone to talk to Client says: I'll not be ignored! (round 2)
Client says: I'll not be ignored! (round 3)
Connected to new client
Looking for someone to talk to Client says: I'll not be ignored! (round 4)
Connected to new client
Looking for someone to talk to Client says: I'll not be ignored! (round 5)
Connected to new client
Looking for someone to talk to Client says: I'll not be ignored! (round 6)
Connected to new client
Looking for someone to talk to Client says: I'll not be ignored! (round 7)
Connected to new client
Looking for someone to talk to Client says: I'll not be ignored! (round 8)
[more output follows ]
Discussion
The Transmission Control Protocol (TCP) is the protocol used by the majority of
traffic on the Internet today. TCP is responsible for the correct delivery of data pack-
ets from one endpoint to another. It uses the Internet Protocol (IP) to make the deliv-
ery. IP handles getting the packets from node to node; TCP detects when packets are
not correct, are missing, or are sent out of order, and it arranges for missing or dam-
aged packets to be resent. The
TCPServer class is a basic server mechanism for deal-
ing with requests that come from clients over TCP.
MyTcpServer takes the IP address and port passed in the Listen method and creates a
TcpListener on that IPAddress and port. Once created, the TcpListener.Start
method is called to start up the server. The blocking AcceptTcpClient method is

called to listen for requests from TCP-based clients. Once the client connects, the
ProcessClient method is executed. In this method, the server reads request data from
the client and returns a brief acknowledgment. The server disconnects from the cli-
ent by calling
NetworkStream.Close and TcpClient.Close. The server stops listening
when the
StopListening method is called. StopListening takes the server offline by
calling
TcpListener.Stop.
See Also
The “IPAddress Class,” “TcpListener Class,” and “TcpClient Class” topics in the
MSDN documentation.
612
|
Chapter 16: Networking
16.2 Writing a TCP Client
Problem
You want to interact with a TCP-based server.
Solution
Use the MyTcpClient class shown in Example 16-2 to connect to and converse with a
TCP-based server by passing the address and port of the server to talk to, using the
System.Net.TcpClient class. This example will talk to the server from Recipe 16.1.
Example 16-2. MyTcpClient class
class MyTcpClient
{
private TcpClient _client;
private IPAddress _address;
private int _port;
private IPEndPoint _endPoint;
private bool _disposed;

public MyTcpClient(IPAddress address, int port)
{
_address = address;
_port = port;
_endPoint = new IPEndPoint(_address, _port);
}
public void ConnectToServer(string msg)
{
try
{
_client = new TcpClient( );
_client.Connect(_endPoint);
// Get the bytes to send for the message
byte[] bytes = Encoding.ASCII.GetBytes(msg);
// Get the stream to talk to the server on
using (NetworkStream ns = _client.GetStream( ))
{
// Send message
Trace.WriteLine("Sending message to server: " + msg);
ns.Write(bytes, 0, bytes.Length);
// Get the response
// Buffer to store the response bytes
bytes = new byte[1024];
// Display the response
int bytesRead = ns.Read(bytes, 0, bytes.Length);
Writing a TCP Client
|
613
To use the MyTcpClient in a program, you can simply create an instance of it and call
ConnectToServer to send a request. In this program, you first make three calls to the

server to test the basic mechanism. Next, you enter a loop to really pound on it and
make sure you force it over the default
ThreadPool limit. This verifies that the server’s
mechanism for handling multiple requests is sound:
static void Main( )
{
MakeClientCallToServer("Just wanted to say hi");
MakeClientCallToServer("Just wanted to say hi again");
MakeClientCallToServer("Are you ignoring me?");
string serverResponse = Encoding.ASCII.GetString(bytes, 0, bytesRead);
Trace.WriteLine("Server said: " + serverResponse);
}
}
catch (SocketException se)
{
Trace.WriteLine("There was an error talking to the server: " +
se.ToString( ));
}
finally
{
Dispose( );
}
}
#region IDisposable Members
public void Dispose( )
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)

{
if (!_disposed)
{
if (disposing)
{
if (_client != null)
_client.Close( );
}
_disposed = true;
}
}
#endregion
}
Example 16-2. MyTcpClient class (continued)
614
|
Chapter 16: Networking
// Now send a bunch of messages
string msg;
for (int i = 0; i < 100; i++)
{
msg = string.Format(Thread.CurrentThread.CurrentCulture,
"I'll not be ignored! (round {0})", i);
ThreadPool.QueueUserWorkItem(new
WaitCallback(MakeClientCallToServer), msg);
}
Console.WriteLine("\n Press any key to continue (if you can find it
)");
Console.Read( );
}

static void MakeClientCallToServer(object objMsg)
{
string msg = (string)objMsg;
MyTcpClient client = new MyTcpClient(IPAddress.Loopback,55555);
client.ConnectToServer(msg);
}
The output on the client side for this exchange of messages is:
Sending message to server: Just wanted to say hi
Server said: Thanks call again!
Sending message to server: Just wanted to say hi again
Server said: Thanks call again!
Sending message to server: Are you ignoring me?
Server said: Thanks call again!
Press any key to continue (if you can find it )
Sending message to server: I'll not be ignored! (round 0)
Sending message to server: I'll not be ignored! (round 1)
Server said: Thanks call again!
Server said: Thanks call again!
Sending message to server: I'll not be ignored! (round 2)
Server said: Thanks call again!
Sending message to server: I'll not be ignored! (round 3)
Sending message to server: I'll not be ignored! (round 4)
Server said: Thanks call again!
Server said: Thanks call again!
Sending message to server: I'll not be ignored! (round 5)
Sending message to server: I'll not be ignored! (round 6)
Server said: Thanks call again!
Server said: Thanks call again!
Sending message to server: I'll not be ignored! (round 7)
Sending message to server: I'll not be ignored! (round 8)

Server said: Thanks call again!
[more output follows ]
Discussion
MyTcpClient.ConnectToServer is designed to send one message, get the response, dis-
play it as a string, and then close the connection. To accomplish this, it creates a
Simulating Form Execution
|
615
System.Net.TcpClient and connects to the server by calling the TcpClient.Connect
method. Connect targets the server using an IPEndPoint built from the address and
port that you passed to the
MyTcpClient constructor.
MyTcpClient.ConnectToServer then gets the bytes for the string using the Encoding.
ASCII.GetBytes
method. Once it has the bytes to send, it gets the NetworkStream from
the underlying
System.Net.TcpClient by calling its GetStream method and then sends
the message using the
TcpClient.Write method.
In order to receive the response from the server, the blocking
TcpClient.Read method
is called. Once Read returns, the bytes are decoded to get the string that contains the
response from the server. The connections are then closed and the client ends.
See Also
The “TcpClient Class,” “NetworkStream Class,” and “Encoding.ASCII Property”
topics in the MSDN documentation.
16.3 Simulating Form Execution
Problem
You need to send a collection of name-value pairs to simulate a form being executed
on a browser to a location identified by a URL.

Solution
Use the System.Net.WebClient class to send a set of name-value pairs to the web
server using the
UploadValues method. This class enables you to masquerade as the
browser executing a form by setting up the name-value pairs with the input data.
The input field ID is the name, and the value to use in the field is the value:
using System;
using System.Net;
using System.Text;
using System.Collections.Specialized;
// In order to use this, you need to run the CSCBWeb project first.
Uri uri = new Uri("http://localhost:7472/CSCBWeb/WebForm1.aspx");
WebClient client = new WebClient( );
// Create a series of name/value pairs to send
// Add necessary parameter/value pairs to the name/value container.
NameValueCollection collection = new NameValueCollection( )
{ {"Item", "WebParts"},
{"Identity", ""},
{"Quantity", "5"} };

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×