views:

258

answers:

4

I have a strange effect on my site when it is under heavy load.

I randomly get the properties of other users settings. I have my own implementation of the profile system so I guess I can not blame the profile system itself.

I just need a point to start debugging from. I guess there is a cookie-value that maps to an Profile entry somewhere. Is there any chance to see how this mapping works?

Here is my profile provider:

using System;
using System.Text;
using System.Configuration;
using System.Web;
using System.Web.Profile;
using System.Collections;
using System.Collections.Specialized;
using B2CShop.Model;
using log4net;
using System.Collections.Generic;
using System.Diagnostics;
using B2CShop.DAL;
using B2CShop.Model.RepositoryInterfaces;
[assembly: log4net.Config.XmlConfigurator()]
namespace B2CShop.Profile
{
    public class B2CShopProfileProvider : ProfileProvider
    {
        private static readonly ILog _log = LogManager.GetLogger(typeof(B2CShopProfileProvider));
        // Get an instance of the Profile DAL using the ProfileDALFactory
        private static readonly B2CShop.DAL.UserRepository dal = new  B2CShop.DAL.UserRepository();
        // Private members
        private const string ERR_INVALID_PARAMETER = "Invalid Profile parameter:";

        private const string PROFILE_USER = "User";
        private static string applicationName = B2CShop.Model.Configuration.ApplicationConfiguration.MembershipApplicationName;

        /// <summary>
        /// The name of the application using the custom profile provider.
        /// </summary>
        public override string ApplicationName
        {
            get
            {
                return applicationName;
            }
            set
            {
                applicationName = value;
            }
        }


        /// <summary>
        /// Initializes the provider.
        /// </summary>
        /// <param name="name">The friendly name of the provider.</param>
        /// <param name="config">A collection of the name/value pairs representing the provider-specific attributes specified in the configuration for this provider.</param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (string.IsNullOrEmpty(config["description"]))
            {
                config.Remove("description");
                config.Add("description", "B2C Shop  Custom Provider");
            }

            if (string.IsNullOrEmpty(name))
                name = "b2c_shop";

            if (config["applicationName"] != null && !string.IsNullOrEmpty(config["applicationName"].Trim()))
                applicationName = config["applicationName"];

            base.Initialize(name, config);

        }

        /// <summary>
        /// Returns the collection of settings property values for the specified application instance and settings property group.
        /// </summary>
        /// <param name="context">A System.Configuration.SettingsContext describing the current application use.</param>
        /// <param name="collection">A System.Configuration.SettingsPropertyCollection containing the settings property group whose values are to be retrieved.</param>
        /// <returns>A System.Configuration.SettingsPropertyValueCollection containing the values for the specified settings property group.</returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {

            string username = (string)context["UserName"];
            bool isAuthenticated = (bool)context["IsAuthenticated"];
            //if (!isAuthenticated) return null;
            int uniqueID = dal.GetUniqueID(username, isAuthenticated, false, ApplicationName);



            SettingsPropertyValueCollection svc = new SettingsPropertyValueCollection();

