Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
107
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

418C H A P T E R 1 1 C A C H I N G

still sent and the cached item is considered invalid. Keeping track of what changes do and do not invalidate a cached data object is simply too much work for SQL Server 2000 (although it is possible in SQL Server 2005).

Tip The implementation of cache invalidation with SQL Server 2000 has more overhead than the implementation with SQL Server 2005 and isn’t as fine-grained. As a result, it doesn’t make sense for tables that change frequently or for narrowly defined queries that retrieve only a small subset of records from a table.

Enabling ASP.NET Polling

The next step is to instruct ASP.NET to poll the database. You do this on a per-application basis. In other words, every application that uses cache invalidation will hold a separate connection and poll the notification table on its own.

To enable the polling service, you use the <sqlCacheDepency> element in the web.config file. You set the enabled attribute to true to turn it on, and you set the pollTime attribute to the number of milliseconds between each poll. (The higher the poll time, the longer the potential delay before a change is detected.) You also need to supply the connection string information.

For example, this web.config file checks for updated notification information every 15 seconds:

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <connectionStrings>

<add name="Northwind" connectionString=

"Data Source=localhost;Initial Catalog=Northwind;Integrated Security=SSPI"/> </connectionStrings>

<system.web>

<caching>

<sqlCacheDependency enabled="true" pollTime="15000" > <databases>

<add name="Northwind" connectionStringName="Northwind" /> </databases>

</sqlCacheDependency>

</caching>

...

</system.web>

</configuration>

Creating the Cache Dependency

Now that you’ve seen how to set up your database to support SQL Server notifications, the only remaining detail is the code, which is quite straightforward. You can use your cache dependency with programmatic data caching, a data source control, and output caching.

For programmatic data caching, you need to create a new SqlCacheDependency and supply that to the Cache.Insert() method, much as you did with file dependencies. In the SqlCacheDependency constructor, you supply two strings. The first is the name of the database you defined in the <add> element in the <sqlCacheDependency> section of the web.config file. The second is the name of the linked table.

Here’s an example:

// Create a dependency for the Employees table. SqlCacheDependency empDependency = new SqlCacheDependency(

"Northwind", "Employees");

C H A P T E R 1 1 C A C H I N G

419

// Add a cache item that will be invalidated if this table changes. Cache.Insert("Employees", dsEmployees, empDependency);

To perform the same trick with output caching, you simply need to set the SqlCacheDependency property with the database dependency name and the table name, separated by a colon:

<%@ OutputCache Duration="600" SqlDependency="Northwind:Employees" VaryByParam="none" %>

You can also set the dependency using programmatic output caching with the Response.AddCacheDependency() method:

Response.AddCacheDependency(empDependency)

// Use output caching for this page (for 60 seconds or until the table changes). Response.Cache.SetCacheability(HttpCacheability.Public); Response.Cache.SetExpires(DateTime.Now.AddSeconds(60)); Response.Cache.SetValidUntilExpires(true);

Finally, the same technique works with the SqlDataSource and ObjectDataSource controls:

<asp:SqlDataSource EnableCaching="True" SqlCacheDependency="Northwind:Employees" ... />

To try a complete example, you can use the downloadable code for this chapter.

Cache Notifications in SQL Server 2005

SQL Server 2005 gets closest to the ideal notification solution, because the notification infrastructure is built into the database with a messaging system called Service Broker. The Service Broker manages queues, which are database objects that have the same standing as tables, stored procedures, or views.

Essentially, you can instruct SQL Server 2005 to send notifications for specific events using the CREATE EVENT NOTIFICATION command. ASP.NET offers a higher-level model—you register a query, and ASP.NET automatically instructs SQL Server 2005 to send notifications for any operations that would affect the results of that query. This mechanism works in a similar way to indexed views. Every time you perform an operation, SQL Server determines whether your operation affects a registered command. If it does, SQL Server sends a notification message and stops the notification process.

When using notification with SQL Server, you get the following benefits over SQL Server 2000:

