:::: MENU ::::

Saturday, August 30, 2008

Here is a little trick you can use to spice up your asp.net registration pages. I will use ASP.NET AJAX to inform the user whether the username they have entered is available. Rather than use the UpdatePanel, I will slim down the amount of data going over the wire for each ajax request. I will accomplish this using the PageMethods feature.

This tutorial assumes that you have a working knowledge of how to start an ASP.NET AJAX web application, drop a script manager control onto your form, and use the CreateUser wizard control, or another method of registering users.  A little JavaScript knowledge will help too.

In order to use PageMethods, I will need to set the EnablePageMethods property to true on my ScriptManager.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="True" />

My registration page is called register.aspx. I will create a static method in the code behind for this page. The method will check to see if a username exists, and return a Boolean indicating whether the username is available or not.

[WebMethod]
public static bool IsUserAvailable(string username) 
{
    MembershipUser usr = Membership.GetUser(username);
     return (usr == null);
}

The WebMethod attribute is needed to mark this as a method that can be executed from Javascript.

Now I will go ahead and create some markup create some markup for my username field on your registration page. Something like this should do.

<asp:Label ID="lblUserName" runat="server" AssociatedControlID="UserName">Desired Username</asp:Label>
<asp:TextBox ID="UserName" runat="server" CssClass="txt" onkeyup="usernameChecker(this.value);" />
<span id="spanAvailability"></span>

There are a couple of things to take note of here. The onkeyup javascript event will be fired every keypress, and will be used to check the availability of the username entered.  Also take note of the span with an id of spanAvailability. The JavaScript will use this span to write output letting the user know whether the username is available.

Here's the last part, the Javascript that wires everything together.  I will insert this at the bottom of my register.aspx page:

<script type="text/javascript">
var usernameCheckerTimer;
var spanAvailability = $get("spanAvailability");
 
function usernameChecker(username) 
{
    clearTimeout(usernameCheckerTimer);
    if (username.length == 0)
        spanAvailability.innerHTML = "";
    else
    {
        spanAvailability.innerHTML = "<span style='color: #ccc;'>checking...</span>";
        usernameCheckerTimer = setTimeout("checkUsernameUsage('" + username + "');", 750);
    }
}
 
function checkUsernameUsage(username) 
{
    // initiate the ajax pagemethod call
    // upon completion, the OnSucceded callback will be executed
    PageMethods.IsUserAvailable(username, OnSucceeded);
}
 
// Callback function invoked on successful completion of the page method.
function OnSucceeded(result, userContext, methodName) 
{
    if (methodName == "IsUserAvailable")
    {
        if (result == true)
            spanAvailability.innerHTML = "<span style='color: DarkGreen;'>Available</span>";
        else
            spanAvailability.innerHTML = "<span style='color: Red;'>Unavailable</span>";
    }
}
</script>

This effect is pretty easy to do, and adds a nice value to the user experience.  I hope you are able to find use for this technique in your applications.

Update: Download a sample project here

 

I've been trying to use labels with the AssociatedControlID property more often.  If you aren't familiar with this property, it changes the behavior of the Label control.  Rather than rendering the typical <span> html element, the Label control will now render a <label> html element with the for property set.  This links the label to another server control.  This is good for accessibility, allows hotkeys to be set for the associated control, and also allows the user to click on the label in order to give focus to the associated control. Syntax is as follows

<asp:Label Text="<u>S</u>earch" AssociatedControlID="txtSearch" AccessKey="s" ID="lblSearch" runat="server" />

<asp:TextBox ID="txtSearch" runat="server" />

In this example, pressing Alt+S (or Ctrl+Alt+S in Firefox), or clicking on the label will give focus to the textbox.

But what if the control you want to target is actually inside of an user control.  This is the problem that I faced recently.  Lets say you have an instance of an user control with the id EmailInput1.  This user control has a textbox txtEmail.  To target this textbox from your aspx page, set the AssociatedControlID property to "EmailInput1:txtEmail".  Just parent -> child with a colon separator.  Easy enough!

 

Don't you hate it when you are idle on a web app for just a bit too long, and your membership session gets timed out without warning?  Your users sure do!  Often times the user may not even realize he has been timed out until he finishes completing a form, and clicks the submit button.  At which time not only has the user lost his session, he has lost his data too.

In this post, I will provide a custom control, complete with source code, which will improve this experience for your users by displaying a nice message to inform them that their session is about to expire.  It will do this without any popups or postbacks by using the ASP.NET AJAX frameworkDownload the control, and source code, here.

