My musings about .NET and what not

How to Prevent Profile Properties From Updating a User's Last Activity Date

You may have noticed that in an ASP.NET application, whenever you access a property in a user's profile, the last activity date is automatically updated for that user. This may not the behavior you want, especially when you're dealing with users who are offline. Here's why it happens, and how to alter this default behavior.

Let's say you need to populate some web controls with values from a user's profile:

protected void Page_Load(object sender, EventArgs e)
{
   if (!this.IsPostBack)
   {
      txtFirstName.Text = this.Profile.FirstName;
      txtLastName.Text = this.Profile.LastName;
   }
}

In this code, by simply reading these profile properties, you're updating the last activity date for this user (more specifically, the value of the LastActivityDate column of the user's record in the aspnet_Users table is set to the current date and time). This isn't really a problem, as long as you're only dealing with a currently online user, as in the above example.

The Problem

But let's say you have an administration page that allows you to access profiles of offline users, like this:

public string UserName {get; set;}
 
protected void Page_Load(object sender, EventArgs e)
{
   if (!this.IsPostBack)
   {
      ProfileCommon profile = this.Profile;
      if (this.UserName.Length > 0)
         profile = this.Profile.GetProfile(this.UserName);
 
      txtFirstName.Text = profile.FirstName;
      txtLastName.Text = profile.LastName;
   }
}

The last activity date of this offline user will be updated too. In this case, the LastActivityDate column in aspnet_Users essentially means the last time the user's record was accessed, whether that user is online or not -- at least as far as ASP.NET is concerned. This may not be the desired behavior in your particular application, especially if you use methods like this:

lblOnlineUsers.Text = Membership.GetNumberOfUsersOnline().ToString();

The Membership.GetNumberOfUsersOnline method returns the number of users where the last activity date is greater than the current time less the value of UserIsOnlineTimeWindow, which by default is 15 minutes (this can be altered in the configuration file). You can see that if your application accesses profile properties for offline users, this number won't be accurate, since GetNumberOfUsersOnline uses LastActivityDate to derive its value. Any offline users whose profile properties were accessed within the time window would be counted as "online" by this method, even though they weren't.

How ASP.NET Accesses Profile Properties

When an application in which user profiles are enabled is started, ASP.NET creates a new class of type ProfileCommon, which inherits from the ProfileBase class. An instance of this class is used as the value of the Profile object.

Properties are added to this automatically-generated ProfileCommon class for each property defined in the profile configuration section. These properties call the GetPropertyValue method of ProfileCommon to retrieve untyped values from the ProfileProvider. The get accessor casts the untyped value as the specified type, then returns the property's value. This mechanism provides strong-typing for the Profile object. 

public class ProfileCommon : System.Web.Profile.ProfileBase {
 
   public virtual string LastName {
      get {
          return ((string)(this.GetPropertyValue("LastName")));
      }
      set {
          this.SetPropertyValue("LastName", value);
      }
   }
    
   public virtual string FirstName {
      get {
          return ((string)(this.GetPropertyValue("FirstName")));
      }
      set {
          this.SetPropertyValue("FirstName", value);
      }
   }
 
   public virtual ProfileCommon GetProfile(string username) {
      return ((ProfileCommon)(ProfileBase.Create(username)));
   }
}

The Solution

Okay, let's review what we have so far. A reference in code to any profile property generates a call to ProfileCommon.GetPropertyValue. This, in turn, generates a call to the ProfileProvider GetPropertyValues method.

The default profile provider for ASP.NET is of type System.Web.Profile.SqlProfileProvider, which as we see below provides access to profile data by running the aspnet_Profile_GetProperties stored procedure.

private void GetPropertyValuesFromDatabase(string userName, SettingsPropertyValueCollection svc)
{
// snip...
   try
   {
      connection = SqlConnectionHelper.GetConnection(this._sqlConnectionString, true);
      this.CheckSchemaVersion(connection.Connection);
      SqlCommand command = new SqlCommand("dbo.aspnet_Profile_GetProperties", connection.Connection);
      command.CommandTimeout = this.CommandTimeout;
      command.CommandType = CommandType.StoredProcedure;
      command.Parameters.Add(this.CreateInputParam("@ApplicationName", SqlDbType.NVarChar, this.ApplicationName));
      command.Parameters.Add(this.CreateInputParam("@UserName", SqlDbType.NVarChar, userName));
      command.Parameters.Add(this.CreateInputParam("@CurrentTimeUtc", SqlDbType.DateTime, DateTime.UtcNow));
      reader = command.ExecuteReader(CommandBehavior.SingleRow);
   // snip...
   }
// snip..
}

Below is the code for aspnet_Profile_GetProperties:

   1: ALTER PROCEDURE dbo.aspnet_Profile_GetProperties
   2:     @ApplicationName      nvarchar(256),
   3:     @UserName             nvarchar(256),
   4:     @CurrentTimeUtc       datetime
   5: AS
   6: BEGIN
   7:     DECLARE @ApplicationId uniqueidentifier
   8:     SELECT  @ApplicationId = NULL
   9:     SELECT  @ApplicationId = ApplicationId FROM dbo.aspnet_Applications WHERE LOWER(@ApplicationName) = LoweredApplicationName
  10:     IF (@ApplicationId IS NULL)
  11:         RETURN
  12:  
  13:     DECLARE @UserId uniqueidentifier
  14:     SELECT  @UserId = NULL
  15:  
  16:     SELECT @UserId = UserId
  17:     FROM   dbo.aspnet_Users
  18:     WHERE  ApplicationId = @ApplicationId AND LoweredUserName = LOWER(@UserName)
  19:  
  20:     IF (@UserId IS NULL)
  21:         RETURN
  22:     SELECT TOP 1 PropertyNames, PropertyValuesString, PropertyValuesBinary
  23:     FROM         dbo.aspnet_Profile
  24:     WHERE        UserId = @UserId
  25:  
  26:     IF (@@ROWCOUNT > 0)
  27:     BEGIN
  28:         UPDATE dbo.aspnet_Users
  29:         SET    [email protected]
  30:         WHERE  UserId = @UserId
  31:     END
  32: END

You can see in the last part of this stored procedure (lines 26 - 31), if a profile is found for the specified user, LastActivityDate is updated. You can alter this behavior by simply commenting out or deleting these lines.

/* IF (@@ROWCOUNT > 0)
    BEGIN
        UPDATE dbo.aspnet_Users
        SET    [email protected]
        WHERE  UserId = @UserId
    END */

That way, the last activity date of a user won't be updated when you access that user's profile from your code.

Subscribe to this blog for more cool content like this!

kick it on DotNetKicks.com

shout it on DotNetShoutOut.com

vote it on WebDevVote.com

Bookmark / Share

    » Similar Posts

    1. Getting Strongly Typed Profile Properties From a Class Library
    2. Managing Anonymous Users For Better Site Performance
    3. ASP.NET Profiles in Web Application Projects

    » Trackbacks & Pingbacks

    1. You've been kicked (a good thing) - Trackback from DotNetKicks.com

      How to Prevent Profile Properties From Updating a User's Last Activity — December 29, 2008 5:57 PM
    Trackback link for this post:
    http://leedumond.com/trackback.ashx?id=36

    » Comments

    1. Chuck avatar

      Hey thanks for this. This helps me out. It solved my dilemma that I posted at asp.net forum at forums.asp.net/.../3411421.aspx

      Chuck — September 17, 2009 2:16 PM

    » Leave a Comment