Notification is much more fine-grained: Instead of invalidating your cached object when the table changes, SQL Server 2005 invalidates your object only when a row that affects your query is inserted, updated, or deleted.

Notification is more intelligent: A notification message is sent when the first time the data is changed, but not if the data is changed again (unless you re-register for notification messages by adding an item back to the cache).

No special steps are required to set up notification: You do not run aspnet_regsql or add polling settings to the web.config file.

Notifications work with SELECT queries and stored procedures. However, some restrictions exist for the SELECT syntax you can use. To properly support notifications, your command must adhere to the following rules:

420C H A P T E R 1 1 C A C H I N G

You must fully qualify table names in the form [Owner].table, as in dbo.Employees (not just Employees).

Your query cannot use an aggregate function, such as COUNT(), MAX(), MIN(), or AVERAGE().

You cannot select all columns with the wildcard * (as in SELECT * FROM Employees). Instead, you must specifically name each column so that SQL Server can properly track changes that do and do not affect the results of your query.

Here’s an acceptable command:

SELECT EmployeeID, FirstName, LastName, City FROM dbo.Employees

These are the most important rules, but the SQL Server Books Online has a lengthy list of caveats and exceptions. If you break one of these rules, you won’t receive an error. However, the notification message will be sent as soon as you register the command, and the cached item will be invalidated immediately.

Creating the Cache Dependency

You use a different syntax to use SQL cache dependencies with SQL Server 2005. That’s because it’s not enough to simply identify the database name and table—instead, SQL Server needs to know the exact command.

If you use programmatic caching, you must create the SqlCacheDependency using the constructor that accepts a SqlCommand object. Here’s an example:

// Create the ADO.NET objects.

SqlConnection con = WebConfigurationManager.ConnectionStrings[ "Northwind"].ConnectionString;

string query =

"SELECT EmployeeID, FirstName, LastName, City FROM dbo.Employees"; SqlCommand cmd = new SqlCommand(query, con);

SqlDataAdapter adapter = new SqlDataAdapter(cmd);

//Fill the DataSet. DataSet ds = new DataSet();

adapter.Fill(ds, "Employees");

//Create the dependency.

SqlCacheDependency empDependency = new SqlCacheDependency(cmd);

//Add a cache item that will be invalidated if one of its records changes

//(or a new record is added in the same range). Cache.Insert("Employees", ds, empDependency);

You also need to call the static SqlDependency.Start() to initialize the listening service on the web server. This needs to be performed only once for each database connection. One place you can call the Start() method is in the Application_Start() event handler.

SqlDependency.Start();

Custom Cache Dependencies

ASP.NET gives you the ability to create your own custom cache dependencies by deriving from CacheDependency, in much the same way that SqlCacheDependency does. This feature allows you

C H A P T E R 1 1 C A C H I N G

421

(or third-party developers) to create dependencies that wrap other databases or to create resources such as message queues, Active Directory queries, or even web service calls.

Designing a custom CacheDependency is remarkably easy. All you need to do is start some asynchronous task that checks when the dependent item has changed. When it has, you call the base CacheDependency.NotifyDependencyChanged() method. In response, the base class updates the values of the HasChanged and UtcLastModified properties, and ASP.NET will remove any linked item from the cache.

You can use one of several techniques to create a custom cache dependency. Here are some typical examples:

Start a timer: When this timer fires, poll your resource to see if it has changed.

Start a separate thread: On this thread, check your resource and, if necessary, pause between checks by sleeping the thread.

Attach an event handler to another component: When the event fires, check your resource. For example, you could use this technique with the FileSystemWatcher to watch for a specific type of file change (such as file deletion).

In every case, you perform the basic initialization (attaching event handlers, creating a separate thread, and so on) in the constructor for your dependency.

A Basic Custom Cache Dependency

The following example shows an exceedingly simple custom cache dependency class. This class uses a timer to periodically check if a cached item is still valid.