timeout1

Implementation is easy. Just add the control to your Visual Studio toolbox, and drag it on to your master page wherever you would like the message to appear. Set the properties to your situation and enter markup into the template section as seen here:

<tsc:Timeout 
    ID="Timeout1" 
    runat="server"
    Enabled="true"
    AboutToTimeoutMinutes="28" 
    TimeoutMinutes="30" 
    TimeoutURL="~/Default.aspx?timeout=true"
    CssClass="timeout" 
    DisplayButton="false" 
    ButtonCssClass="btn" 
    ButtonText="Continue My Session!"
    >
    <Template>
        For your safety and protection, your session is about to expire.  If you wish to continue your session, please click here.
    </Template>
</tsc:Timeout>

This control needs the ASP.NET AJAX framework to be included in your project.  It will also require a Script Manager control.  Following is a description of each property

  • Enabled - Whether the control is enabled or not.  I typically place the control on a master page, and set the enabled property in code behind based on whether the user is logged in or not.  If they are not logged in, they can't very well timeout can they?
  • AboutToTimeoutMinutes - Idle minutes until the user is informed that their session is about to time out.
  • TimeoutMinutes - Idle minutes until the membership session will time out.  This should match the forms authentication timeout property in the web.config.
  • TimeoutURL - URL to redirect the user, in the event of a membership session timeout
  • CssClass - CSS class name applied to the control
  • DisplayButton - Whether the button is visible.  If this is false, the entire control is clickable by the user to restore their session.
  • ButtonCssClass - CSS class name applied to the button control
  • ButtonText - The text to be displayed on the button
  • Template - Enter your html or asp.net markup here.  This is the message that will be displayed when the user's membership session is about to timeout.

Enjoy, and please post here if you see any room for improvement in the source.  This is my first attempt at a AJAX enabled custom control.  Download the control, and source code, here.

More

Friday, August 29, 2008

You've installed SQL Server 2008 on a clean machine and your vb script that uses DMO doesn't work. You're asking yourself: What the heck happened? Did those goofballs over @ MS hose me? Deprecation of DMO was announced in SQL Server 2005, but SQL Server 2005 still installed DMO. In SQL Server 2008 we've taken the next step down the deprecation path - we ship DMO but don't automatically install it. There are two places to get DMO:

Generally it's best to go to http://www.microsoft.com/downloads, search for "SQL Server Feature Pack" and sort by Release Date to find the latest version.

With the addition of PowerShell support in SQL Server 2008 we highly encourage you to move off of DMO and start using SMO.

 

In this tip, Stephen Walther demonstrate how you can create new LINQ to SQL extension methods that enable you to dramatically reduce the amount of code that you are required to write for typical data access scenarios.

By taking advantage of LINQ to SQL, you can dramatically reduce the amount of code that you need to write to access a database. LINQ to SQL has given me back several weeks of my life that I would have otherwise wasted in writing tedious ADO.NET code. In short, I am a fan.

However, LINQ to SQL is a general data access technology. It is not specific to ASP.NET MVC. You can use LINQ to SQL when building Windows Forms applications, console applications, or just about any other type of Microsoft application. For this reason, it is not optimized for ASP.NET MVC. You are required to write more lines of code to access a database within an ASP.NET MVC application than strictly necessary.

In this tip, I streamline (optimize? reinterpret? re-imagine?) LINQ to SQL for ASP.NET MVC. I show how you can add extension methods to LINQ to SQL that enables you to reduce the amount of code that you must write for common data access scenarios to a single line of code.

For example, normally you need to write the following code to add a new Movie database record to the database:

  1. var movieToAdd = new Movie();  
  2. movieToAdd.Title = title;  
  3. movieToAdd.Director = director;  
  4. movieToAdd.DateReleased = dateReleased;  
  5. _dataContext.Movies.InsertOnSubmit(movieToAdd);  
  6. _dataContext.SubmitChanges();  
var movieToAdd = new Movie();
movieToAdd.Title = title;
movieToAdd.Director = director;
movieToAdd.DateReleased = dateReleased;
_dataContext.Movies.InsertOnSubmit(movieToAdd);
_dataContext.SubmitChanges();

You need four lines of code to create a new Movie and set its properties. You need an additional two lines of code to add the new Movie to the database (InsertOnSubmit() and SubmitChanges()).

After we add our extension methods, we can do the same thing with the following line of code:

_dataContext.Insert<Movie>(formParams);

