Base solution for your next web application
Ends in:
01 DAYS
01 HRS
01 MIN
01 SEC
Open Closed

Registration Pattern #6513


User avatar
0
andmattia created

I try to implement my custom Session to store some information (es. companyId) I use this code

 public interface IMySession : ITransientDependency
    {
        long CompanyId { get; set; }
        void SetCompanyId(long companyId);
    }

    public class NullableMySession : IMySession
    {
        private static readonly NullablemySession SingletonInstance = new NullableMySession();

        public static NullableMySession Instance
        {
            get { return NullableMySession.SingletonInstance; }
        }

        public long CompanyId { get; set; }
        public void SetCompanyId(long companyId)
        {
            
        }
    }
    
    public class MySession : IMySession
    {
        public long CompanyId
        {
            get;
            set;
            //get
            //{
            //    var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
            //    if (claimsPrincipal == null)
            //    {
            //        return 0;
            //    }

            //    var company = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == myConsts.SessionCompany);
            //    if (company == null || string.IsNullOrEmpty(company.Value))
            //    {
            //        return 0;
            //    }

            //    return System.Convert.ToInt32(company.Value);
            //}
        }

        public void SetCompanyId(long companyId)
        {
            var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
            var claimsIdentity= Thread.CurrentPrincipal.Identity as ClaimsIdentity;
            
            var company = claimsPrincipal.Claims.FirstOrDefault(c=> c.Type == myConsts.SessionCompany);
            if (company != null)
            {
                claimsIdentity.RemoveClaim(company);
                
            }
            claimsIdentity.AddClaim(new Claim(myConsts.SessionCompany, companyId.ToString()));
        }
    }

If I try to set value after login on Principal the new value is not persisted on Principal, so I move to simple solution store on session. I add a

        public IMySession MySession { get; set; }

        protected demoAppServiceBase()
        {
            LocalizationSourceName = clayConsts.LocalizationSourceName;
            MySession = NullableMySession.Instance;
        }

But Any time to redo a call from API the value is not persisted on mySession so I add the row on coreModule preInitialize

IocManager.IocContainer.Register(Component.For<IMySession, MySession>().ImplementedBy<MySession>());

After do that the value is persistend on all call, but is it the correct way or I make some mistakes?

Mattia


12 Answer(s)
  • User Avatar
    0
    aaron created
    Support Team

    You should get CompanyId from claims, instead of storing it in a property on a singleton. Why did you comment that out?

  • User Avatar
    0
    andmattia created

    I try that way but when I need to change that value is impossible... the value is store on claims during the call but on second call the value is disapeared

  • User Avatar
    0
    aaron created
    Support Team

    You should not change claims dynamically. Do it when ClaimsPrincipal is created:

    https://gist.github.com/ismcagdas/6f2a9a3b5d7b907cb8d94a72124d59a1

  • User Avatar
    0
    andmattia created

    @aaron this work on ABPZERO core, my solution is on .NET 4.6.1. Any suggestion?

  • User Avatar
    0
    aaron created
    Support Team

    Do you mean MVC 5?

  • User Avatar
    0
    andmattia created

    Yes

  • User Avatar
    0
    aaron created
    Support Team

    https://gist.github.com/hikalkan/67469e05475c2d18cb88

  • User Avatar
    0
    andmattia created

    @aaron my test work fine and mY custom session do what I need.

    So know I found an issue, during debugging. If I restart the debug session my custom will be lost until I redo a F5 on my browser. When I do that ABP call SessionAppService and in GetCurrentLoginInformations I reset the data for my custom session.

    The same happen on login in account controller. But if I don't do a F5 AbpSession is populate (I read the abpSession is on cookie) but my session no wich is the entrt point where abpsession is restored from cookies?

  • User Avatar
    0
    aaron created
    Support Team

    AbpSession doesn't use cookies. ABP is open source, so you can see the source code of ClaimsAbpSession.

  • User Avatar
    0
    andmattia created

    @aaron thank for the calrification but if I call from my web site after debug restart I found data in my AbpSession, so some method call AbpSession to reload it (like when you do the login or do a reload page) I need to identify this event and add my logic to restore my data from DB

  • User Avatar
    0
    aaron created
    Support Team

    ClaimsAbpSession gets values from the claims on the fly. Please see the source code that has been linked for your convenience.

    The sample code for adding a new property to session has also been provided in the Gist linked a little further up.

  • User Avatar
    0
    andmattia created

    Hi @aaron

    I complete the integration with my custom session (ABP MVC + ZERO ver 6.6.1). During test I use a single tenant but after move to production we see a strnge situation in some case our session change the value from user to use, this happen because we use the AppSessionAppService to inject our session and any use call this method (login or F5) the session change with the lastest one.

    I search the correct place to put my custom session and I found it in UserManager

     public override async Task<ClaimsIdentity> CreateIdentityAsync(User user, string authenticationType)
            {
                var identity = await base.CreateIdentityAsync(user, authenticationType);
                using (var uo = _unitOfWorkManager.Begin())
                {
                    _unitOfWorkManager.Current.SetTenantId(user.TenantId);
                    // Read Company for user from db
                    var companyId = await _settingManager.GetSettingValueForUserAsync<long>("userSettings.CompanyId",
                        user.TenantId, user.Id);
                    
                    identity.AddClaim(new Claim(CustomClaim.ClaimCompany, companyId.ToString()));
                    
                    uo.Complete();
                }
                return identity;
            }
    

    In this way my user has correct value on every roundtrip.

    The only problem is when I need to change the value of my session data and is not allowed so I need to force a signIn & signOut

                User user;
                ClaimsIdentity identity;
                using (var uo = _unitOfWorkManager.Begin())
                {
                    user = await ClayUserManager.FindByIdAsync(AbpSession.UserId.Value);
                    identity = await ClayUserManager.CreateIdentityAsync(user,
                        DefaultAuthenticationTypes.ApplicationCookie);
    
                    uo.Complete();
                }
    

    I need to use UOW beacuse in code above set the new value on DB. So in that way I can use my custom sassion

    public interface IMySession : ITransientDependency
        {
            long? CompanyId { get; }
        }
    

    And the implementation is

    //// Take care of name to use auto registration path MySession -> IMySession 
     public class MySession : ClaimsAbpSession, IMySession
        {
            public MySession(
                IPrincipalAccessor principalAccessor,
                IMultiTenancyConfig multiTenancy,
                ITenantResolver tenantResolver,
                IAmbientScopeProvider<SessionOverride> sessionOverrideScopeProvider) :
                base(principalAccessor, multiTenancy, tenantResolver, sessionOverrideScopeProvider)
            {
    
            }
    
            public long? CompanyId
            {
                get
                {
                    var company = PrincipalAccessor.Principal?.Claims.FirstOrDefault(c => c.Type == appConsts.ClaimCompany);
                    if (string.IsNullOrEmpty(company?.Value))
                    {
                        return null;
                    }
    
                    return System.Convert.ToInt64(company.Value);
                }
                //...
            }
        }
    
    

    Now all data is stored on claim and user filter company works fine based on session value.

    I hope this explanation can be useful for other user.