            foreach (SettingsProperty prop in collection)
            {
                SettingsPropertyValue pv = new SettingsPropertyValue(prop);

                switch (pv.Property.Name)
                {
                    case PROFILE_USER:
                        if (!String.IsNullOrEmpty(username))
                        {
                            pv.PropertyValue = GetUser(uniqueID);
                        }
                        break;
                    default:
                        throw new ApplicationException(ERR_INVALID_PARAMETER + " name.");
                }

                svc.Add(pv);
            }
            return svc;
        }


        /// <summary>
        /// Sets the values of the specified group of property settings.
        /// </summary>
        /// <param name="context">A System.Configuration.SettingsContext describing the current application usage.</param>
        /// <param name="collection">A System.Configuration.SettingsPropertyValueCollection representing the group of property settings to set.</param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {

            string username = (string)context["UserName"];
            CheckUserName(username);
            bool isAuthenticated = (bool)context["IsAuthenticated"];

            int uniqueID = dal.GetUniqueID(username, isAuthenticated, false, ApplicationName);
            if (uniqueID == 0)
            {
                uniqueID = dal.CreateProfileForUser(username, isAuthenticated, ApplicationName);
            }

            foreach (SettingsPropertyValue pv in collection)
            {

                if (pv.PropertyValue != null)
                {
                    switch (pv.Property.Name)
                    {
                        case PROFILE_USER:
                            SetUser(uniqueID, (UserInfo)pv.PropertyValue);
                            break;
                        default:
                            throw new ApplicationException(ERR_INVALID_PARAMETER + " name.");
                    }
                }
            }

            UpdateActivityDates(username, false);
        }


        // Profile gettters
        // Retrieve UserInfo
        private static UserInfo GetUser(int userID)
        {
            return dal.GetUser(userID);
        }




        // Update account info
        private static void SetUser(int uniqueID, UserInfo user)
        {
            user.UserID = uniqueID;
            dal.SetUser(user);
        }


        // UpdateActivityDates
        // Updates the LastActivityDate and LastUpdatedDate values 
        // when profile properties are accessed by the
        // GetPropertyValues and SetPropertyValues methods. 
        // Passing true as the activityOnly parameter will update
        // only the LastActivityDate.
        private static void UpdateActivityDates(string username, bool activityOnly)
        {
            dal.UpdateActivityDates(username, activityOnly, applicationName);
        }

        /// <summary>
        /// Deletes profile properties and information for the supplied list of profiles.
        /// </summary>
        /// <param name="profiles">A System.Web.Profile.ProfileInfoCollection of information about profiles that are to be deleted.</param>
        /// <returns>The number of profiles deleted from the data source.</returns>
        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {

            int deleteCount = 0;

            foreach (ProfileInfo p in profiles)
                if (DeleteProfile(p.UserName))
                    deleteCount++;

            return deleteCount;
        }

        /// <summary>
        /// Deletes profile properties and information for profiles that match the supplied list of user names.
        /// </summary>
        /// <param name="usernames">A string array of user names for profiles to be deleted.</param>
        /// <returns>The number of profiles deleted from the data source.</returns>
        public override int DeleteProfiles(string[] usernames)
        {

            int deleteCount = 0;

            foreach (string user in usernames)
                if (DeleteProfile(user))
                    deleteCount++;

            return deleteCount;
        }

        // DeleteProfile
        // Deletes profile data from the database for the specified user name.
        private static bool DeleteProfile(string username)
        {
            CheckUserName(username);
            return dal.DeleteAnonymousProfile(username, applicationName);   
        }

        // Verifies user name for sise and comma
        private static void CheckUserName(string userName)
        {
            if (string.IsNullOrEmpty(userName) || userName.Length > 256 || userName.IndexOf(",") > 0)
                throw new ApplicationException(ERR_INVALID_PARAMETER + " user name.");
        }

        /// <summary>
        /// Deletes all user-profile data for profiles in which the last activity date occurred before the specified date.
        /// </summary>
        /// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are deleted.</param>
        /// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
        /// <returns>The number of profiles deleted from the data source.</returns>
        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {

            string[] userArray = new string[0];
             dal.GetInactiveProfiles((int)authenticationOption, userInactiveSinceDate, ApplicationName).CopyTo(userArray, 0);

            return DeleteProfiles(userArray);
        }

        /// <summary>
        /// Retrieves profile information for profiles in which the user name matches the specified user names.
        /// </summary>
        /// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="usernameToMatch">The user name to search for.</param>
        /// <param name="pageIndex">The index of the page of results to return.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
        /// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information
        //     for profiles where the user name matches the supplied usernameToMatch parameter.</returns>
        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {

            CheckParameters(pageIndex, pageSize);

            return GetProfileInfo(authenticationOption, usernameToMatch, null, pageIndex, pageSize, out totalRecords);
        }

        /// <summary>
        /// Retrieves profile information for profiles in which the last activity date occurred on or before the specified date and the user name matches the specified user name.
        /// </summary>
        /// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="usernameToMatch">The user name to search for.</param>
        /// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate value of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
        /// <param name="pageIndex">The index of the page of results to return.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
        /// <returns>A System.Web.Profile.ProfileInfoCollection containing user profile information for inactive profiles where the user name matches the supplied usernameToMatch parameter.</returns> 
        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string usernameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {

            CheckParameters(pageIndex, pageSize);

            return GetProfileInfo(authenticationOption, usernameToMatch, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
        }

        /// <summary>
        /// Retrieves user profile data for all profiles in the data source.
        /// </summary>
        /// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="pageIndex">The index of the page of results to return.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
        /// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information for all profiles in the data source.</returns>      
        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            CheckParameters(pageIndex, pageSize);

            return GetProfileInfo(authenticationOption, null, null, pageIndex, pageSize, out totalRecords);
        }

        /// <summary>
        /// Retrieves user-profile data from the data source for profiles in which the last activity date occurred on or before the specified date.
        /// </summary>
        /// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
        /// <param name="pageIndex">The index of the page of results to return.</param>
        /// <param name="pageSize">The size of the page of results to return.</param>
        /// <param name="totalRecords">When this method returns, contains the total number of profiles.</param>
        /// <returns>A System.Web.Profile.ProfileInfoCollection containing user-profile information about the inactive profiles.</returns>
        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            CheckParameters(pageIndex, pageSize);

            return GetProfileInfo(authenticationOption, null, userInactiveSinceDate, pageIndex, pageSize, out totalRecords);
        }

        /// <summary>
        /// Returns the number of profiles in which the last activity date occurred on or before the specified date.
        /// </summary>
        /// <param name="authenticationOption">One of the System.Web.Profile.ProfileAuthenticationOption values, specifying whether anonymous, authenticated, or both types of profiles are returned.</param>
        /// <param name="userInactiveSinceDate">A System.DateTime that identifies which user profiles are considered inactive. If the System.Web.Profile.ProfileInfo.LastActivityDate of a user profile occurs on or before this date and time, the profile is considered inactive.</param>
        /// <returns>The number of profiles in which the last activity date occurred on or before the specified date.</returns>
        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {

            int inactiveProfiles = 0;

            ProfileInfoCollection profiles = GetProfileInfo(authenticationOption, null, userInactiveSinceDate, 0, 0, out inactiveProfiles);

            return inactiveProfiles;
        }

        //Verifies input parameters for page size and page index.
        private static void CheckParameters(int pageIndex, int pageSize)
        {
            if (pageIndex < 1 || pageSize < 1)
                throw new ApplicationException(ERR_INVALID_PARAMETER + " page index.");
        }

        //GetProfileInfo
        //Retrieves a count of profiles and creates a 
        //ProfileInfoCollection from the profile data in the 
        //database. Called by GetAllProfiles, GetAllInactiveProfiles,
        //FindProfilesByUserName, FindInactiveProfilesByUserName, 
        //and GetNumberOfInactiveProfiles.
        //Specifying a pageIndex of 0 retrieves a count of the results only.
        private static ProfileInfoCollection GetProfileInfo(ProfileAuthenticationOption authenticationOption, string usernameToMatch, object userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {

            ProfileInfoCollection profiles = new ProfileInfoCollection();

            totalRecords = 0;

            // Count profiles only.
            if (pageSize == 0)
                return profiles;

            int counter = 0;
            int startIndex = pageSize * (pageIndex - 1);
            int endIndex = startIndex + pageSize - 1;

            DateTime dt = new DateTime(1900, 1, 1);
            if (userInactiveSinceDate != null)
                dt = (DateTime)userInactiveSinceDate;

            /*
            foreach(CustomProfileInfo profile in dal.GetProfileInfo((int)authenticationOption, usernameToMatch, dt, applicationName, out totalRecords)) {
                if(counter >= startIndex) {
                    ProfileInfo p = new ProfileInfo(profile.UserName, profile.IsAnonymous, profile.LastActivityDate, profile.LastUpdatedDate, 0);
                    profiles.Add(p);
                }

                if(counter >= endIndex) {
                    break;
                }

                counter++;
            }
            */
            return profiles;
        }
    }
}