With this tip, you can save another few weeks of your life for more useful things like seeing movies, going to the park, baking cakes, building new MVC view engines, and so on.

Using the LINQ to SQL Extension Methods

You can use the LINQ to SQL extension methods with an ASP.NET MVC project by following these three steps:

1) Download the LinqToSqlExtensions project attached to the end of this blog entry. After downloading the file, right-click the file and select Properties. Next, click the Unblock button. After you unblock the file, unzip the file.

2) Within an ASP.NET MVC application, add a reference to the LinqToSqlExtensions assembly. Select the menu option Project, Add Reference and browse to the LinqToSqlExtensions.dll assembly located in the \LinqToSqlExtensions\Bin\Debug folder.

3) Add a using LinqToSqlExtensions statement to any controller in which you want to use the extensions.

The LInqToSqlExtensions project contains a single class, named DataContextExtensions, which contain the LINQ to SQL extensions. Unfortunately, the source for this class is a little too long to include in this blog entry.

The DataContextExtensions class adds the following methods to the DataContext:

· Select() – Enables you to select all records from a database table

· Get() – Enables you to get a single record from a database table

· Save() – Enables you to either insert a new record or update an existing record

· Insert() – Enables you to insert a new record

· Update() – Enables you to update an existing record

· Delete() – Enables you to delete an existing record

The Save(), Insert(), and Update() methods accept a NameValueCollection of form parameters. For example, you insert a new record with the following line of code:

_dataContext.Insert<Movie>(formParams);

There are two ways that you can get a reference to the form parameters in a controller action. First, you can use the Request.Form collection like this:

  1. public ActionResult Insert()  
  2. {  
  3.     _dataContext.Insert<Movie>(Request.Form);  
  4.     return RedirectToAction("Index");  
  5. }  
        public ActionResult Insert()
        {
            _dataContext.Insert<Movie>(Request.Form);
            return RedirectToAction("Index");
        }

Alternatively, you create a custom action invoker and pass the form parameters to the action method like this:

  1. public ActionResult Insert(NameValueCollection formParams)  
  2. {  
  3.     _dataContext.Insert<Movie>(formParams);  
  4.     return RedirectToAction("Index");  
  5. }  
        public ActionResult Insert(NameValueCollection formParams)
        {
            _dataContext.Insert<Movie>(formParams);
            return RedirectToAction("Index");
        }

I discuss creating a custom action invoker in the following tip:

http://weblogs.asp.net/stephenwalther/archive/2008/07/11/asp-net-mvc-tip-18-parameterize-the-http-context.aspx

I prefer this second method because it is more testable. If you create a custom action invoker then you can test an action method in your unit tests by creating a new NameValueCollection and passing it to the controller action. I use this second method in the rest of the code samples in this tip.

Creating a Controller with the Extension Methods

I’m going to show you two ways that you can use the extensions methods. First, I’m going to create a controller that contains separate actions for displaying, inserting, updating, and deleting Movie database records. Next, I’m going to show you how you can combine the Insert() and Update() actions into a single Save() action.

The Home controller in Listing 1 contains separate Insert() and Update() methods. This controller can be used when building a Movie database application. It contains all of the methods that you need to manage a simple Movie database (see Figure 1).

Figure 1 – Movie Database Application

Listing 1 -- \Controllers\HomeController.cs

  1. using System.Web.Mvc;  
  2.   
  3. using LinqToSqlExtensions;  
  4. using Tip38.Models;  
  5. using System.Collections.Specialized;  
  6.   
  7. namespace Tip38.Controllers  
  8. {  
  9.     public class HomeController : Controller  
  10.     {  
  11.         private MovieDataContext _dataContext;  
  12.   
  13.         public HomeController()  
  14.         {  
  15.             _dataContext = new MovieDataContext();  
  16.         }  
  17.   
  18.         public ActionResult Index()  
  19.         {  
  20.             return View("Index", _dataContext.Select<Movie>());  
  21.         }  
  22.   
  23.         public ActionResult Details(int id)  
  24.         {  
  25.             return View("Details", _dataContext.Get<Movie>(id));  
  26.         }  
  27.   
  28.         public ActionResult Edit(int id)  
  29.         {  
  30.             return View("Edit", _dataContext.Get<Movie>(id));  
  31.         }  
  32.   
  33.         public ActionResult Update(int id, NameValueCollection formParams)  
  34.         {  
  35.             _dataContext.Update<Movie>(formParams, id);  
  36.             return RedirectToAction("Index");  
  37.         }  
  38.   
  39.         public ActionResult Delete(int id)  
  40.         {  
  41.             _dataContext.Delete<Movie>(id);  
  42.             return RedirectToAction("Index");  
  43.         }  
  44.   
  45.         public ActionResult Create()  
  46.         {  
  47.             return View("Create");  
  48.         }  
  49.   
  50.         public ActionResult Insert(NameValueCollection formParams)  
  51.         {  
  52.             _dataContext.Insert<Movie>(formParams);  
  53.             return RedirectToAction("Index");  
  54.         }  
  55.   
  56.     }  
  57. }  
