Challenge:
I am sure most of you must’ve heard/used Virtual user. Last week. We faced interesting scenario with Virtual user. Which took lot of our time. And I think it will be good idea to share with you. If you are new to Virtual user then this will be a good read for you : http://www.sitecoreinsight.com/lightweight-authentication-using-virtual-users-in-sitecore/
We have an E-Commerce site — Where We allow user to place an order without creating a user (Order as Guest) [In backed we create Virtual user for that user’s email address]. Then after that let’s say – User tried to create account and we had some custom profile fields. Which were not getting saved. And those fields were critical for us. Because our Login logic uses those fields.
If we directly create user without logging in as guest — It works without any problem.
We’ve been able to reproduce this issue in plain Sitecore as well.
Solution:
Initially we took sometime to understand what’s going on. Then after spending sometime we’ve been able to find that it happens because of RuntimeSetting property. It returns true. If you’ve created Virtual user and logged in with it earlier. And then when you call Profile.Save(); If this property is true. Sitecore will not save user’s profile data. See following code from : UserProfile class
public override void Save() { this.innerMembershipUser = null; bool flag = (this.ProfileUser == null) || !this.ProfileUser.RuntimeSettings.IsVirtual; if (flag) { this.SaveUserProperties(); } User profileUser = this.ProfileUser; if (profileUser != null) { profileUser.RuntimeSettings.Serialize(); } this.SerializeCustomProperties(); if (flag) { base.Save(); } this.RaiseUserUpdatedEvent(); }
So, you might get impatient and say. Let’s set User.RuntimeSettings.IsVirtual = false; and try it again! We also did the same. But that didn’t fixed the issue [“Life is not as easy as it seems to be!”]. Because as soon as we set IsVirtual to false. Create user and check IsVirtual property Sitecore sets it as true. Then we thought to check UserRuntimeSettings property
public virtual UserRuntimeSettings RuntimeSettings { get { if (this._runtimeSettings == null) { this._runtimeSettings = new UserRuntimeSettings(this.Name); string str = ClientContext.GetValue("SC_USR_" + this.Name) as string; if (!string.IsNullOrEmpty(str)) { this._runtimeSettings.Deserialize(str); } } return this._runtimeSettings; } }
As you can see in code. Sitecore does cache value against username and it also does deserialization business!
Then on delving further we found that ClientContext internally uses ClientData cache. Then we thought we should read and understand more about ClientData. Because that’s the lead we had to work upon. Which was tough task. Because I haven’t found very good detail on this anywhere. Then we thought to use our best friend — Reflector. After delving more and reading more. Here’s what we found:
- ClientData stores cache values in “ClientData” cache (BTW, We tried clearing this cache as well. But with no luck. As it will fetch it from DB)
- It also stores value using <clientDataStore type=”Sitecore.Data.$(database).$(database)ClientDataStore, Sitecore.Kernel”> which will be replaced to this if you are using SQL Server : Sitecore.Data.SqlServer.SqlServerClientDataStore, Sitecore.Kernel. It stores value in ClientData table in Core DB (This value is configurable in clientDataStore node) and for how long gets decided by object lifetime parameter. Which was 1 hour in our scenario
- The Caching.DefaultClientDataCacheSize setting in the web.config file specifies the size of the client data store cache.
- To disableClientData attribute of each /configuration/Sitecore/sites/site element in the web.config file enables or disables client data caching for that managed Website.
I think this information was good to build our theory. Let’s see what was happening in our scenario:
- As soon as we build virtual user. Sitecore stores that user’s information in Core.ClientData table
- To make it faster. Sitecore stores in ClientData cache as well
- Initially we tried clearing cache/restating app pool as well. But value was getting fetched from DB as Object lifetime was 1 Hour
- And if we directly clear value in Core.ClientData table. Then it was fetching value from Cache
This theory makes sense? Cool! So, the solution was to clean value from Both Core.ClientData and ClientData cache before creating user. And then this is what we came up with (Credits : Nikki Punjabi):
if (Sitecore.Context.ClientData != null) { var ClientDataValues = Sitecore.Context.ClientData.GetValueNames(); if (ClientDataValues != null && ClientDataValues.Any()) { var KeyName = "SC_USR_" + userName; var clientDataValue = Sitecore.Context.ClientData.GetValue(KeyName); if (clientDataValue != null) { Sitecore.Context.ClientData.RemoveValue(KeyName); } } }
We kept this code. Before creating user and it fixed this issue! We confirmed this fix with Sitecore as well. And they have also approved this fix!
So, now you can also easily create your Virtual Sitecore users to Real Sitecore users!
It would have been easy for me to type directly solution. But I wanted to spend sometime and pen down full process. If you are not facing this issue. But If you follow basic steps. You can fix any issue. “If you can break any BIG issue in small parts, You can fix any issue”
Thanks to Nikki who worked on this issue. And Reflector – Without whom we would have not been able to fix this (or many) issue – God bless Reflector developer! And thanks to our boss Sabin for buying us Reflector license!