This is how I use it in the controller:

    public ActionResult AddTyreToCart(CartViewModel model)
    {

        string profile = Request.IsAuthenticated ? Request.AnonymousID : User.Identity.Name;

    }

I would like to debug: How can 2 users who provide different cookies get the same profileid?

EDIT

Here is the code for getuniqueid

public int GetUniqueID(string userName, bool isAuthenticated, bool ignoreAuthenticationType, string appName)
{

  SqlParameter[] parms = {
            new SqlParameter("@Username", SqlDbType.VarChar, 256),
            new SqlParameter("@ApplicationName", SqlDbType.VarChar, 256)};
  parms[0].Value = userName;
  parms[1].Value = appName;

  if (!ignoreAuthenticationType)
  {
    Array.Resize(ref parms, parms.Length + 1);
    parms[2] = new SqlParameter("@IsAnonymous", SqlDbType.Bit) { Value = !isAuthenticated };
  }

  int userID;

  object retVal = null;
  retVal = SqlHelper.ExecuteScalar(ConfigurationManager.ConnectionStrings["SQLOrderB2CConnString"].ConnectionString, CommandType.StoredProcedure, "getProfileUniqueID", parms);

  if (retVal == null)
    userID = CreateProfileForUser(userName, isAuthenticated, appName);
  else
    userID = Convert.ToInt32(retVal);
  return userID;
}

