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!
You've been kicked (a good thing) - Trackback from DotNetKicks.com