The first step is to create the class by deriving from CacheDependency:

public class TimerTestCacheDependency : CacheDependency { ... }

When the dependency is first created, you can set up the timer. In this example, the polling time isn’t configurable—it’s hard-coded at 5 seconds. That means every 5 seconds the timer fires and the dependency check runs.

private Timer timer; private int pollTime = 5000;

public TimerTestCacheDependency()

{

//Check immediately and then wait the poll time

//for each subsequent check (same as CacheDependency behavior). timer = new Timer(new TimerCallback(CheckDependencyCallback),

this, 0, pollTime);

}

As a test, the dependency check simply counts the number of times it’s called. Once it’s called for the fifth time (after a total of about 25 seconds), it invalidates the cached item. The important part of this example is how it tells ASP.NET to remove the dependent item. All you need to do is call the base CacheDependency.NotifyDependencyChanged() method, passing in a reference to the event sender (the current class) and any event arguments.

private int count = 0;

private void CheckDependencyCallback(object sender)

{

// Check your resource here. If it has changed, notify ASP.NET:

422 C H A P T E R 1 1 C A C H I N G

count++;

if (count > 4)

{

//Signal that the item is expired. base.NotifyDependencyChanged(this, EventArgs.Empty);

//Don't fire this callback again.

timer.Dispose();

}

}

The last step is to override DependencyDispose() to perform any cleanup that you need. DependencyDispose() is called soon after you use the NotifyDependencyChanged() method to invalidate the cached item. At this point, the dependency is no longer needed.

protected override void DependencyDispose()

{

// Cleanup code goes here.

if (timer != null) timer.Dispose();

}

Once you’ve created a custom dependency class, you can use it in the same way as the CacheDependency class, by supplying it as a parameter when you call Cache.Insert():

TimerTestCacheDependency dependency = new TimerTestCacheDependency();

Cache.Insert("MyItem", item, dependency);

A Custom Cache Dependency Using Message Queues

Now that you’ve seen how to create a basic custom cache dependency, it’s worth considering a more practical example. The following MessageQueueCacheDependency monitors a Windows Messaging Queuing (formerly known as MSMQ) queue. As soon as that queue receives a message, the item is considered expired (although you could easily extend the class so that it waits to receive a specific message). The MessageQueueCacheDependency class could come in handy if you’re building the backbone of a distributed system and you need to pass messages between components on different computers to notify them when certain actions are performed or changes are made.

Before you can create the MessageQueueCacheDependency, you need to add a reference to the System.Messaging.dll and import the System.Messaging namespace where the MessageQueue and Message classes reside. Then you’re ready to build the solution.

In this example, the MessageQueueCacheDependency is able to monitor any queue. When you instantiate the dependency, you supply the queue name (which includes the location information). To perform the monitoring, the MessageQueueCacheDependency fires its private WaitForMessage() method asynchronously. This method waits until a new message is received in the queue, at which point it calls NotifyDependencyChanged() to invalidate the cached item.

Here’s the complete code for the MessageQueueCacheDependency:

public class MessageQueueCacheDependency : CacheDependency

{

// The queue to monitor. private MessageQueue queue;

public MessageQueueCacheDependency(string queueName)

{

queue = new MessageQueue(queueName);

C H A P T E R 1 1 C A C H I N G

423

// Wait for the queue message on another thread. WaitCallback callback = new WaitCallback(WaitForMessage); ThreadPool.QueueUserWorkItem(callback);

}

private void WaitForMessage(object state)

{

//Check your resource here (the polling).

//This blocks until a message is sent to the queue. Message msg = queue.Receive();

//(If you're looking for something specific, you could

//perform a loop and check the Message object here

//before invalidating the cached item.) base.NotifyDependencyChanged(this, EventArgs.Empty);

}

}

To test this, you can use a revised version of the file-dependency testing page shown earlier (see Figure 11-8).

Figure 11-8. Testing a message queue dependency

This page creates a new private cache on the current computer and then adds a new item to the cache with a dependency on that queue:

