CslaProvider being lost with session on a load test

CslaProvider being lost with session on a load test

Old forum URL: forums.lhotka.net/forums/t/9981.aspx


jwright posted on Tuesday, January 25, 2011

We have an ASP.NET MVC 2 CSLA 3.6.2  application. We are using a custom identity and principal objects and modified the Global.asax appropriately to rehydrate Csla.ApplicationContext.User with the custom principal from session. I have attached the code snippets below.

The issue is that when we run a load test against our application (with just 40 concurrent users), some users are getting logged off randomly all the time. We have found that the issue is because the custom principal cannot be rehydrated from session for whatever reason. It may be because session is not available at that time but I do not know the defense mechanisms to put in place and every code sample I have seen does what we have in place below.

Any ideas?

Custom Identity object:

    [Serializable()]

    public class Identity : CslaIdentity

    {

        private static PropertyInfo<int> IdProperty = RegisterProperty<int>(typeof(Identity), new PropertyInfo<int>("Id"));

        private static PropertyInfo<string> FirstNameProperty = RegisterProperty<string>(typeof(Identity), new PropertyInfo<string>("FirstName", Properties.Resources.FirstNamePropertyFriendlyName));

        private static PropertyInfo<string> LastNameProperty = RegisterProperty<string>(typeof(Identity), new PropertyInfo<string>("LastName", Properties.Resources.LastNamePropertyFriendlyName));

        private static PropertyInfo<string> MiddleNameProperty = RegisterProperty<string>(typeof(Identity), new PropertyInfo<string>("MiddleName", Properties.Resources.MiddleNamePropertyFriendlyName));

 

        // TODO: Add is active???

 

        public int Id

        {

            get { return GetProperty<int>(IdProperty); }

        }

 

        public string FirstName

        {

            get { return GetProperty<string>(FirstNameProperty); }

        }

 

        public string LastName

        {

            get { return GetProperty<string>(LastNameProperty); }

        }

 

        public string MiddleName

        {

            get { return GetProperty<string>(MiddleNameProperty); }

        }

 

        public string FullName

        {

            get

            {

                return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}{1}{2}",

                    FirstName,

                    !string.IsNullOrEmpty(MiddleName) ? " " + MiddleName : string.Empty,

                    !string.IsNullOrEmpty(LastName) ? " " + LastName : string.Empty);

            }

        }

 

        private Identity() { /* Require the client to use Factory methods */ }

 

        #region Factory Methods

 

        internal static Identity GetIdentity(string username, string password)

        {

            return DataPortal.Fetch<Identity>(new UsernameCriteria(username, password));

        }

 

        #endregion

 

        #region Data Access Methods

 

        private void DataPortal_Fetch(UsernameCriteria criteria)

        {

            IUserDataService ds = DataServiceContext.Current.UserDataService;

            ds.BeginTransaction();

            ISecurityIdentityDTO user = ds.GetIdentity(criteria.Username);

            IsAuthenticated = user != null && 

                              !string.IsNullOrEmpty(user.Password) && 

                              !string.IsNullOrEmpty(user.Password) &&

                              user.Password.Decrypt() == criteria.Password;

            if (IsAuthenticated)

                Populate(user);

            ds.EndTransaction();

        }

 

        private void Populate(ISecurityIdentityDTO user)

        {

            LoadProperty<int>(IdProperty, user.Id);

            LoadProperty<string>(FirstNameProperty, user.FirstName);

            LoadProperty<string>(MiddleNameProperty, user.MiddleName);

            LoadProperty<string>(LastNameProperty, user.LastName);

            Name = FullName;

 

            // Load roles

            var roleList = new Csla.Core.MobileList<string>();

            foreach (IRoleDTO role in user.Roles)

            {

                roleList.Add(role.Name);

            }

            Roles = roleList;

        }

 

        #endregion

    }

}

 

Custom principal object:

 