using System.Web.Mvc;
 
using LinqToSqlExtensions;
using Tip38.Models;
using System.Collections.Specialized;
 
namespace Tip38.Controllers
{
    public class HomeController : Controller
    {
        private MovieDataContext _dataContext;
 
        public HomeController()
        {
            _dataContext = new MovieDataContext();
        }
 
        public ActionResult Index()
        {
            return View("Index", _dataContext.Select<Movie>());
        }
 
        public ActionResult Details(int id)
        {
            return View("Details", _dataContext.Get<Movie>(id));
        }
 
        public ActionResult Edit(int id)
        {
            return View("Edit", _dataContext.Get<Movie>(id));
        }
 
        public ActionResult Update(int id, NameValueCollection formParams)
        {
            _dataContext.Update<Movie>(formParams, id);
            return RedirectToAction("Index");
        }
 
        public ActionResult Delete(int id)
        {
            _dataContext.Delete<Movie>(id);
            return RedirectToAction("Index");
        }
 
        public ActionResult Create()
        {
            return View("Create");
        }
 
        public ActionResult Insert(NameValueCollection formParams)
        {
            _dataContext.Insert<Movie>(formParams);
            return RedirectToAction("Index");
        }
 
    }
}

Notice that the data access code for each action has been condensed to a single line. The LINQ to SQL extension methods perform all of the work behind the scenes.

If you prefer, you can combine the Insert() and Update() methods into a single action. I took this approach when writing the Movie controller in Listing 2.

Listing 2 – MovieController.cs

  1. using System.Web.Mvc;  
  2.   
  3. using LinqToSqlExtensions;  
  4. using Tip38.Models;  
  5. using System.Collections.Specialized;  
  6.   
  7. namespace Tip38.Controllers  
  8. {  
  9.     public class MovieController : Controller  
  10.     {  
  11.         private MovieDataContext _dataContext;  
  12.   
  13.         public MovieController()  
  14.         {  
  15.             _dataContext = new MovieDataContext();  
  16.         }  
  17.   
  18.         public ActionResult Index()  
  19.         {  
  20.             return View("Index", _dataContext.Select<Movie>());  
  21.         }  
  22.   
  23.         public ActionResult Edit(int? id)  
  24.         {  
  25.             return View("Edit", _dataContext.Get<Movie>(id));  
  26.         }  
  27.   
  28.         public ActionResult Update(int? id, NameValueCollection formParams)  
  29.         {  
  30.             _dataContext.Save<Movie>(formParams, id);  
  31.             return RedirectToAction("Index");  
  32.         }  
  33.   
  34.   
  35.     }  
  36. }  
using System.Web.Mvc;
 
using LinqToSqlExtensions;
using Tip38.Models;
using System.Collections.Specialized;
 
namespace Tip38.Controllers
{
    public class MovieController : Controller
    {
        private MovieDataContext _dataContext;
 
        public MovieController()
        {
            _dataContext = new MovieDataContext();
        }
 
        public ActionResult Index()
        {
            return View("Index", _dataContext.Select<Movie>());
        }
 
        public ActionResult Edit(int? id)
        {
            return View("Edit", _dataContext.Get<Movie>(id));
        }
 
        public ActionResult Update(int? id, NameValueCollection formParams)
        {
            _dataContext.Save<Movie>(formParams, id);
            return RedirectToAction("Index");
        }
 
 
    }
}

The controller in Listing 2 uses the same view to display an XHTML form for inserting and updating a Movie database record (see Figure 2). This form is displayed by the Edit() controller action. The form is submitted to the Update() controller action. The Update() action calls the Save() method to either insert or update a Movie record.

Here’s how the Save() method works. If you call the Save() method with an Id parameter with a value of 0 or null, then the Save() method calls the Insert() method. Otherwise, the Save() method performs an update on the record with the matching Id.