private string queueName = @".\Private$\TestQueue";

//The leading . represents the current computer.

//The following Private$ indicates it's a private queue for this computer.

//The TestQueue is the queue name (you can modify this part).

protected void Page_Load(object sender, EventArgs e)

{

if (!this.IsPostBack)

{

424C H A P T E R 1 1 C A C H I N G

//Set up the queue. MessageQueue queue;

if (MessageQueue.Exists(queueName))

{

queue = new MessageQueue(queueName);

}

else

{

queue = MessageQueue.Create(@".\Private$\TestQueue");

}

lblInfo.Text += "Creating dependent item...<br />"; Cache.Remove("Item");

MessageQueueCacheDependency dependency = new MessageQueueCacheDependency(queueName);

string item = "Dependent cached item"; lblInfo.Text += "Adding dependent item<br />"; Cache.Insert("Item", item, dependency);

}

}

When you click Send Message, a simple text message is sent to the queue, which will be received almost instantaneously by the custom dependency class:

protected void cmdModify_Click(object sender, EventArgs e)

{

MessageQueue queue = new MessageQueue(queueName);

//(You could send a custom object instead

//of a string.) queue.Send("Invalidate!"); lblInfo.Text += "Message sent<br />";

}

To learn more about Message Queuing, you can refer to Microsoft .NET Distributed Applications (Microsoft Press, 2003).

Summary

In this chapter, you took a detailed look at caching, which is one of ASP.NET’s premier features and one of the most dramatically improved features in ASP.NET 2.0. As a professional ASP.NET programmer, you should design with caching strategies in mind from the beginning. Caching is particularly important when using the data source controls, which can exert a sizeable footprint because they repeat their database queries for every page request.

C H A P T E R 1 2

■ ■ ■

XML

Ever since XML (Extensible Markup Language) first arrived on the scene in the late 1990s, it has been the focus of intense activity and overenthusiastic speculation. Based on nothing but ordinary text, XML offers a means of sharing data between just about any two applications, whether they’re new or old, written in different languages, built by distinct companies, or even hosted on different operating systems. Now that XML has come of age, it’s being steadily integrated into different applications, problem domains, and industries.

Microsoft’s .NET Framework uses XML heavily and gives ASP.NET applications a rich set of features for using and manipulating XML data. In this chapter, you’ll learn how to work with XML in streams and strings. Additionally, you’ll look at the new XML data binding features in .NET 2.0. XML data binding works analogously to data binding with the SqlDataSource and ObjectDataSource controls. It lets you extract XML content from a file and show that data in a bound control, all without requiring you to write a single line of code.

XML CHANGES IN .NET 2.0

.NET 2.0 refines its XML classes but keeps the overall model almost the same. At one point, early in the beta cycle, there were more ambitious plans to make XPathNavigator the new standard for editing XML and to outfit it with a high-powered set of editing features. However, the cost of forcing developers to rework their code was considered too great.

ASP.NET 2.0 does have one innovation when it comes to XML—the new XmlDataSource control. With this control, you can bind to XML data sources just as easily as you bind to databases and data objects. However, this has a few limitations. For example, the XmlDataSource is best suited to XML content in a file, and it doesn’t support two-way binding. For those reasons, you’ll still need to use the XML classes in .NET in many scenarios.

The XmlDataSource control is described in the “XML Data Binding” section later in this chapter.

425

426 C H A P T E R 1 2 X M L

When Does Using XML Make Sense?

The question that every new ASP.NET developer asks (and many XML proponents don’t answer) is, when does it make sense to use XML in an ASP.NET web application? It makes sense in a few core scenarios:

You need to manipulate data that’s already stored in XML. This situation might occur if you want to exchange data with an existing application that uses a specific flavor of XML.

You want to use XML to store your data and open the possibilities of future integration. Because you use XML, you know other third-party applications can be designed to read this data in the future.

You want to use a technology that depends on XML. For example, web services (discussed in Part 6) use various standards that are all based on XML.

Many ASP.NET features use XML behind the scenes. For example, web services use a higherlevel model that’s built on top of the XML infrastructure. You don’t need to directly manipulate XML to use web services—instead, you can work through an abstraction of objects. Similarly, you don’t need to manipulate XML to read information from ASP.NET configuration files, use the DataSet, or rely on other .NET Framework features that have XML underpinnings. In all these situations, XML is quietly at work, and you gain the benefits of XML without needing to deal with it by hand.

XML makes the most sense in application integration scenarios. However, there’s no reason you can’t use an XML format to store your own proprietary data. If you do, you’ll gain a few minor conveniences, such as the ability to use .NET classes to read XML data from a file. When storing complex, highly structured data, the convenience of using these classes rather than designing your own custom format and writing your own file-parsing logic is significant. It will also make it easier for other developers to understand the system you’ve created and to reuse or enhance your work.

Note One of the most important concepts developers must understand is that there are two decisions when storing data—choosing the way data will be structured (the logical format) and choosing the way data will be stored (the physical data store). XML is a choice of format, not a choice of storage. This means if you decide to store data in an XML format, you still need to decide whether that XML will be inserted into a database field, inserted into a file, or just kept in memory in a string or some other type of object.

An Introduction to XML

In its simplest form, the XML specification is a set of guidelines, defined by the W3C (World Wide Web Consortium), for describing structured data in plain text. Like HTML, XML is a markup language based on tags within angled brackets. As with HTML, the textual nature of XML makes the data highly portable and broadly deployable. In addition, you can create and edit XML documents in any standard text editor.

Unlike HTML, XML does not have a fixed set of tags. Instead, XML is a metalanguage that allows for the creation of other markup languages. In other words, XML sets out a few simple rules for naming and ordering elements, and you create your own data format with your own custom elements.

C H A P T E R 1 2 X M L

427

For example, the following document shows a custom XML format that stores a product catalog. It starts with some generic product catalog information, followed by a product list with itemized information about two products.

<?xml version="1.0" ?> <productCatalog>

<catalogName>Acme Fall 2005 Catalog</catalogName> <expiryDate>2006-01-01</expiryDate>

<products>

<product id="1001">

<productName>Magic Ring</productName> <productPrice>342.10</productPrice>

</product> <product id="1002">

<productName>Flying Carpet</productName> <productPrice>982.99</productPrice>

</product>

</products>

</productCatalog>

This example uses elements such as <productCatalog>, <product>, and <catalogName> to indicate the document structure. However, you’re free to use whatever element names describe your data best.

It’s because of this flexibility that XML has become extremely successful. Of course, flexibility also has drawbacks. Because XML doesn’t define any standard data formats, it’s up to you to create data formats that represent product catalogs, invoices, customer lists, and so on. Different companies can easily store similar data using completely different tag names and structures. And even though any application can parse XML data, the writer and the reader of that data still need to agree on a common set of tags and structure in order for the reader to be able to interpret that data and extract meaningful information.

Usually, third-party organizations define standards for particular problem domains and industries. For example, if you need to store a mathematical equation in XML, you’ll probably choose the MathML format, which is an XML-based format that defines a specific set of tags and a specific structure. Similarly, hundreds more standard XML formats exist for real estate listings, music notation, legal documents, patient records, vector graphics, and much more. Creating a robust, usable XML format takes some experience, so it’s always best to use a standardized, agreed-upon, XMLbased markup language when possible.

The Advantages of XML

When XML was first introduced, its success was partly due to its simplicity. The rules of XML are much shorter and simpler than the rules of its predecessor, SGML (Standard Generalized Markup Language), and simple XML documents are human-readable. However, in the intervening years many other supporting standards have been added to the XML mix, and as a result, using XML in a professional application isn’t simple at all.

Note Although XML is human-readable in theory, it’s often difficult to understand complex documents, and only computer applications, not developers, can read many types of XML.