[Serializable()]

    public class AdministrationPrincipal : BusinessPrincipalBase

    {

        private AdministrationPrincipal(IIdentity identity) : base(identity) { /* Require the client to use login Factory methods */ }

 

        public static bool Login(string username, string password)

        {

            bool authenticated = SetPrincipal(Security.Identity.GetIdentity(username, password));

            if (authenticated)

            {

                Security.Identity identity = Csla.ApplicationContext.User.Identity as Security.Identity;

                User.RegisterLogin(identity.Id, new Csla.SmartDate(DateTime.Now));

            }

 

            return authenticated;

        }

 

        private static bool SetPrincipal(Security.Identity identity)

        {

            bool authenticated = identity.IsAuthenticated && ((ICheckRoles)identity).IsInRole(Role.User);

            if (authenticated)

            {

                AdministrationPrincipal principal = new AdministrationPrincipal(identity);

                Csla.ApplicationContext.User = principal;

            }

            else

                SetUnauthenticatedPrincipal();

            return authenticated;

        }

 

        private static void SetUnauthenticatedPrincipal()

        {

            Csla.ApplicationContext.User = new UnauthenticatedPrincipal();

        }

 

        public static void Logout()

        {

            SetUnauthenticatedPrincipal();

        }

    }

 

Global.asax:

 

protected void Application_AcquireRequestState(object sender, EventArgs e)

        {

            if (HttpContext.Current.Handler is IRequiresSessionState)

            {

                if (ApplicationContext.AuthenticationType == "Windows")

                    return;

 

                IPrincipal principal;

                try

                {

                    principal = (System.Security.Principal.IPrincipal)HttpContext.Current.Session["CslaPrincipal"];

                }

                catch

                {

                    principal = null;

                }

 

                if (principal == null)

                {

                    if (User.Identity.IsAuthenticated && 

                        User.Identity is FormsIdentity)

                    {

                        // no principal in session, but ASP.NET token

                        // still valid - so sign out ASP.NET

                        FormsAuthentication.SignOut();

                        Response.Redirect(Request.Url.PathAndQuery);

                    }

                    // didn't get a principal from Session, so

                    // set it to an unauthenticted PTPrincipal

                    Model.Security.AdministrationPrincipal.Logout();

                }

                else

                {

                    // use the principal from Session

                    ApplicationContext.User = principal;

 

                }

            }

        }

mattruma replied on Tuesday, January 25, 2011

What version of IIS is this running on? We had isses where the AppPool was timing out before the Session which was making all kinds of weird stuff happen ... I'm in the process of looking at my custom principal/identity objects to see where things might differ ...

jwright replied on Tuesday, January 25, 2011

We are using IIS 7 on Server 2008 R2. Was the app pool issue specific to a version of IIS?

mattruma replied on Tuesday, January 25, 2011

I'll have to check with one of my team members, but it was my understanding it was ... we also tweak our global.asax a little differently ...

       protected void Application_AcquireRequestState(object sender, EventArgs e)
        {
            if (HttpContext.Current.Handler is IRequiresSessionState)
            {
                if (Csla.ApplicationContext.AuthenticationType == "Windows")
                {
                    return;
                }

                IPrincipal principal = null;

                try
                {
                    principal = (IPrincipal)HttpContext.Current.Session["EPIWORXUSER"];
                }
                catch
                {
                    principal = null;
                }

                if (principal == null)
                {
                    if (this.User.Identity.IsAuthenticated
                        && this.User.Identity is FormsIdentity)
                    {
                        BusinessPrincipal.LoadPrincipal(this.User.Identity.Name);

                        HttpContext.Current.Session["EPIWORXUSER"] = Csla.ApplicationContext.User;

                        this.Response.Redirect(this.Request.Url.PathAndQuery);
                    }

                   FormsAuthentication.SignOut();

                    BusinessPrincipal.Logout();
                }
                else
                {
                    Csla.ApplicationContext.User = principal;
                }
            }
        }

Not sure if that helps or not ... I'll be honest, we've never load tested our application, but have yet to run into this problem ... is this something new and/or isolated?

jwright replied on Tuesday, January 25, 2011

Actually, this code may work for our situation. I will change this and test.

I have the same exact code on other MVC projects but I have not load tested those.

mattruma replied on Tuesday, January 25, 2011

Have you tried load testing it on a different server? Wonder if it's the application and/or a setting with IIS?

jwright replied on Tuesday, January 25, 2011

We have load tested it on 4 different servers :( They are all set up the same way though.

ajj3085 replied on Thursday, January 27, 2011

AquireSessionState isn't the supported place to handle this.  It should be done in AuthenticateRequest.  At that point you won't have access to Session, which should be ok.  Storing the principal in session opens users to session hijacking attempts.

I recently had to rework this same code, and I moved the principal from Session into Cache.  This might not work for you under load though, but you'd need to figure out some durable place to store the principal. 

Also know that under some conditions (I'm not sure why) the Request isn't authenticated and I found I had to store something in the formsauthcookie to use to lookup the principal and put it back into ApplicationContext.User.

Copyright (c) Marimer LLC