Figure 2 – \Views\Movie\Edit.aspx

Summary

Never forget that the ASP.NET MVC framework is a framework. You always have the option of customizing it to fit your particular needs. In this tip, I demonstrated how you can add extension methods to LINQ to SQL to dramatically reduce the amount of data access code that you are required to write in typical data access scenarios. Save yourself some time! Use this tip!

Download the Code

 

I was in the middle of making so nice templates in order to generate some test data.  I wanted to create Firstnames, Lastnames and Titles but also have them specialize by sex.  So I have methods including GetMaleFirstName, GetFemaleFirstName and GetLastName.  I have three xml files which have the data in and it occurred to me that I have not yet touched on LINQ to XML, so that is what I did.  Of my collections I wanted to return a random element and also ensuring that whatever method I create I do not want to couple it , hence the reason I thought of an extension method for the IEnumerable<T> type. So the code is the following:

    public static class Extenders

    {

        public static T GetSingleRandom<T>(this IEnumerable<T> target)

        {

            Random r = new Random(DateTime.Now.Millisecond);

            int position = r.Next(target.Count<T>());

            return target.ElementAt<T>(position);

        }

    }

You shouldn't be creating a random generator each call. Create the Random as a static property (and the default constructor would be fine).

  public static class Extenders

   {

       public static Random r = new Random();

       public static T GetSingleRandom<T>(this IEnumerable<T> target)

       {

           int position = r.Next(target.Count<T>());

           return target.ElementAt<T>(position);

       }

   }

 

This now allows me to return a single, random, element from any IEnumerable collection.  A couple of simple tests are below.  The first one being on a simple list and the second on a XML File.

Example 1

            //Example 1

            //Simple List

 

            IList<string> names = new List<string>();

            names.Add("Andy");

            names.Add("Jamie");

            names.Add("John");

            names.Add("David");

            names.Add("Jo");

 

            Console.WriteLine(names.GetSingleRandom<string>());

 

Example 2

 

        static void Main(string[] args)

        {

            //Example 2

            //An XML Document using LINQ to XML

 

            string s = RandomNameAccess(@"C:\Firstnames.xml");

            Console.WriteLine(s);

            Console.ReadLine();

        }

 

        private static string RandomNameAccess(string fileUrl)

        {

            XDocument namesDocument = XDocument.Load(fileUrl);

 

            var name = from c in namesDocument.Descendants("Names")

                       select c.Elements().GetSingleRandom<XElement>().Value;

 

            return name.Single<string>();

        }

I know that Visual Studio Team Edition for Database Developer has a Test Data Generator but I cannot seem to get it enabled on my version. 

 

Steve Smith, owner of ASP Alliance and Lake Quincy Media joins us today to teach us about some hidden gems in ASP.NET caching and performance. Steve’s expertise in this area comes from first-hand experience as Lake Quincy’s ad system serves over 60 requests per second and handles over 150 million requests per month. Steve is an ASP Insider, Regional Director, Microsoft MVP, INETA Speaker and fellow book author!


What performance metrics are worth tracking?

  • Requests per second
  • Time to last byte: How long it takes the user to see the page load

Indicators of performance problems include high requests per second, but very long page load times. Alternatively you may notice quick load times, but only for a small number of users. The balance of these two metrics will help you even out the performance of your website.

How do you find bottlenecks?

  • Recognize there is only one bottleneck at a time: The rule of thumb is that there is always one bottleneck – and changing anything else before addressing the bottleneck will not improve performance. Most bottlenecks in web applications are external resources, like a database, web service or the file system.
  • Avoid optimizing too early. Beware of pre-mature optimization as it may cause performance problems. Without the benefit of production environment (large number of users going to your site) you may make implementation mistakes.

  • Measure: Run load tests and profilers to establish benchmarks

Caching Basics

Output Caching

ASP.NET included output caching as a feature from the beginning. Output caching is easy to use and has many different features available.

One feature that many people don’t know about is the control’s ability to vary by browser. This is important because if you have markup optimized for a particular browser and then a user views your site with an un-optimized browser version, your site may look broken. Using the output cache’s vary by browser parameter can help you avoid this problem.

Do you have to be worried about your output cache getting to big?

Yes, but most servers can handle the load that caching will create on the server. Caching saves the rendered markup in memory and then serves it while the cache is still valid, so many cache entries will only be around 5 to 10KB of markup.

