Wednesday, January 10, 2007

Amits .Net Articles [ best Practices ]


If you are like me, you are always looking for ways to make your programming life easier - reusable class libraries and controls being a prime target of this effort. One of the things that annoys me about ASP.NET 2.0 is that it offers an ASP.NET menu control, but I just can't bring myself to like it. Sure, it's highly configurable, works with SiteMap xml data, and so on. But, regardless of the amount of tinkering I"ve done, there always seems to be some little annoyance about it's behavior that "gets my goat".

For the kind of development I do, I rarely need one of those fancy-schmancy multilevel javascript callback xpealidocious bodacious scriptabobulous menu thingies. All I need is what I like to call a "MenuStrip". Its a control that lays out items that link to to where you want to go, and it fits with the CSS and design of your page. I don't need dropdowns because if you click an item, you go to that page where I would have another one of these with page-specific items on it, including a "HOME" item that can take you back to the home page (or to where you came from). You can drag the control into a ContentPlaceholder on your page, set an XML source file for the menu items, and you are done.

What I"ve done here, since I couldn't find one already done - that I liked, is to create such an animal, with these features:

1) The CSS to match my page layout is inline in the ASCX page portion of the control. It's not a lot of CSS, so I'd rather have it "keep it's own". If I ever need to change it for another site, that's easy to do.

2) The control loads the XML to display its contents into a DataSet, and caches it for speed. It also sports a file-based CacheDependency so that if I want to change the items on a particular menu, all I need to do is edit the small Xml File that specifies them. When the file is changed, the Cache Item gets invalidated and the control automatically reloads the modified file, updating my changes instantly.

3) You can specify the name of the Xml file to use as a property of the control - so each MenuStrip on each page knows exactly which Xml file to use for the items it is supposed to display.

Here's an example of what one looks like, minus the page, and I also have a separate page where you can look at images of two different versions:



Here's a link to the bigger pictures.

When you download this solution, I'm going to include a MasterPage and two Default pages with two different versions of the control on them, so everything will integrate nicely in a nice site layout. The original CSS Layout came from a free template for SNews, a PHP CMS application; I've converted it to a nice MasterPage.

Now let's look at the code for the ASCX codebehind:


1 using System;

2 using System.Data;

3 using System.Configuration;

4 using System.Collections;

5 using System.Web;

6 using System.Web.Security;

7 using System.Web.UI;

8 using System.Web.UI.WebControls;

9 using System.Web.UI.WebControls.WebParts;

10 using System.Web.UI.HtmlControls;

11 using System.Data.SqlClient;

12

13 namespace MenuStrip