And this is the SP:

CREATE  PROCEDURE [dbo].[getProfileUniqueID]

@Username VarChar( 256),
@ApplicationName VarChar( 256),
@IsAnonymous bit = null
AS
BEGIN

    SET NOCOUNT ON;
/*
[getProfileUniqueID]

created 
08.07.2009  mf

Retrive unique id for current user

*/
SELECT UniqueID FROM dbo.Profiles WHERE Username = @Username 
AND ApplicationName = @ApplicationName
AND IsAnonymous = @IsAnonymous or @IsAnonymous = null

END
+1  A: 

This is a very difficult type of issue to troubleshoot. We had a similar issue in an ASP.NET app (not MVC). I'd suggest that you can start by setting a separate cookie to identify the user, completely bypassing the ASP.NET profile system. You can then check whether the identity in that cookie matches the identity in the profile. If it doesn't then you can 1) log out the user so at least they don't get access to someone else's data and 2) gather some diagnostic information - the complete HTTP request details would be a start. If that doesn't establish any sort of pattern you could start logging all HTTP requests by user to see if there's a pattern in their request history that helps you to reproduce the problem.

In our case the issue ended up being a silly home-grown caching mechanism. The application would try to cache static files (eg. images) in ASP.NET and cookies got cached with them. Under heavy load a user would sometimes load an image with another user's cookies and assume the other user's identity. Your case may be completely different, but this might give you some clue of the type of thing to look for.

Evgeny
Your story looks kind of what I thing is going on here. I saw now that different users (different IP) get the same cookievalue .ASPXANONYMOUS. I think I will open a new question that asks about "FormsAuthentication".
Malcolm Frexner
A: 

The given sources seem to be okay, but i cannot make a profound statement without haveing all the corresponding sourcecode (eg. all used methods of class B2CShop.DAL.UserRepository), perhaps even the database scheme.

First would be to check if perhaps the data in the database is corrupted, so to say that some user may literally own the same unique id or the same username/application/isanonymous combination. This can be excluded if uniqueid is a primary key.

henchman
+1  A: 

I haven't looked at this in depth, but one thing did jump out at me.

In your where clause change

@IsAnonymous = null

to

@IsAnonymous IS NULL

What do you want to happen when @IsAnonymous is null? You might need to add a few parenthesis to the where clause.

SELECT UniqueID FROM dbo.Profiles 
WHERE Username = @Username 
    AND ApplicationName = @ApplicationName
    AND (IsAnonymous = @IsAnonymous OR @IsAnonymous IS NULL)

Do you two users who received the same profile data? If so start there. I would run them through the stored proc first. It would be easy and quick to do.

37Stars
A: 

Whenever these kinds of issue happen... I have seen the following to be the causes...

  1. Cache
  2. Static variables

Can you try to write this code without static variables and check if the issue resolves?

Regards, Rahul

Rahul Soni