ASP.NET’s caching also automatically manages memory so the cache will purge when the server detects memory pressure. Caching uses a least-recently-used (LRU) algorithm to determine which cache entries to purge first. The worst case scenario is that your server will begin to "thrash" alternately caching pages and purging pages upon every request.

Substitution Control

The substitution control gives you a way to display dynamic content on a cached page. The control uses a callback to generate a string to substitute in its place before serving the content to the client. For example if you wanted to display the login information for a user on the screen on a cached page, the substitution control can display the user’s name in the middle of cached content.

Even though the control requires a string, you are not limited to simple strings. You may still use rich UI controls - you would simply need to render the control’s HTML and pass it up to the substitution control.

For an example of how to tell a control to render its HTML, Steve has an example that even comes with a nice extension method that makes getting the markup easy for any control.

The only seeming drawback to using the substitution control is the lack of kernel-level caching support in IIS7.

State Bag Access Pattern Gotchas

Many developers will implement caching by using the state access pattern. While this approach is acceptable there is a gotcha you must be aware of: only make a call to the cache only ONCE during a request. If you make multiple calls to the cache an item may be expired or removed between the first and subsequent calls.

Naïve State Bag Access Pattern

public List List()
{
      List myList;
      if(Cache["customers"] == null)
      {
            myList = DAL.ListCustomers();
            Cache.Insert("customers", mList, null,
      DateTime.Now.AddHours(1), TimeSpan.Zero);
      }
      return (List)Cache["customers"];
}

Correct State Bag Access Pattern

public List List()
{
      string cacheKey = "customers";
      List myList =
            Cache[cacheKey] as List;
      if(myList == null)
      {
            myList = DAL.ListCustomers();
            Cache.Insert(cacheKey, mList, null,
      SiteConfig.CacheDuration, TimeSpan.Zero);
      }
      return myList;
}

Cache API

The caching API lives in the System.Web namespace, but is not limited to a web application or IIS.

Benefits of using the API include:

  • Manages memory itself
  • Supports dependencies based on
    • key
    • time
    • file system
    • databases

Limitations of ASP.NET Caching

The built-in ASP.NET caching is app-domain specific, so caching on a web farm is problematic. If you put something into the cache on server "A" the same cache does not exist on server "B". A project in its infancy from Microsoft, codenamed Velocity, will help address this issue. Products available today that supports distributed cache and session are ScaleOut State Server or Alachisoft NCache.

Write Caching

Write caching is the practice of filling a buffer of commands and sending them to the server in infrequent intervals. A Common practice for using write caching is in logging scenarios. The implementation may include a short-lived session or cache object which is accessed by a separate process powered by a timer which takes whatever has built up in the buffer and then executes the commands against the server.

For a code examples read Steve’s article, Use Write Caching to Optimize High Volume Data Driven Applications. While Steve’s article is relevant for SQL 2000 environments, Jason Follas posed an update to the write caching technique that is optimized for SQL 2005 called, Coding in SQL Server: An Evolution. Jason’s article shows you how to implement write caching which requires less code and performs even faster than Steve’s SQL 2000 example.

Finally, you could go even further and make these write caching reliable by implementing a queuing solution.

Client-Side Caching

Client-side caching includes:

  • Storing data at the browser and then manipulating it with JavaScript
  • Configuring client cache headers will store static resources at the client and proxy servers

Side Effects of Caching: Logging

Web servers log traffic statistics by counting how many times a file is served off the file system. When you implement caching pages are served from memory and not off the server’s file system. Knowing this, you may need to alter your approach to reporting the traffic on your site.

Most of the caching techniques discussed here include storing the rendered HTML of a page or a control, so request to images are still served as normal even on a cached page. Therefore as you begin to make use caching, you may want to include web beacons (or the infamous clear pixel image) to help you track statistics for your site.

Further, logging implemented with custom logic is also affected. Cached pages do not exist as control objects and therefore cannot execute the code-behind while in cache. If you have logging setup on a page the logic will only run the first time and when the cache expires.

Debugging Cached Applications

Debugging cached applications can be hard if you are expecting code to execute that never runs. A few ways you can help debugging are to:

  • Make it easy to turn off caching: Turning off caching in a development environment will allow you to go straight to problem areas
  • An object's rendered HTML is what is cached: Be aware that cached controls exist as the rendered HTML string in the cache and not the original object. If you try to run a method of a cached user control you will encounter an exception

Tips and Tricks

Note: Browsers only make two simultaneous requests per domain. By simply creating extra CNAMEs for images, style sheets and JavaScript files, you can greatly improve the load speed of a page.

Resources