14 {

15 public partial class ucMenu : System.Web.UI.UserControl

16 {

17 private string menuFile = "Menu.xml";

18

19 public string MenuFile

20 {

21 get { return menuFile; }

22 set { menuFile = value; }

23 }

24

25 protected void Page_Load(object sender, EventArgs e)

26 {

27 PopulateMenu();

28 }

29

30

31 private void PopulateMenu()

32 {

33 DataSet ds=new DataSet();

34 if (Cache[menuFile] != null)

35 {

36 ds = (DataSet)Cache[menuFile];

37 }

38 else

39 {

40 ds.ReadXml(Server.MapPath(menuFile));

41 Cache.Insert(menuFile,ds, new

42 System.Web.Caching.CacheDependency(Server.MapPath(menuFile)));

43

44 }

45 DataTable dt = ds.Tables[0];

46 HyperLink hl = null;

47 string ctrlId = "";

48 for (int i = 0; i < ctrlid = "HyperLink" hl =" (HyperLink)this.FindControl(ctrlId);" navigateurl =" (string)dt.Rows[i][" text =" (string)dt.Rows[i][" prefix =" uc1">

Then, I have the control load the required file into a DataSet, Cache it and set a file-based CacheDependency, and it proceeds to "fill in" the NavigateUrl and Text Properties of a bunch of blank Hyperlink Controls that I have in the HTML Markup portion of the ASCX "Page". The rest is done via CSS. You can have as many "blank" Hyperlink controls in the HTML markup of the ASCX as you think you may have menu-items, controls that aren't populated by the code simply don't show up.

This may be a little different approach than other "menu" thingies that you have seen, I hope you like it. The solution you can download below has two fully working versions of the control, and the items point to actual pages on eggheadcafe.com (we'd like you to stick around while you are experimenting with it).

One last item - this is a WAP (Web Application Project) NOT a "WebSite" project. If you haven't installed the WAP add-in, you will find that the solution does not load. You can find out more about WAP along with a link to the download at ScottGu's Blog here.

Download the Visual Studio 2005 Web Application Project Solution that acompanies this article

--------------------------------------------

Caching Pages and Application Data with Database Dependencies

Here I'll narrow the focus on to two specific implementations of caching -- for both ASP.NET Pages and Application Data -- using Database Dependencies.
Most ASP.NET developers get involved with caching as an afterthought, usually implementing some sort of caching only after their web application is completed. I suggest very strongly that this approach is both wrong and flawed. When you start to design an ASP.NET application and you are grappling with how to retrieve and use any kind of object (database data, a page, a control, or other objects) that is expensive to create, the very first thing on your mind should be, "How can I cache this to speed up operations?". Engineer caching into your apps from the very beginning.

I was aware of caching capabilities very early on in the history of ASP.NET, but I didn't really "get the message" as I've described just above until I sat in one of Rob Howard's ASP.NET presentations at Tech-Ed 2004. Rob demonstrated some advanced caching techniques (including one I've adapted for the SQLite database here) and showed real-time App Center Test results, both with and without caching. When you are in a room full of 600 other goons all of whom are having an epiphany at the same time, the message really sinks in. I hope this article helps to get it across for you, too, or at least helps to motivate further study.

"Caching" is a somewhat ambiguous term in that it doesn't always mean using the ASP.NET Cache class. To me, what caching really means is the ability to store expensive-to-create objects or data, and produce them (or their results) on demand without having to recreate the data. The ADO.NET SqlConnection Pool is an example of caching database connections, but it does not use the ASP.NET Caching classes. The use of WeakReferences is another, for caching expensive objects.

Caching is kind of like a hybrid car, having a set of batteries charged up with exactly what you need, that can deliver your power on demand without having to start the expensive gasoline engine. If the batteries are low or empty, the gas engine starts on demand and not only provides your power, it also recharges the batteries so that you can have more efficient throughput. Caching data for as little as 1 second can have a major effect on site performance, as Rob has so astutely pointed out in his Tech-Ed and other presentations.

In ASP.NET 2.0, the caching infrastructure you have to work with has been appropriately "souped up" to provide lots more flexibility and ease of use. You can cache pages ( ), pages based on Query String or FormField parameters, pages based on browser type and / or version, or even by custom developer-defined strings. You can also cache pages based on database dependencies, and that's what we will examine here.

ASP.NET 1.1 offered a number of ways to cache data with dependencies, but using database dependencies wasn't one of them. In ASP.NET 2.0, we can cache pages based on data in a SQL Server database. This includes SQL Server 7.0, 2000, 2005, MSDE, and SQLExpress.

As with most other cache dependencies, the controlling factor is to be able to determine that cache content should be expired based on the knowledge that some data has changed -- in this case, data in the database.

Caching Pages with SqlDependencies
SQL Server 2005 and Express offer built-in notification events, and neither requires any configuration changes to support this other than adding the element to the web.config file. With SQL Server 7.0, 2000 and MSDE, the ability to determine if data has changed is provided via a polling mechanism. The table AspNet_SqlCacheTablesForChangeNotification contains a row for each table to be monitored. When data in a monitored table changes, a trigger changes the value in the changeId column for that table and the next polling interval picks this up. With these older databases, your database needs to be altered to support these changes.

There are two options for doing this:

1) aspnet_regsql.exe can be run to configure a database for notifications, and to configure a specific table to be monitored:

Database: aspnet_regsql -S -E -d -ed
Table: aspnet_regsql -S -E -d -et -t

2) Using the SqlCacheDependencyAdmin Class.

Simplified Example:

Database: SqlCacheDependencyAdmin.EnableNotifications(connectionString);
Table: SqlCacheDependencyAdmin.EnableNotifications(connectionString, tableName);

There are corresponding Disable methods as well.

When your database is configured for notifications, you add the element to your web.config:









Note that you can override the default pollTime attribute for a specific connectionString in the databases node. The pollTime attribute is in milliseconds. The name attribute of the element in the element defines the name of the database when defining SqlDependencies in your app; it does not have to be the actual database name. The connectionStringName attribute must be set to the name of a connection string stored in the element of your web.config.

With your setup complete, you can cache any page based on a database dependency by adding the <@OutputCache page directive, like so: <%@ OutputCache Duration="100" VaryByParam="none" SqlDependency="Pubs:Books" %>

The SqlDependency attribute is set to the name of the database as defined in web.config "name" attribute, followed by a colon and the name of the table that you want to use as the dependency for the page. Multiple databases and tables can be specified by providing a semicolon-delimited list of database:table pairs.

Caching Application Data with SqlDependencies.
You do not have to cache the entire page with SqlDependencies. You can cache individual controls, or where appropriate, the application data that is used by a control (or the page).

To cache Application Data, you would configure your SQL Server database as described above, and add the element to your web.config the same way as for Page caching.

Once you've configured your SQL Server and sqlCacheDependency web.config element, adding data to the ASP.NET Cache with a SqlCacheDependency is simple. You only need to create an instance of a SqlCacheDependency class, specifiying the database (as in the web.config element) and the table, and pass this instance when adding your data to the Cache:

SqlCacheDependency sqlDep =new SqlCacheDependency("Pubs", "Books");
Context.Cache.Insert("myCacheKey", dataSetBookData, sqlDep);

If you are using SQL Server 2005 or Express, you can create a SqlCacheDependency object by passing a SqlCommand object to the constructor. This provides a more granular approach to define the data being monitored and used for expiring the data from the Cache, because a SqlCommand can contain any stored procedure invocation or CommandText including a WHERE clause. For example, "newest employees":

cmdText = "SELECT FIRSTNAME, LASTNAME, EMPLOYEEID FROM EMPLOYEES WHERE HIREDATE >'09/30/2006'" ;
sqlCommand sqlCmd = new SqlCommand(cmdText, sqlConnection);
SqlCacheDependency sqlDep = new SqlCacheDependency(sqlCmd);
// etc.

The above examples are all quite simplified and do not have any exception handling, which of course, I leave to the expert reader to supply.


Caching DataSources
Finally, you can cache datasources. The XmlDataSource, ObjectDataSource and SqlDataSource classes all offer caching:

dataSource.EnableCaching=true;
dataSource.CacheDuration=5;

Summary
The SqlCacheDependency and related tools are an important tool in your arsenal of techniques to be a more professional ASP.NET developer. Developers should strive to create a mindset that includes using caching, optionally with the SqlCacheDependency tools, from the outset when developing ASP.NET 2.0 applications. The result will not only be more scalable web applications, but the recognition of your peers and the customers who consume your work.



No comments: